feat: implement G2 Supplementary character set and DRCS rendering support
All checks were successful
Build Linux / Build Linux (push) Successful in 1m30s
Build Windows / Build Windows (push) Successful in 4m52s

This commit is contained in:
2026-02-21 12:29:46 +01:00
parent fe4253c8df
commit 6d88b59e04
4 changed files with 74 additions and 1 deletions

View File

@@ -74,6 +74,26 @@ SETS = [
ENGLISH, # 111 (Romania placeholder)
]
# G2 Supplementary Set - ETSI EN 300 706, Section 15.2
G2_SUPPLEMENTARY = {
0x20: ' ', 0x21: '¡', 0x22: '¢', 0x23: '£', 0x24: '$', 0x25: '¥', 0x26: '#', 0x27: '§',
0x28: '¤', 0x29: '', 0x2A: '', 0x2B: '«', 0x2C: '', 0x2D: '', 0x2E: '', 0x2F: '',
0x30: '°', 0x31: '±', 0x32: '²', 0x33: '³', 0x34: '×', 0x35: 'µ', 0x36: '', 0x37: '·',
0x38: '÷', 0x39: '', 0x3A: '', 0x3B: '»', 0x3C: '¼', 0x3D: '½', 0x3E: '¾', 0x3F: '¿',
0x41: 'À', 0x42: 'Á', 0x43: 'Â', 0x44: 'Ã', 0x45: 'Ä', 0x46: 'Å', 0x47: 'Æ', 0x48: 'Ç',
0x49: 'È', 0x4A: 'É', 0x4B: 'Ê', 0x4C: 'Ë', 0x4D: 'Ì', 0x4E: 'Í', 0x4F: 'Î', 0x50: 'Ï',
0x51: 'Ð', 0x52: 'Ñ', 0x53: 'Ò', 0x54: 'Ó', 0x55: 'Ô', 0x56: 'Õ', 0x57: 'Ö', 0x58: 'Œ',
0x59: 'Ø', 0x5A: 'Ù', 0x5B: 'Ú', 0x5C: 'Û', 0x5D: 'Ü', 0x5E: 'Ý', 0x5F: 'Þ', 0x60: 'ª',
0x61: 'à', 0x62: 'á', 0x63: 'â', 0x64: 'ã', 0x65: 'ä', 0x66: 'å', 0x67: 'æ', 0x68: 'ç',
0x69: 'è', 0x6A: 'é', 0x6B: 'ê', 0x6C: 'ë', 0x6D: 'ì', 0x6E: 'í', 0x6F: 'î', 0x70: 'ï',
0x71: 'ð', 0x72: 'ñ', 0x73: 'ò', 0x74: 'ó', 0x75: 'ô', 0x76: 'õ', 0x77: 'ö', 0x78: 'œ',
0x79: 'ø', 0x7A: 'ù', 0x7B: 'ú', 0x7C: 'û', 0x7D: 'ü', 0x7E: 'ý', 0x7F: 'þ',
}
def get_char_g2(byte_val):
valid_byte = byte_val & 0x7F
return G2_SUPPLEMENTARY.get(valid_byte, ' ')
def get_char(byte_val, subset_idx):
if subset_idx < 0 or subset_idx >= len(SETS):
subset_idx = 0

View File

@@ -198,6 +198,17 @@ class Page:
pass
return None
@dataclass
class DRCSCharacter:
"""
Represents a Dynamically Redefinable Character.
Typically 12x10 pixels (Level 3.5) or 12x20 (Level 2.5).
Stored as a flat list of bits or bytes.
"""
width: int
height: int
pixels: bytearray # 0 for bg, 1 for fg
@dataclass
class TeletextService:
"""
@@ -206,4 +217,6 @@ class TeletextService:
pages: List[Page] = field(default_factory=list)
# We also keep a flat list of all packets to preserve order on save
all_packets: List[Packet] = field(default_factory=list)
# DRCS storage: (Set ID, Char Code) -> DRCSCharacter
drcs_data: dict = field(default_factory=dict)

View File

@@ -60,6 +60,7 @@ class TeletextCanvas(QWidget):
self.setMouseTracking(True) # Just in case
self.setMinimumSize(800, 600) # 40x20 * 25x24
self.page: Page = None
self.service = None # Reference to TeletextService for DRCS/Shared data
self.subset_idx = 0 # Default English
# Teletext is 40 columns x 25 rows
@@ -236,6 +237,28 @@ class TeletextCanvas(QWidget):
painter.end()
def draw_drcs(self, painter, x, y, drcs_char, color, double_height=False):
"""
Draws a DRCS character bitmap.
"""
h_target = self.cell_h * 2 if double_height else self.cell_h
w_target = self.cell_w
# We need to scale the drcs_char.pixels (width x height) to (cell_w x cell_h)
# Simplest: fillRect for each 'pixel' in the source bitmap
pw = w_target / drcs_char.width
ph = h_target / drcs_char.height
painter.setBrush(QBrush(color))
painter.setPen(Qt.PenStyle.NoPen)
for py in range(drcs_char.height):
for px in range(drcs_char.width):
if drcs_char.pixels[py * drcs_char.width + px]:
rx = x + (px * pw)
ry = y + (py * ph)
painter.drawRect(QRect(int(rx), int(ry), int(pw + 0.9), int(ph + 0.9)))
def draw_row(self, painter, row, packet, draw_bg=True, draw_fg=True, occlusion_mask=None):
if occlusion_mask is None:
occlusion_mask = [False] * 40
@@ -403,7 +426,22 @@ class TeletextCanvas(QWidget):
# Draw space (nothing, since we filled BG)
pass
else:
if graphics_mode:
# Check for DRCS
drcs_char = None
if self.page and hasattr(self, 'parent') and hasattr(self.parent(), 'service'):
# Try to find drcs in service
service = self.parent().service
# For now, assume Set 0 if we have drcs_data for this code
drcs_char = service.drcs_data.get((0, byte_val))
# Alternatively, if we just want it to work in the test where we might not have parent()
# We can store a reference to the service in the canvas
if not drcs_char and hasattr(self, 'service') and self.service:
drcs_char = self.service.drcs_data.get((0, byte_val))
if drcs_char:
self.draw_drcs(painter, x, y, drcs_char, fg, double_height)
elif graphics_mode:
# Mosaic Graphics
h_mos = self.cell_h * 2 if double_height else self.cell_h
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F):

View File

@@ -193,6 +193,7 @@ class MainWindow(QMainWindow):
# Canvas
self.canvas = TeletextCanvas()
self.canvas.service = self.service
self.canvas.cursorChanged.connect(self.on_cursor_changed)
middle_layout.addWidget(self.canvas, 1) # Expand
@@ -628,6 +629,7 @@ class MainWindow(QMainWindow):
self.progress_bar.setValue(0)
self.service = load_t42(fname, progress_callback=self.update_progress)
self.canvas.service = self.service
self.current_file_path = fname
self.populate_list()