feat: implement G2 Supplementary character set and DRCS rendering support
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user