feat: implement G2 Supplementary character set and DRCS rendering support
This commit is contained in:
@@ -74,6 +74,26 @@ SETS = [
|
|||||||
ENGLISH, # 111 (Romania placeholder)
|
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):
|
def get_char(byte_val, subset_idx):
|
||||||
if subset_idx < 0 or subset_idx >= len(SETS):
|
if subset_idx < 0 or subset_idx >= len(SETS):
|
||||||
subset_idx = 0
|
subset_idx = 0
|
||||||
|
|||||||
@@ -198,6 +198,17 @@ class Page:
|
|||||||
pass
|
pass
|
||||||
return None
|
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
|
@dataclass
|
||||||
class TeletextService:
|
class TeletextService:
|
||||||
"""
|
"""
|
||||||
@@ -206,4 +217,6 @@ class TeletextService:
|
|||||||
pages: List[Page] = field(default_factory=list)
|
pages: List[Page] = field(default_factory=list)
|
||||||
# We also keep a flat list of all packets to preserve order on save
|
# We also keep a flat list of all packets to preserve order on save
|
||||||
all_packets: List[Packet] = field(default_factory=list)
|
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.setMouseTracking(True) # Just in case
|
||||||
self.setMinimumSize(800, 600) # 40x20 * 25x24
|
self.setMinimumSize(800, 600) # 40x20 * 25x24
|
||||||
self.page: Page = None
|
self.page: Page = None
|
||||||
|
self.service = None # Reference to TeletextService for DRCS/Shared data
|
||||||
self.subset_idx = 0 # Default English
|
self.subset_idx = 0 # Default English
|
||||||
|
|
||||||
# Teletext is 40 columns x 25 rows
|
# Teletext is 40 columns x 25 rows
|
||||||
@@ -236,6 +237,28 @@ class TeletextCanvas(QWidget):
|
|||||||
|
|
||||||
painter.end()
|
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):
|
def draw_row(self, painter, row, packet, draw_bg=True, draw_fg=True, occlusion_mask=None):
|
||||||
if occlusion_mask is None:
|
if occlusion_mask is None:
|
||||||
occlusion_mask = [False] * 40
|
occlusion_mask = [False] * 40
|
||||||
@@ -403,7 +426,22 @@ class TeletextCanvas(QWidget):
|
|||||||
# Draw space (nothing, since we filled BG)
|
# Draw space (nothing, since we filled BG)
|
||||||
pass
|
pass
|
||||||
else:
|
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
|
# Mosaic Graphics
|
||||||
h_mos = self.cell_h * 2 if double_height else self.cell_h
|
h_mos = self.cell_h * 2 if double_height else self.cell_h
|
||||||
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F):
|
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F):
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Canvas
|
# Canvas
|
||||||
self.canvas = TeletextCanvas()
|
self.canvas = TeletextCanvas()
|
||||||
|
self.canvas.service = self.service
|
||||||
self.canvas.cursorChanged.connect(self.on_cursor_changed)
|
self.canvas.cursorChanged.connect(self.on_cursor_changed)
|
||||||
middle_layout.addWidget(self.canvas, 1) # Expand
|
middle_layout.addWidget(self.canvas, 1) # Expand
|
||||||
|
|
||||||
@@ -628,6 +629,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
|
|
||||||
self.service = load_t42(fname, progress_callback=self.update_progress)
|
self.service = load_t42(fname, progress_callback=self.update_progress)
|
||||||
|
self.canvas.service = self.service
|
||||||
self.current_file_path = fname
|
self.current_file_path = fname
|
||||||
self.populate_list()
|
self.populate_list()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user