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:
+1
-1
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
|
||||
import configparser
|
||||
import os
|
||||
|
||||
VERSION = "0.0.0.131"
|
||||
VERSION = "0.0.0.132"
|
||||
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||
DEFAULT_GRID_SIZE = 10
|
||||
DEFAULT_ADD_TIMESTAMP = True
|
||||
|
||||
@@ -175,6 +175,33 @@ class DeviceSession:
|
||||
for cid in parent.children
|
||||
if self.sox_client.cache_get(cid) is not None]
|
||||
|
||||
def find_component_of_type(self, type_name, slot_name):
|
||||
"""A component of `type_name` with `slot_name` read, loading where services live.
|
||||
|
||||
Nothing is loaded until a folder is opened, so a platform service the user never
|
||||
expanded to is not cached yet. It sits one or two levels down (/service/plat), and
|
||||
those are loaded to find it rather than the whole app.
|
||||
"""
|
||||
client = self.sox_client
|
||||
if client is None:
|
||||
return None
|
||||
|
||||
def cached():
|
||||
return next((c for c in client.cache
|
||||
if c is not None and c.type_name == type_name), None)
|
||||
|
||||
comp = cached()
|
||||
if comp is None:
|
||||
for top in self.load_children(0):
|
||||
if top.name == "service" or top.type_name == type_name:
|
||||
for child in self.load_children(top.id):
|
||||
if child.children and child.type_name != type_name:
|
||||
self.load_children(child.id)
|
||||
comp = cached()
|
||||
if comp is not None and slot_name not in comp.values:
|
||||
client.update([comp], CONFIG)
|
||||
return comp
|
||||
|
||||
# ── Wiresheet Open / Close ──────────────────────────────────
|
||||
|
||||
def open_wiresheet(self, folder_comp_id):
|
||||
|
||||
+11
-2
@@ -5,7 +5,8 @@ widgets (ontrolOx::Widget subclasses) by their x, y, width, height slots.
|
||||
Supports selection, drag-move, and a synced widget tree.
|
||||
|
||||
Device resolution is detected from the schema kits:
|
||||
ontrolOrion → 800×480
|
||||
ontrolOrion → 800×480, or 480×800 when its platform service's screenOrientation
|
||||
is portrait (drawn in frame_orion_portrait.png)
|
||||
ontrolRion → 320×240
|
||||
|
||||
A device frame image (the physical bezel) sits behind the screen area when the
|
||||
@@ -25,7 +26,7 @@ except ImportError:
|
||||
import app_state
|
||||
import live_hooks
|
||||
import undo
|
||||
from ox_widget_properties import OxWidgetPropertiesPanel
|
||||
from ox_widget_properties import ORION_KIT, OxWidgetPropertiesPanel, orion_portrait
|
||||
from ox_widget_render import WidgetPainter
|
||||
from sax_dom import set_prop_value
|
||||
|
||||
@@ -47,6 +48,12 @@ DEVICE_PROFILES = {
|
||||
"default_zoom": 1.0,
|
||||
},
|
||||
}
|
||||
ORION_PORTRAIT_PROFILE = {
|
||||
"screen": (480, 800),
|
||||
"frame_file": "frame_orion_portrait.png",
|
||||
"screen_offset": (190, 170),
|
||||
"default_zoom": 0.5,
|
||||
}
|
||||
DEFAULT_SCREEN = (800, 480)
|
||||
DEFAULT_ZOOM = 0.5
|
||||
|
||||
@@ -105,6 +112,8 @@ def _short_type(full_type):
|
||||
def _detect_device_profile():
|
||||
for kit_name, profile in DEVICE_PROFILES.items():
|
||||
if kit_name in app_state.active_schema_kits:
|
||||
if kit_name == ORION_KIT and orion_portrait():
|
||||
return ORION_PORTRAIT_PROFILE
|
||||
return profile
|
||||
return None
|
||||
|
||||
|
||||
+171
-64
@@ -4,7 +4,7 @@ The Sedona slot sheet lists a widget's raw slots; this one lists what a screen d
|
||||
thinks in. Several slots fold into one property and one slot splits into several:
|
||||
|
||||
layout x, y, width, height "0, 20, 320, 40"
|
||||
font fontName, fontSize, "32pt Tahoma", "Bold 16pt Lato"
|
||||
font fontName, fontSize, "32pt Tahoma", "bold 16pt Lato"
|
||||
fontAttributes
|
||||
alignment attributes bits 4-5 Left / Center / Right
|
||||
transparent attributes bit 3 (inverted) bit 3 set means "draw background"
|
||||
@@ -237,24 +237,124 @@ def lookup_preview(text):
|
||||
return f"{prefix}[{' | '.join(items)}]{suffix}"
|
||||
|
||||
|
||||
# --- images ---------------------------------------------------------------------------
|
||||
# --- the device's font and image banks ------------------------------------------------
|
||||
|
||||
# Orion's 800x480 screen takes the XL banks; Rion's 320x240 one the plain ones.
|
||||
ORION_KIT = "ontrolOrion"
|
||||
RION_KIT = "ontrolRion"
|
||||
FONT_EXTENSIONS = (".bin", ".oxfl", ".oxfp")
|
||||
# Orion keeps a landscape (.oxfl) and a portrait (.oxfp) cut of every font, and draws
|
||||
# with the one its platform service's screenOrientation (false = landscape) picks.
|
||||
ORION_PLATFORM_TYPE = "ontrolOrion::OrionPlatformService"
|
||||
LANDSCAPE_FONT_EXTENSION = ".oxfl"
|
||||
PORTRAIT_FONT_EXTENSION = ".oxfp"
|
||||
_FONT_STEM = re.compile(r"^(bold\s+)?(\d+)pt\s+(.+)$", re.IGNORECASE)
|
||||
|
||||
|
||||
def bank_folders(kind):
|
||||
"""The <sedona home>/ontrol folders holding the open platform's `kind` bank.
|
||||
|
||||
`kind` is "font" or "image". Orion reads fontBankXL/imageBankXL and Rion
|
||||
fontBank/imageBank; a document of neither platform is offered both.
|
||||
"""
|
||||
home = getattr(app_state, "SEDONA_HOME", None)
|
||||
if not home:
|
||||
return []
|
||||
kits = app_state.active_schema_kits
|
||||
if ORION_KIT in kits:
|
||||
names = (f"{kind}BankXL",)
|
||||
elif RION_KIT in kits:
|
||||
names = (f"{kind}Bank",)
|
||||
else:
|
||||
names = (f"{kind}Bank", f"{kind}BankXL")
|
||||
return [os.path.join(home, "ontrol", name) for name in names]
|
||||
|
||||
|
||||
def _bank_files(kind, extensions):
|
||||
"""The bank's file names, extension included, of the kinds in `extensions`."""
|
||||
files = set()
|
||||
for folder in bank_folders(kind):
|
||||
for path in glob.glob(os.path.join(folder, "*")):
|
||||
if os.path.splitext(path)[1].lower() in extensions and os.path.isfile(path):
|
||||
files.add(os.path.basename(path))
|
||||
return files
|
||||
|
||||
|
||||
def _bank_stems(kind, extensions):
|
||||
return {os.path.splitext(name)[0] for name in _bank_files(kind, extensions)}
|
||||
|
||||
|
||||
def image_names():
|
||||
"""Image names worth offering: the local image banks' files and the document's own."""
|
||||
names = set()
|
||||
home = getattr(app_state, "SEDONA_HOME", None)
|
||||
if home:
|
||||
for folder in glob.glob(os.path.join(home, "ontrol", "imageBank*")):
|
||||
for path in glob.glob(os.path.join(folder, "*")):
|
||||
stem, ext = os.path.splitext(os.path.basename(path))
|
||||
if ext.lower() in IMAGE_EXTENSIONS:
|
||||
names.add(stem)
|
||||
"""The image bank's files as the list shows them: 'trt.jpg', 'logo.png'."""
|
||||
return sorted(_bank_files("image", IMAGE_EXTENSIONS), key=str.lower)
|
||||
|
||||
|
||||
def image_file_text(name):
|
||||
"""An imageFileName value with its bank file's extension: 'trt' -> 'trt.jpg'.
|
||||
|
||||
A name no bank file matches is shown as it is.
|
||||
"""
|
||||
if name:
|
||||
for file_name in image_names():
|
||||
if os.path.splitext(file_name)[0] == name:
|
||||
return file_name
|
||||
return name
|
||||
|
||||
|
||||
def image_file_value(text):
|
||||
"""The imageFileName value for a listed file: 'trt.jpg' -> 'trt'.
|
||||
|
||||
The slot holds the bare name, as the device looks the file up by it.
|
||||
"""
|
||||
stem, ext = os.path.splitext(text.strip())
|
||||
return stem if ext.lower() in IMAGE_EXTENSIONS else text.strip()
|
||||
|
||||
|
||||
def parse_font_stem(stem):
|
||||
"""'bold 12pt Tahoma' -> ('Tahoma', 12, True); None for a name not in that form."""
|
||||
match = _FONT_STEM.match(stem.strip())
|
||||
if not match:
|
||||
return None
|
||||
return match.group(3).strip(), int(match.group(2)), bool(match.group(1))
|
||||
|
||||
|
||||
def orion_portrait():
|
||||
"""True when the Orion platform service is set to a portrait screen.
|
||||
|
||||
In live mode the tree holds only the folders opened so far, so a platform service
|
||||
the document lacks is read from the device instead.
|
||||
"""
|
||||
root = app_state.xml_root_element
|
||||
if root is not None:
|
||||
for prop in root.iter("prop"):
|
||||
if "imagefilename" in (prop.get("name") or "").lower() and prop.get("val"):
|
||||
names.add(prop.get("val"))
|
||||
return sorted(names, key=str.lower)
|
||||
for comp in root.iter("comp") if root is not None else ():
|
||||
if comp.get("type") == ORION_PLATFORM_TYPE:
|
||||
slot = next((s for s in schema.get_full_slots_for_type(ORION_PLATFORM_TYPE)
|
||||
if s["name"] == "screenOrientation"), None)
|
||||
raw = properties.effective_value(comp, slot) if slot else None
|
||||
return str(raw).lower() == "true"
|
||||
session = getattr(app_state, "device_session", None)
|
||||
if app_state.live_mode and session is not None:
|
||||
try:
|
||||
comp = session.find_component_of_type(ORION_PLATFORM_TYPE, "screenOrientation")
|
||||
except Exception: # a dropped connection reads as the default, landscape
|
||||
comp = None
|
||||
if comp is not None:
|
||||
return str(comp.values.get("screenOrientation")).lower() == "true"
|
||||
return False
|
||||
|
||||
|
||||
def font_extensions():
|
||||
"""The font file extensions the open platform draws with."""
|
||||
if ORION_KIT in app_state.active_schema_kits:
|
||||
return (PORTRAIT_FONT_EXTENSION if orion_portrait() else LANDSCAPE_FONT_EXTENSION,)
|
||||
return FONT_EXTENSIONS
|
||||
|
||||
|
||||
def font_bank_names():
|
||||
"""The font bank's fonts, smallest first and plain before bold at each size."""
|
||||
fonts = [(parse_font_stem(stem), stem) for stem in _bank_stems("font", font_extensions())]
|
||||
fonts = [(parsed, stem) for parsed, stem in fonts if parsed]
|
||||
fonts.sort(key=lambda f: (f[0][1], f[0][2], f[0][0].lower()))
|
||||
return [stem for _, stem in fonts]
|
||||
|
||||
|
||||
# --- links ----------------------------------------------------------------------------
|
||||
@@ -288,7 +388,7 @@ class Prop:
|
||||
def __init__(self, label, kind, read, write=None, choices=(), dialog=None, unit="",
|
||||
slot=None, output=False):
|
||||
self.label = label
|
||||
self.kind = kind # text | choice | colour | dialog | image | link | readonly
|
||||
self.kind = kind # text | choice | colour | dialog | image | font | link | readonly
|
||||
self.read = read
|
||||
self.write = write
|
||||
self.choices = choices
|
||||
@@ -360,7 +460,7 @@ def build_props(comp):
|
||||
|
||||
if m.has(*FONT_SLOTS):
|
||||
consumed.update(FONT_SLOTS)
|
||||
rows.append(Prop("font", "dialog", lambda: font_text(m), dialog="font"))
|
||||
rows.append(Prop("font", "font", lambda: font_text(m), _font_writer(m)))
|
||||
|
||||
if m.has("attributes"):
|
||||
consumed.add("attributes")
|
||||
@@ -449,14 +549,35 @@ def font_styles(m):
|
||||
|
||||
|
||||
def font_text(m):
|
||||
"""The font as the font bank names its file: '32pt Tahoma', 'bold 16pt Lato'."""
|
||||
names, styles = font_names(m), font_styles(m)
|
||||
name_i, style_i = m.int("fontName"), m.int("fontAttributes")
|
||||
name = names[name_i] if 0 <= name_i < len(names) else f"font {name_i}"
|
||||
style = styles[style_i] if 0 <= style_i < len(styles) else ""
|
||||
prefix = "" if style_i == 0 or not style else f"{style} "
|
||||
prefix = "" if style_i == 0 or not style else f"{style.lower()} "
|
||||
return f"{prefix}{m.int('fontSize')}pt {name}"
|
||||
|
||||
|
||||
def _font_writer(m):
|
||||
"""A font bank name back into fontName, fontSize and fontAttributes."""
|
||||
def write(text):
|
||||
parsed = parse_font_stem(text)
|
||||
if parsed is None:
|
||||
return None, "expected a font such as '12pt Tahoma' or 'bold 12pt Tahoma'"
|
||||
family, size, bold = parsed
|
||||
names = [n.lower() for n in font_names(m)]
|
||||
if family.lower() not in names:
|
||||
return None, f"'{family}' is not one of: {', '.join(font_names(m))}"
|
||||
size_raw, error = m.check("fontSize", str(size))
|
||||
if error:
|
||||
return None, f"size: {error}"
|
||||
styles = [s.lower() for s in font_styles(m)]
|
||||
style = styles.index("bold") if bold and "bold" in styles else (1 if bold else 0)
|
||||
return {"fontName": str(names.index(family.lower())), "fontSize": size_raw,
|
||||
"fontAttributes": str(style)}, None
|
||||
return write
|
||||
|
||||
|
||||
def _bit_props(m, name, fallback, specs):
|
||||
"""Rows that each own some bits of one byte slot: (label, mask, choices, values)."""
|
||||
def current():
|
||||
@@ -539,8 +660,8 @@ def _slot_prop(m, slot):
|
||||
lambda text: ({name: text}, None))
|
||||
|
||||
if _is_image_slot(slot):
|
||||
return Prop(name, "image", lambda: m.text(name),
|
||||
lambda text: m.text_values(name, text.strip()))
|
||||
return Prop(name, "image", lambda: image_file_text(m.text(name)),
|
||||
lambda text: m.text_values(name, image_file_value(text)))
|
||||
|
||||
if slot["type"] == "sys::Buf":
|
||||
return Prop(name, "text", lambda: m.text(name), lambda text: m.text_values(name, text))
|
||||
@@ -779,13 +900,36 @@ class OxWidgetPropertiesPanel:
|
||||
self._bind_commit(entry, row)
|
||||
self._controls.append((entry, None))
|
||||
|
||||
def _bank_combo(self, cell, row, bank_values):
|
||||
"""A pick-only list filled from a bank each time it opens, the value kept listed."""
|
||||
var = row["var"]
|
||||
|
||||
def fill():
|
||||
values = bank_values()
|
||||
current = var.get()
|
||||
if current and current not in values:
|
||||
values = [current] + values
|
||||
combo.configure(values=values)
|
||||
|
||||
combo = ttk.Combobox(cell, textvariable=var, state="readonly", font=self._name_font,
|
||||
postcommand=fill)
|
||||
combo.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=1)
|
||||
|
||||
def selected(_event=None):
|
||||
combo.selection_clear()
|
||||
if var.get() != row["prop"].read():
|
||||
self._write(row, var.get())
|
||||
|
||||
combo.bind("<<ComboboxSelected>>", selected)
|
||||
row["load"] = lambda: var.set(row["prop"].read())
|
||||
self._controls.append((combo, "readonly"))
|
||||
return combo
|
||||
|
||||
def _build_image(self, cell, row):
|
||||
combo = ttk.Combobox(cell, textvariable=row["var"], font=self._name_font,
|
||||
postcommand=lambda: combo.configure(values=image_names()))
|
||||
combo.pack(fill=tk.BOTH, expand=True, padx=1)
|
||||
commit = self._bind_commit(combo, row)
|
||||
combo.bind("<<ComboboxSelected>>", commit)
|
||||
self._controls.append((combo, "normal"))
|
||||
self._bank_combo(cell, row, image_names)
|
||||
|
||||
def _build_font(self, cell, row):
|
||||
self._bank_combo(cell, row, font_bank_names)
|
||||
|
||||
def _build_choice(self, cell, row):
|
||||
prop, var = row["prop"], row["var"]
|
||||
@@ -938,43 +1082,6 @@ class OxWidgetPropertiesPanel:
|
||||
entries[0].focus_set()
|
||||
entries[0].select_range(0, tk.END)
|
||||
|
||||
def _open_font_dialog(self):
|
||||
comp = self.comp
|
||||
if comp is None:
|
||||
return
|
||||
m = _Model(comp)
|
||||
names, styles = font_names(m), font_styles(m)
|
||||
dialog, body = self._dialog("Font")
|
||||
|
||||
def index_text(choices, index):
|
||||
return choices[index] if 0 <= index < len(choices) else choices[0]
|
||||
|
||||
name_var = tk.StringVar(value=index_text(names, m.int("fontName")))
|
||||
size_var = tk.StringVar(value=str(m.int("fontSize")))
|
||||
style_var = tk.StringVar(value=index_text(styles, m.int("fontAttributes")))
|
||||
|
||||
ttk.Label(body, text="Name").grid(row=0, column=0, sticky="w", pady=2)
|
||||
ttk.Combobox(body, textvariable=name_var, values=list(names), state="readonly",
|
||||
width=14).grid(row=0, column=1, sticky="ew", pady=2)
|
||||
ttk.Label(body, text="Size (pt)").grid(row=1, column=0, sticky="w", pady=2)
|
||||
ttk.Spinbox(body, textvariable=size_var, from_=1, to=255, width=14).grid(
|
||||
row=1, column=1, sticky="ew", pady=2)
|
||||
ttk.Label(body, text="Style").grid(row=2, column=0, sticky="w", pady=2)
|
||||
ttk.Combobox(body, textvariable=style_var, values=list(styles), state="readonly",
|
||||
width=14).grid(row=2, column=1, sticky="ew", pady=2)
|
||||
|
||||
def apply():
|
||||
size_raw, error = m.check("fontSize", size_var.get())
|
||||
values = None if error else {
|
||||
"fontName": str(names.index(name_var.get())),
|
||||
"fontSize": size_raw,
|
||||
"fontAttributes": str(styles.index(style_var.get())),
|
||||
}
|
||||
self._apply_values(dialog, comp, values, error and f"Size: {error}")
|
||||
|
||||
dialog.bind("<Return>", lambda e: apply())
|
||||
self._finish_dialog(dialog, body, 3, apply)
|
||||
|
||||
def _open_numeric_dialog(self):
|
||||
"""NumericLabel's text: the caption around the value and how it is formatted."""
|
||||
comp = self.comp
|
||||
|
||||
+18
-17
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user