diff --git a/src/teletext/charsets.py b/src/teletext/charsets.py index 7455290..3a2cb9b 100644 --- a/src/teletext/charsets.py +++ b/src/teletext/charsets.py @@ -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 diff --git a/src/teletext/models.py b/src/teletext/models.py index 8ea7c64..8a5719c 100644 --- a/src/teletext/models.py +++ b/src/teletext/models.py @@ -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) diff --git a/src/teletext/renderer.py b/src/teletext/renderer.py index a6219d3..a173f8a 100755 --- a/src/teletext/renderer.py +++ b/src/teletext/renderer.py @@ -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): diff --git a/src/teletext/ui.py b/src/teletext/ui.py index 4b633eb..6f82ee0 100644 --- a/src/teletext/ui.py +++ b/src/teletext/ui.py @@ -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()