Rewrite plugin installer as a declarative manifest/engine framework for reliable mix-and-match plugin installs, with backup/restore support and assorted plugin fixes (Hardcover file API, Bookerly/Hardcover SettingAction registration, GitHubSync include path, Lockscreen render timing)
This commit is contained in:
1 parent
3ad7a7ab39
commit
fec251f4d1
23 files changed
+2079
-3243
No files matched your search
@@ -1,425 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import shutil
|
||||
import hashlib
|
||||
import subprocess
|
||||
|
||||
|
||||
def read_file(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
def write_file(path, content):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
def find_first(filename, repo_dir):
|
||||
results = glob.glob(os.path.join(repo_dir, "**", filename), recursive=True)
|
||||
if not results:
|
||||
sys.exit(f"ERROR: Could not locate {filename} in {repo_dir}")
|
||||
return results[0]
|
||||
|
||||
def font_id_from_files(paths):
|
||||
total = 0
|
||||
for p in paths:
|
||||
h = hashlib.sha256(open(p, "rb").read()).hexdigest()
|
||||
total += int(h, 16)
|
||||
return total % (2 ** 32) - (2 ** 31)
|
||||
|
||||
|
||||
def generate_fonts(plugin_dir, repo_dir):
|
||||
scripts_dir = os.path.join(repo_dir, "lib", "EpdFont", "scripts")
|
||||
fonts_dir = os.path.join(repo_dir, "lib", "EpdFont", "builtinFonts")
|
||||
source_dir = os.path.join(plugin_dir, "fonts")
|
||||
fontconvert = os.path.join(scripts_dir, "fontconvert.py")
|
||||
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "freetype-py==2.5.1", "fonttools", "--break-system-packages", "-q"],
|
||||
check=True
|
||||
)
|
||||
|
||||
sizes = [12, 14, 16, 18]
|
||||
styles = ["Regular"]
|
||||
|
||||
for size in sizes:
|
||||
for style in styles:
|
||||
fname = f"bookerly_{size}_{style.lower()}"
|
||||
out_path = os.path.join(fonts_dir, f"{fname}.h")
|
||||
if os.path.exists(out_path):
|
||||
print(f" already exists: {fname}.h")
|
||||
continue
|
||||
src_path = os.path.join(source_dir, f"Bookerly-{style}.ttf")
|
||||
if not os.path.exists(src_path):
|
||||
sys.exit(f"ERROR: Missing font file: {src_path}")
|
||||
with open(out_path, "w") as f:
|
||||
result = subprocess.run(
|
||||
[sys.executable, fontconvert, fname, str(size), src_path, "--2bit", "--compress"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0 or not result.stdout.strip():
|
||||
print(f" ERROR generating {fname}: {result.stderr[:200]}")
|
||||
if os.path.exists(out_path):
|
||||
os.remove(out_path)
|
||||
continue
|
||||
f.write(result.stdout)
|
||||
print(f" ✓ generated {fname}.h")
|
||||
|
||||
return fonts_dir
|
||||
|
||||
|
||||
def compute_font_ids(fonts_dir):
|
||||
ids = {}
|
||||
sizes = [12, 14, 16, 18]
|
||||
for size in sizes:
|
||||
path = os.path.join(fonts_dir, f"bookerly_{size}_regular.h")
|
||||
if os.path.exists(path):
|
||||
h = hashlib.sha256(open(path, "rb").read()).hexdigest()
|
||||
ids[f"BOOKERLY_{size}_FONT_ID"] = int(h, 16) % (2 ** 32) - (2 ** 31)
|
||||
return ids
|
||||
|
||||
|
||||
def patch_font_ids_h(repo_dir, new_ids):
|
||||
path = find_first("fontIds.h", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
lines = []
|
||||
for name, val in new_ids.items():
|
||||
if name not in content:
|
||||
lines.append(f"#define {name} ({val})")
|
||||
|
||||
if not lines:
|
||||
print(" fontIds.h already patched, skipping.")
|
||||
return
|
||||
|
||||
write_file(path, content.rstrip() + "\n" + "\n".join(lines) + "\n")
|
||||
print(f" fontIds.h: added {len(lines)} new font IDs.")
|
||||
|
||||
|
||||
def patch_all_h(repo_dir, new_ids):
|
||||
path = find_first("all.h", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
added = 0
|
||||
sizes = [12, 14, 16, 18]
|
||||
for size in sizes:
|
||||
key = f"BOOKERLY_{size}_FONT_ID"
|
||||
if key not in new_ids:
|
||||
continue
|
||||
fname = f"bookerly_{size}_regular"
|
||||
inc = f'#include <builtinFonts/{fname}.h>'
|
||||
if inc not in content:
|
||||
content += inc + "\n"
|
||||
added += 1
|
||||
|
||||
if added:
|
||||
write_file(path, content)
|
||||
print(f" all.h: added {added} includes.")
|
||||
else:
|
||||
print(" all.h already patched, skipping.")
|
||||
|
||||
|
||||
def patch_main_cpp(repo_dir, new_ids):
|
||||
path = find_first("main.cpp", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
sizes = [8, 10, 12, 14, 16, 18]
|
||||
decl_lines = []
|
||||
insert_lines = []
|
||||
|
||||
for size in sizes:
|
||||
key = f"BOOKERLY_{size}_FONT_ID"
|
||||
varbase = f"bookerly{size}"
|
||||
if key not in new_ids:
|
||||
continue
|
||||
if f"EpdFontFamily {varbase}FontFamily" in content:
|
||||
continue
|
||||
decl_lines += [
|
||||
f'EpdFont {varbase}RegularFont(&bookerly_{size}_regular);',
|
||||
f'EpdFontFamily {varbase}FontFamily(&{varbase}RegularFont, &{varbase}RegularFont, &{varbase}RegularFont, &{varbase}RegularFont);',
|
||||
]
|
||||
insert_lines.append(f' renderer.insertFont({key}, {varbase}FontFamily);')
|
||||
|
||||
if not decl_lines:
|
||||
print(" main.cpp already patched, skipping.")
|
||||
return
|
||||
|
||||
anchor_decl = '#ifndef OMIT_FONTS'
|
||||
content = content.replace(anchor_decl, "\n".join(decl_lines) + "\n" + anchor_decl, 1)
|
||||
|
||||
anchor_insert = ' renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);'
|
||||
content = content.replace(anchor_insert, "\n".join(insert_lines) + "\n" + anchor_insert, 1)
|
||||
|
||||
write_file(path, content)
|
||||
print(f" main.cpp: added {len(decl_lines) // 2} font families.")
|
||||
|
||||
|
||||
def patch_cross_point_settings_h(repo_dir):
|
||||
path = find_first("CrossPointSettings.h", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
if "BOOKERLY = 3" in content:
|
||||
print(" CrossPointSettings.h already patched, skipping.")
|
||||
return
|
||||
|
||||
if '"activities/settings/BookerlyPlugin.h"' not in content:
|
||||
content = content.replace(
|
||||
'#include "activities/settings/SmallerFontsPlugin.h"',
|
||||
'#include "activities/settings/SmallerFontsPlugin.h"\n#include "activities/settings/BookerlyPlugin.h"'
|
||||
)
|
||||
|
||||
if 'BOOKERLY = 3' not in content:
|
||||
content = content.replace(
|
||||
'enum FONT_FAMILY { NOTOSERIF = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_FAMILY_COUNT };',
|
||||
'enum FONT_FAMILY { NOTOSERIF = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, BOOKERLY = 3, FONT_FAMILY_COUNT };'
|
||||
)
|
||||
|
||||
|
||||
|
||||
write_file(path, content)
|
||||
print(" CrossPointSettings.h patched.")
|
||||
|
||||
|
||||
def patch_cross_point_settings_cpp(repo_dir, new_ids):
|
||||
path = find_first("CrossPointSettings.cpp", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
if "BOOKERLY" in content:
|
||||
print(" CrossPointSettings.cpp already patched, skipping.")
|
||||
return
|
||||
|
||||
|
||||
content = content.replace(
|
||||
' case OPENDYSLEXIC:\n switch (lineSpacing) {\n case TIGHT:\n return 0.90f;\n case NORMAL:\n default:\n return 0.95f;\n case WIDE:\n return 1.0f;\n }\n }\n}\n\nunsigned long',
|
||||
' case OPENDYSLEXIC:\n switch (lineSpacing) {\n case TIGHT:\n return 0.90f;\n case NORMAL:\n default:\n return 0.95f;\n case WIDE:\n return 1.0f;\n }\n case BOOKERLY:\n switch (lineSpacing) {\n case TIGHT:\n return 0.95f;\n case NORMAL:\n default:\n return 1.0f;\n case WIDE:\n return 1.1f;\n }\n }\n}\n\nunsigned long'
|
||||
)
|
||||
|
||||
use_resolve = 'resolve' in content
|
||||
bookerly_return = 'resolve(BOOKERLY_12_FONT_ID)' if use_resolve else 'BOOKERLY_12_FONT_ID'
|
||||
bookerly_case = (
|
||||
' case BOOKERLY:\n'
|
||||
f' return {bookerly_return};\n'
|
||||
)
|
||||
|
||||
anchor = None
|
||||
for candidate in [
|
||||
' case EXTRA_LARGE:\n return resolve(OPENDYSLEXIC_14_FONT_ID);\n }\n }\n}\n',
|
||||
' case EXTRA_LARGE:\n return OPENDYSLEXIC_14_FONT_ID;\n }\n }\n}\n',
|
||||
]:
|
||||
if candidate in content:
|
||||
anchor = candidate
|
||||
break
|
||||
|
||||
if anchor:
|
||||
content = content.replace(
|
||||
anchor,
|
||||
' case EXTRA_LARGE:\n return ' + ('resolve(OPENDYSLEXIC_14_FONT_ID)' if 'resolve' in anchor else 'OPENDYSLEXIC_14_FONT_ID') + ';\n }\n'
|
||||
+ bookerly_case +
|
||||
' }\n}\n'
|
||||
)
|
||||
|
||||
write_file(path, content)
|
||||
print(" CrossPointSettings.cpp patched.")
|
||||
|
||||
|
||||
def patch_settings_list_h(repo_dir):
|
||||
path = find_first("SettingsList.h", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
already_patched = 'STR_NONE_OPT}, "fontFamily"' in content and 'bookerlyInstalled' in content
|
||||
if already_patched:
|
||||
print(" SettingsList.h already patched, skipping.")
|
||||
return
|
||||
|
||||
if 'STR_NONE_OPT}, "fontFamily"' not in content:
|
||||
content = content.replace(
|
||||
'{StrId::STR_NOTO_SERIF, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, "fontFamily"',
|
||||
'{StrId::STR_NOTO_SERIF, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC, StrId::STR_NONE_OPT}, "fontFamily"'
|
||||
)
|
||||
|
||||
|
||||
write_file(path, content)
|
||||
print(" SettingsList.h patched.")
|
||||
|
||||
|
||||
def patch_settings_activity_h(repo_dir):
|
||||
path = find_first("SettingsActivity.h", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
if 'BookerlyInstalled' in content:
|
||||
print(" SettingsActivity.h already patched, skipping.")
|
||||
return
|
||||
|
||||
if 'categoryCount = 4' in content:
|
||||
content = content.replace(
|
||||
'static constexpr int categoryCount = 4;',
|
||||
'static constexpr int categoryCount = 5;'
|
||||
)
|
||||
|
||||
if 'pluginsSettings' not in content:
|
||||
content = content.replace(
|
||||
' std::vector<SettingInfo> systemSettings;',
|
||||
' std::vector<SettingInfo> systemSettings;\n std::vector<SettingInfo> pluginsSettings;'
|
||||
)
|
||||
|
||||
content = content.replace(
|
||||
' PongGame,\n};',
|
||||
' PongGame,\n BookerlyInstalled,\n};'
|
||||
)
|
||||
|
||||
write_file(path, content)
|
||||
print(" SettingsActivity.h patched.")
|
||||
|
||||
|
||||
def patch_settings_activity_cpp(repo_dir):
|
||||
path = find_first("SettingsActivity.cpp", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
if '"Bookerly Font"' in content:
|
||||
print(" SettingsActivity.cpp already patched, skipping.")
|
||||
return
|
||||
|
||||
content = content.replace(
|
||||
'#include "fontIds.h"',
|
||||
'#include "fontIds.h"\n#include "BookerlyPlugin.h"'
|
||||
)
|
||||
|
||||
if 'SettingAction::BookerlyInstalled' not in content:
|
||||
content = content.replace(
|
||||
' readerSettings.push_back(SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));\n',
|
||||
' readerSettings.push_back(SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));\n'
|
||||
' pluginsSettings.push_back(SettingInfo::Action(\n'
|
||||
' StrId::STR_NONE_OPT, SettingAction::BookerlyInstalled\n'
|
||||
' ));\n'
|
||||
)
|
||||
|
||||
if '"Bookerly Font"' not in content:
|
||||
content = content.replace(
|
||||
' [&settings](int index) { return std::string(I18N.get(settings[index].nameId)); }, nullptr, nullptr,',
|
||||
' [&settings, this](int index) -> std::string {\n'
|
||||
' if (selectedCategoryIndex == 4) {\n'
|
||||
' const auto& s = settings[index];\n'
|
||||
' if (s.type == SettingType::ACTION && s.action == SettingAction::BookerlyInstalled) return "Bookerly Font";\n'
|
||||
' }\n'
|
||||
' return std::string(I18N.get(settings[index].nameId));\n'
|
||||
' }, nullptr, nullptr,'
|
||||
)
|
||||
|
||||
if '"Bookerly"' not in content:
|
||||
content = content.replace(
|
||||
' const uint8_t value = SETTINGS.*(setting.valuePtr);\n'
|
||||
' valueText = I18N.get(setting.enumValues[value]);\n',
|
||||
' const uint8_t value = SETTINGS.*(setting.valuePtr);\n'
|
||||
' if (setting.key && std::string(setting.key) == "fontFamily" && value == CrossPointSettings::BOOKERLY) {\n'
|
||||
' valueText = "Bookerly";\n'
|
||||
' } else {\n'
|
||||
' valueText = I18N.get(setting.enumValues[value]);\n'
|
||||
' }\n'
|
||||
)
|
||||
|
||||
if '"Installed"' not in content:
|
||||
content = content.replace(
|
||||
' } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {\n'
|
||||
' valueText = std::to_string(SETTINGS.*(setting.valuePtr));\n'
|
||||
' }',
|
||||
' } else if (setting.type == SettingType::ACTION && setting.action == SettingAction::BookerlyInstalled) {\n'
|
||||
' valueText = "Installed";\n'
|
||||
' } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {\n'
|
||||
' valueText = std::to_string(SETTINGS.*(setting.valuePtr));\n'
|
||||
' }'
|
||||
)
|
||||
|
||||
write_file(path, content)
|
||||
print(" SettingsActivity.cpp patched.")
|
||||
|
||||
def patch_web_server(repo_dir):
|
||||
path = find_first("CrossPointWebServer.cpp", repo_dir)
|
||||
content = read_file(path)
|
||||
|
||||
if '"Bookerly"' in content:
|
||||
print(" CrossPointWebServer.cpp already patched, skipping.")
|
||||
return
|
||||
|
||||
content = content.replace(
|
||||
' } else if (strcmp(s.key, "smallerFontsMode") == 0) {\n'
|
||||
' doc["name"] = "Smaller Fonts";\n'
|
||||
' doc["category"] = "Plugins";\n'
|
||||
' }\n'
|
||||
' }\n',
|
||||
' } else if (strcmp(s.key, "smallerFontsMode") == 0) {\n'
|
||||
' doc["name"] = "Smaller Fonts";\n'
|
||||
' doc["category"] = "Plugins";\n'
|
||||
' } else if (strcmp(s.key, "fontFamily") == 0) {\n'
|
||||
' JsonArray opts = doc["options"].to<JsonArray>();\n'
|
||||
' opts.add("Noto Serif");\n'
|
||||
' opts.add("Noto Sans");\n'
|
||||
' opts.add("OpenDyslexic");\n'
|
||||
' opts.add("Bookerly");\n'
|
||||
' }\n'
|
||||
' }\n'
|
||||
)
|
||||
|
||||
write_file(path, content)
|
||||
print(" CrossPointWebServer.cpp patched.")
|
||||
|
||||
|
||||
def copy_plugin_sources(plugin_dir, repo_dir):
|
||||
dest = os.path.join(repo_dir, "src", "activities", "settings")
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
|
||||
for fname in os.listdir(plugin_dir):
|
||||
if fname.endswith((".h", ".cpp", ".hpp")):
|
||||
src = os.path.join(plugin_dir, fname)
|
||||
dst = os.path.join(dest, fname)
|
||||
shutil.copy2(src, dst)
|
||||
print(f" ✓ {fname}")
|
||||
|
||||
|
||||
def patch(repo_dir: str):
|
||||
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
print(" Copying plugin sources...")
|
||||
copy_plugin_sources(plugin_dir, repo_dir)
|
||||
|
||||
print(" Generating Bookerly font files...")
|
||||
fonts_dir = generate_fonts(plugin_dir, repo_dir)
|
||||
|
||||
print(" Computing font IDs...")
|
||||
new_ids = compute_font_ids(fonts_dir)
|
||||
for k, v in new_ids.items():
|
||||
print(f" {k} = {v}")
|
||||
|
||||
print(" Patching fontIds.h...")
|
||||
patch_font_ids_h(repo_dir, new_ids)
|
||||
|
||||
print(" Patching all.h...")
|
||||
patch_all_h(repo_dir, new_ids)
|
||||
|
||||
print(" Patching main.cpp...")
|
||||
patch_main_cpp(repo_dir, new_ids)
|
||||
|
||||
print(" Patching CrossPointSettings.h...")
|
||||
patch_cross_point_settings_h(repo_dir)
|
||||
|
||||
print(" Patching CrossPointSettings.cpp...")
|
||||
patch_cross_point_settings_cpp(repo_dir, new_ids)
|
||||
|
||||
print(" Patching SettingsList.h...")
|
||||
patch_settings_list_h(repo_dir)
|
||||
|
||||
print(" Patching SettingsActivity.h...")
|
||||
patch_settings_activity_h(repo_dir)
|
||||
|
||||
print(" Patching SettingsActivity.cpp...")
|
||||
patch_settings_activity_cpp(repo_dir)
|
||||
|
||||
print(" Patching CrossPointWebServer.cpp (web UI)...")
|
||||
patch_web_server(repo_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python patch.py <path-to-crosspoint-repo>")
|
||||
patch(sys.argv[1])
|
||||
@@ -0,0 +1,288 @@
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
import subprocess
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
from framework.manifest import (
|
||||
PluginManifest, SourceFile, PluginsTabEntry, EnumValueOverride, WebOptionAppend, SettingActionEnumValue,
|
||||
)
|
||||
from framework.engine import find_first, read_file, write_file, PatchError, replace_once, contains_once
|
||||
|
||||
PLUGIN_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# NOTE: this only extends the *static* built-in font-family list
|
||||
# (SettingsList.h's plain SettingInfo::Enum entry). CrossPoint also has a
|
||||
# second, SD-card-font-aware variant (buildFontFamilySetting()) that swaps in
|
||||
# when the user has SD-card fonts installed - Bookerly won't appear there.
|
||||
# That's a narrow pre-existing edge case (SD-card fonts + Bookerly at once)
|
||||
# left as a known follow-up rather than something this pass fully covers.
|
||||
|
||||
|
||||
def _generate_fonts(ctx):
|
||||
fonts_dir = os.path.join(ctx.repo_dir, "lib", "EpdFont", "builtinFonts")
|
||||
scripts_dir = os.path.join(ctx.repo_dir, "lib", "EpdFont", "scripts")
|
||||
source_dir = os.path.join(PLUGIN_DIR, "fonts")
|
||||
fontconvert = os.path.join(scripts_dir, "fontconvert.py")
|
||||
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "freetype-py==2.5.1", "fonttools", "--break-system-packages", "-q"],
|
||||
check=True,
|
||||
)
|
||||
|
||||
sizes = [12, 14, 16, 18]
|
||||
for size in sizes:
|
||||
fname = f"bookerly_{size}_regular"
|
||||
out_path = os.path.join(fonts_dir, f"{fname}.h")
|
||||
if os.path.exists(out_path):
|
||||
print(f" already exists: {fname}.h")
|
||||
continue
|
||||
src_path = os.path.join(source_dir, "Bookerly-Regular.ttf")
|
||||
if not os.path.exists(src_path):
|
||||
raise PatchError(f"Missing font file: {src_path}")
|
||||
result = subprocess.run(
|
||||
[sys.executable, fontconvert, fname, str(size), src_path, "--2bit", "--compress"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if result.returncode != 0 or not result.stdout.strip():
|
||||
raise PatchError(f"Failed generating {fname}: {result.stderr[:300]}")
|
||||
with open(out_path, "w") as f:
|
||||
f.write(result.stdout)
|
||||
print(f" ✓ generated {fname}.h")
|
||||
|
||||
ids = {}
|
||||
for size in sizes:
|
||||
path = os.path.join(fonts_dir, f"bookerly_{size}_regular.h")
|
||||
h = hashlib.sha256(open(path, "rb").read()).hexdigest()
|
||||
ids[f"BOOKERLY_{size}_FONT_ID"] = int(h, 16) % (2 ** 32) - (2 ** 31)
|
||||
ctx.bookerly_font_ids = ids
|
||||
|
||||
|
||||
def _patch_font_registration(ctx):
|
||||
ids = ctx.bookerly_font_ids
|
||||
|
||||
# fontIds.h
|
||||
path = find_first("fontIds.h", ctx.repo_dir)
|
||||
content = read_file(path)
|
||||
lines = [f"#define {name} ({val})" for name, val in ids.items() if name not in content]
|
||||
if lines:
|
||||
write_file(path, content.rstrip() + "\n" + "\n".join(lines) + "\n")
|
||||
print(f" fontIds.h: added {len(lines)} font ID(s).")
|
||||
|
||||
# all.h
|
||||
path = find_first("all.h", ctx.repo_dir)
|
||||
content = read_file(path)
|
||||
added = 0
|
||||
for size in [12, 14, 16, 18]:
|
||||
inc = f"#include <builtinFonts/bookerly_{size}_regular.h>"
|
||||
if inc not in content:
|
||||
content += inc + "\n"
|
||||
added += 1
|
||||
if added:
|
||||
write_file(path, content)
|
||||
print(f" all.h: added {added} include(s).")
|
||||
|
||||
# main.cpp: font family declarations + registration
|
||||
path = find_first("main.cpp", ctx.repo_dir)
|
||||
content = read_file(path)
|
||||
decl_lines, insert_lines = [], []
|
||||
for size in [12, 14, 16, 18]:
|
||||
var = f"bookerly{size}"
|
||||
if f"EpdFontFamily {var}FontFamily" in content:
|
||||
continue
|
||||
decl_lines += [
|
||||
f"EpdFont {var}RegularFont(&bookerly_{size}_regular);",
|
||||
f"EpdFontFamily {var}FontFamily(&{var}RegularFont, &{var}RegularFont, &{var}RegularFont, &{var}RegularFont);",
|
||||
]
|
||||
insert_lines.append(f" renderer.insertFont(BOOKERLY_{size}_FONT_ID, {var}FontFamily);")
|
||||
if decl_lines:
|
||||
# "#ifndef OMIT_FONTS" legitimately appears twice (global font
|
||||
# declarations, then again inside setup() around insertFont calls) -
|
||||
# we specifically want the *first* (global declaration) occurrence,
|
||||
# which is what replace(..., count=1) targets.
|
||||
anchor_decl = "#ifndef OMIT_FONTS"
|
||||
if anchor_decl not in content:
|
||||
raise PatchError("main.cpp: could not find `#ifndef OMIT_FONTS` (font declarations section)")
|
||||
content = content.replace(anchor_decl, "\n".join(decl_lines) + "\n" + anchor_decl, 1)
|
||||
|
||||
anchor_insert = " renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);"
|
||||
contains_once(content, anchor_insert, "main.cpp")
|
||||
content = content.replace(anchor_insert, "\n".join(insert_lines) + "\n" + anchor_insert, 1)
|
||||
|
||||
write_file(path, content)
|
||||
print(f" main.cpp: registered {len(decl_lines) // 2} Bookerly size(s).")
|
||||
|
||||
|
||||
def _patch_font_family_enum(ctx):
|
||||
path = find_first("CrossPointSettings.h", ctx.repo_dir)
|
||||
content = read_file(path)
|
||||
if "BOOKERLY" in content:
|
||||
return
|
||||
m = re.search(r"enum FONT_FAMILY \{(.*?)FONT_FAMILY_COUNT\s*\};", content)
|
||||
if not m:
|
||||
raise PatchError("CrossPointSettings.h: could not find `enum FONT_FAMILY { ... FONT_FAMILY_COUNT };`")
|
||||
content = content[:m.start()] + m.group(0).replace("FONT_FAMILY_COUNT", "BOOKERLY, FONT_FAMILY_COUNT", 1) + content[m.end():]
|
||||
write_file(path, content)
|
||||
print(" CrossPointSettings.h: added FONT_FAMILY::BOOKERLY.")
|
||||
|
||||
|
||||
def _patch_line_spacing_and_reader_font_id(ctx):
|
||||
path = find_first("CrossPointSettings.cpp", ctx.repo_dir)
|
||||
content = read_file(path)
|
||||
label = "CrossPointSettings.cpp"
|
||||
|
||||
if "case BOOKERLY:" not in content:
|
||||
spacing_anchor = (
|
||||
" case NOTOSANS:\n"
|
||||
" switch (lineSpacing) {\n"
|
||||
" case TIGHT:\n"
|
||||
" return 0.90f;\n"
|
||||
" case NORMAL:\n"
|
||||
" default:\n"
|
||||
" return 0.95f;\n"
|
||||
" case WIDE:\n"
|
||||
" return 1.0f;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
)
|
||||
spacing_replacement = (
|
||||
" case NOTOSANS:\n"
|
||||
" switch (lineSpacing) {\n"
|
||||
" case TIGHT:\n"
|
||||
" return 0.90f;\n"
|
||||
" case NORMAL:\n"
|
||||
" default:\n"
|
||||
" return 0.95f;\n"
|
||||
" case WIDE:\n"
|
||||
" return 1.0f;\n"
|
||||
" }\n"
|
||||
" case BOOKERLY:\n"
|
||||
" switch (lineSpacing) {\n"
|
||||
" case TIGHT:\n"
|
||||
" return 0.95f;\n"
|
||||
" case NORMAL:\n"
|
||||
" default:\n"
|
||||
" return 1.0f;\n"
|
||||
" case WIDE:\n"
|
||||
" return 1.1f;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
)
|
||||
content = replace_once(content, spacing_anchor, spacing_replacement, label)
|
||||
|
||||
if "BOOKERLY_12_FONT_ID" not in content:
|
||||
font_anchor = (
|
||||
" case NOTOSANS:\n"
|
||||
" switch (fontSize) {\n"
|
||||
" case SMALL:\n"
|
||||
" return NOTOSANS_12_FONT_ID;\n"
|
||||
" case MEDIUM:\n"
|
||||
" default:\n"
|
||||
" return NOTOSANS_14_FONT_ID;\n"
|
||||
" case LARGE:\n"
|
||||
" return NOTOSANS_16_FONT_ID;\n"
|
||||
" case EXTRA_LARGE:\n"
|
||||
" return NOTOSANS_18_FONT_ID;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
)
|
||||
font_replacement = (
|
||||
" case NOTOSANS:\n"
|
||||
" switch (fontSize) {\n"
|
||||
" case SMALL:\n"
|
||||
" return NOTOSANS_12_FONT_ID;\n"
|
||||
" case MEDIUM:\n"
|
||||
" default:\n"
|
||||
" return NOTOSANS_14_FONT_ID;\n"
|
||||
" case LARGE:\n"
|
||||
" return NOTOSANS_16_FONT_ID;\n"
|
||||
" case EXTRA_LARGE:\n"
|
||||
" return NOTOSANS_18_FONT_ID;\n"
|
||||
" }\n"
|
||||
" case BOOKERLY:\n"
|
||||
" switch (fontSize) {\n"
|
||||
" case SMALL:\n"
|
||||
" return BOOKERLY_12_FONT_ID;\n"
|
||||
" case MEDIUM:\n"
|
||||
" default:\n"
|
||||
" return BOOKERLY_14_FONT_ID;\n"
|
||||
" case LARGE:\n"
|
||||
" return BOOKERLY_16_FONT_ID;\n"
|
||||
" case EXTRA_LARGE:\n"
|
||||
" return BOOKERLY_18_FONT_ID;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
)
|
||||
content = replace_once(content, font_anchor, font_replacement, label)
|
||||
|
||||
write_file(path, content)
|
||||
print(" CrossPointSettings.cpp: Bookerly line-spacing + font-id cases added.")
|
||||
|
||||
|
||||
def _patch_settings_list_option(ctx):
|
||||
path = find_first("SettingsList.h", ctx.repo_dir)
|
||||
content = read_file(path)
|
||||
label = "SettingsList.h"
|
||||
anchor = (
|
||||
'SettingInfo::Enum(StrId::STR_FONT_FAMILY, &CrossPointSettings::fontFamily,\n'
|
||||
' {StrId::STR_NOTO_SERIF, StrId::STR_NOTO_SANS}, "fontFamily", StrId::STR_CAT_READER),'
|
||||
)
|
||||
if "StrId::STR_NONE_OPT}, \"fontFamily\"" in content:
|
||||
return
|
||||
replacement = (
|
||||
'SettingInfo::Enum(StrId::STR_FONT_FAMILY, &CrossPointSettings::fontFamily,\n'
|
||||
' {StrId::STR_NOTO_SERIF, StrId::STR_NOTO_SANS, StrId::STR_NONE_OPT}, "fontFamily", StrId::STR_CAT_READER),'
|
||||
)
|
||||
content = replace_once(content, anchor, replacement, label)
|
||||
write_file(path, content)
|
||||
print(" SettingsList.h: fontFamily gained a 3rd (Bookerly) option.")
|
||||
|
||||
|
||||
def _pre_patch(ctx):
|
||||
print(" Generating Bookerly font files...")
|
||||
_generate_fonts(ctx)
|
||||
|
||||
|
||||
def _post_patch(ctx):
|
||||
print(" Registering Bookerly font family...")
|
||||
_patch_font_registration(ctx)
|
||||
_patch_font_family_enum(ctx)
|
||||
_patch_line_spacing_and_reader_font_id(ctx)
|
||||
_patch_settings_list_option(ctx)
|
||||
|
||||
|
||||
def get_manifest(ctx):
|
||||
return PluginManifest(
|
||||
name="bookerly",
|
||||
pretty_name="Bookerly Font",
|
||||
phase=0, # must run before smallerfonts (phase 1) wraps getReaderFontId's returns
|
||||
source_files=[
|
||||
SourceFile("BookerlyPlugin.h", "src/activities/settings"),
|
||||
SourceFile("BookerlyPlugin.cpp", "src/activities/settings"),
|
||||
],
|
||||
setting_actions=[
|
||||
SettingActionEnumValue("BookerlyInstalled"),
|
||||
],
|
||||
plugins_tab_entries=[
|
||||
PluginsTabEntry(
|
||||
label="Bookerly Font",
|
||||
kind="action",
|
||||
action_name="BookerlyInstalled",
|
||||
action_value_text="Installed",
|
||||
activity_launch_expr=None, # informational row only, matches original behaviour
|
||||
),
|
||||
],
|
||||
enum_value_overrides=[
|
||||
EnumValueOverride(key="fontFamily", condition_expr="value == CrossPointSettings::BOOKERLY", text="Bookerly"),
|
||||
],
|
||||
web_option_appends=[
|
||||
WebOptionAppend(key="fontFamily", label="Bookerly"),
|
||||
],
|
||||
pre_patch=_pre_patch,
|
||||
post_patch=_post_patch,
|
||||
)
|
||||
Reference in new issue
Block a user