Pick Page Editor fonts and images from the platform's banks

The font and image rows are now dropdowns filled from <sedona home>/ontrol:
fontBank/imageBank on Rion, fontBankXL/imageBankXL on Orion. Orion lists
.oxfl fonts in landscape and .oxfp fonts in portrait, read from the
platform service's screenOrientation (loaded from the device in live mode
when the tree has not reached it). Images show their file extension while
the slot keeps the bare name. The font dialog is gone.

In portrait the Page Editor draws a 480x800 screen in
frame_orion_portrait.png, and the canvas renders text with the .oxfp
fonts, whose glyphs are stored a quarter turn anticlockwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 21:15:43 +03:00
co-authored by Claude Opus 5.5
parent 138d9927be
commit c01a192365
5 changed files with 228 additions and 84 deletions
+18 -17
View File
@@ -18,7 +18,6 @@ Everything is drawn in canvas units: device pixels times the zoom.
"""
import datetime
import glob
import os
import tkinter as tk
from tkinter import font as tkfont
@@ -33,7 +32,8 @@ import app_state
import properties
from ox_widget_properties import (_Model, BACKGROUND_BIT, ALIGN_MASK, SUBSCRIPT_MASK,
DATE_HIDE_YEAR, DATE_TWO_DIGIT_YEAR, DATE_LEADING_ZERO,
DATE_MONTH_FIRST, IMAGE_EXTENSIONS, font_names,
DATE_MONTH_FIRST, IMAGE_EXTENSIONS, PORTRAIT_FONT_EXTENSION,
bank_folders, font_extensions, font_names,
parse_lookup, parse_numeric_format, rgb565_to_hex)
PLACEHOLDER_FILL = "#dce6f2"
@@ -71,6 +71,10 @@ def _font(family, pixels, style):
class BitmapFont:
"""A device font file: fontBank/'32pt Tahoma.bin', or fontBankXL/'.oxfl' (same layout).
Orion's portrait cut, fontBankXL/'.oxfp', has the same header and table but stores
each glyph turned a quarter turn anticlockwise: `height` pixels to a row, `width`
rows. glyph_image turns it back, so every caller sees the upright glyph.
0..63 header; u16 character count at 32, line height at 34
64.. 10 bytes per character: code (u8, or u16 above 191), data offset (u24
at 2), width, height, x offset (signed), y offset, x advance
@@ -80,6 +84,7 @@ class BitmapFont:
def __init__(self, path):
data = open(path, "rb").read()
self.data = data
self.rotated = path.lower().endswith(PORTRAIT_FONT_EXTENSION)
self.height = data[34]
count = data[32] | data[33] << 8
self.glyphs = {}
@@ -100,7 +105,8 @@ class BitmapFont:
def glyph_image(self, code):
offset, width, height, _, _, _ = self.glyphs[code]
img = Image.new("L", (max(1, width), max(1, height)))
stored = (height, width) if self.rotated else (width, height)
img = Image.new("L", (max(1, stored[0]), max(1, stored[1])))
if width and height:
raw = self.data[offset:offset + (width * height + 1) // 2]
pixels = bytearray(width * height)
@@ -108,7 +114,7 @@ class BitmapFont:
byte = raw[i >> 1] if (i >> 1) < len(raw) else 0
pixels[i] = ((byte >> 4) if i % 2 == 0 else (byte & 0x0F)) * 17
img.putdata(pixels)
return img
return img.transpose(Image.ROTATE_270) if self.rotated else img
_bitmap_fonts = {}
@@ -119,18 +125,16 @@ def find_bitmap_font(family, size, style):
get_Font_File_Name's naming: '<size>pt <Name>', prefixed 'bold ' for bold. There is
no italic cut, so italic falls back to the regular one as the device's lookup would
find nothing better.
find nothing better. The file comes from the platform's own bank, and on Orion in
the cut its screen orientation draws with.
"""
home = getattr(app_state, "SEDONA_HOME", None)
if not home or not _HAS_PIL:
if not _HAS_PIL:
return None
stem = f"{'bold ' if style == 1 else ''}{size}pt {family}"
candidates = [os.path.join(home, "ontrol", "fontBank", stem + ".bin"),
os.path.join(home, "ontrol", "fontBankXL", stem + ".oxfl")]
stems = [f"{'bold ' if style == 1 else ''}{size}pt {family}"]
if style == 1:
plain = f"{size}pt {family}"
candidates += [os.path.join(home, "ontrol", "fontBank", plain + ".bin"),
os.path.join(home, "ontrol", "fontBankXL", plain + ".oxfl")]
stems.append(f"{size}pt {family}")
candidates = [os.path.join(folder, stem + ext) for stem in stems
for folder in bank_folders("font") for ext in font_extensions()]
for path in candidates:
if path in _bitmap_fonts:
return _bitmap_fonts[path]
@@ -179,10 +183,7 @@ def find_image_file(name):
"""The local file for a device image name, looked for in Sedona home's image banks."""
if not name:
return None
home = getattr(app_state, "SEDONA_HOME", None)
if not home:
return None
for folder in sorted(glob.glob(os.path.join(home, "ontrol", "imageBank*"))):
for folder in bank_folders("image"):
for ext in IMAGE_EXTENSIONS:
path = os.path.join(folder, name + ext)
if os.path.isfile(path):