diff --git a/src/teletext/renderer.py b/src/teletext/renderer.py index f153b17..e179889 100755 --- a/src/teletext/renderer.py +++ b/src/teletext/renderer.py @@ -215,13 +215,27 @@ class TeletextCanvas(QWidget): if 0 <= p.row <= 25: grid[p.row] = p + # Pass 1: Backgrounds + occlusion_mask = [False] * 40 for r in range(25): packet = grid[r] - self.draw_row(painter, r, packet) + occlusion_mask = self.draw_row(painter, r, packet, draw_bg=True, draw_fg=False, occlusion_mask=occlusion_mask) + + # Pass 2: Foregrounds + occlusion_mask = [False] * 40 + for r in range(25): + packet = grid[r] + occlusion_mask = self.draw_row(painter, r, packet, draw_bg=False, draw_fg=True, occlusion_mask=occlusion_mask) painter.end() - def draw_row(self, painter, row, packet): + 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 + + # Output mask for the next row + next_occlusion_mask = [False] * 40 + # Default State at start of row fg = COLORS[7] # White bg = COLORS[0] # Black @@ -229,6 +243,7 @@ class TeletextCanvas(QWidget): contiguous = True # Mosaic hold_graphics = False held_char = 0x20 # Space + double_height = False y = row * self.cell_h @@ -248,6 +263,24 @@ class TeletextCanvas(QWidget): for c in range(40): x = c * self.cell_w + # If this cell is occluded by the row above, skip drawing and attribute processing? + # Spec says "The characters in the row below are ignored." + # Ideally we shouldn't even process attributes, but for simple renderer we just skip draw. + # However, if we skip attribute processing, state (fg/bg) won't update. + # Teletext attributes are serial. + # BUT, if the row above covers it, the viewer sees the row above. + # Does the hidden content affect the *rest* of the row? + # Likely yes, attributes usually propagate. + # But the spec says "ignored". Let's assume we skip *everything* for this cell visually, + # but maybe we should technically maintain state? + # For "Double Height" visual correctness, skipping drawing is the key. + # We will Process attributes (to keep state consistent) but Skip Drawing if occluded. + + # Wait, if we process attributes, we might set double_height=True for the NEXT row? + # If this cell is occluded, it shouldn't trigger DH for the next row. + + is_occluded = occlusion_mask[c] + # Decide byte value if row == 0 and c < 8: # Use generated header prefix @@ -274,9 +307,9 @@ class TeletextCanvas(QWidget): elif byte_val == 0x1D: # New BG bg = fg elif byte_val == 0x0C: # Normal Height - pass + double_height = False elif byte_val == 0x0D: # Double Height - pass # Not implemented yet + double_height = True elif byte_val == 0x19: # Contiguous Graphics contiguous = True elif byte_val == 0x1A: # Separated Graphics @@ -286,43 +319,79 @@ class TeletextCanvas(QWidget): elif byte_val == 0x1F: # Release Graphics hold_graphics = False + # Record Double Height for next row + if double_height and not is_occluded: + next_occlusion_mask[c] = True + + # If occluded, do not draw anything for this cell + if is_occluded: + continue + # Draw Background - painter.fillRect(x, y, self.cell_w, self.cell_h, bg) + if draw_bg: + # If double height, draw taller background + h_bg = self.cell_h * 2 if double_height else self.cell_h + painter.fillRect(x, y, self.cell_w, h_bg, bg) # Draw Foreground - if is_control: - # "Set-at" spacing attribute? Teletext control codes occupy a space - # unless "Hold Graphics" replaces it with previous graphic char. - if hold_graphics and graphics_mode: - self.draw_mosaic(painter, x, y, held_char, fg, contiguous) - else: - # Draw space (nothing, since we filled BG) - pass - else: - if graphics_mode: - # Mosaic Graphics - if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F): - self.draw_mosaic(painter, x, y, byte_val, fg, contiguous) - held_char = byte_val + if draw_fg: + # Calculate height + # For Mosaics, we use the height param. + # For Alphanumerics, we scale the painter. + + if is_control: + # "Set-at" spacing attribute? Teletext control codes occupy a space + # unless "Hold Graphics" replaces it with previous graphic char. + if hold_graphics and graphics_mode: + if double_height: + self.draw_mosaic(painter, x, y, held_char, fg, contiguous, height=self.cell_h * 2) + else: + self.draw_mosaic(painter, x, y, held_char, fg, contiguous) else: - # Capital letter in graphics mode? Usually shows char? + # Draw space (nothing, since we filled BG) + pass + else: + if 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): + self.draw_mosaic(painter, x, y, byte_val, fg, contiguous, height=h_mos) + held_char = byte_val + else: + # Capital letter in graphics mode? Usually shows char? + char = get_char(byte_val, self.subset_idx) + painter.setPen(fg) + if double_height: + painter.save() + painter.translate(x, y) + painter.scale(1, 2) + painter.drawText(QRect(0, 0, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) + painter.restore() + else: + painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) + held_char = 0x20 + else: + # Alphanumeric char = get_char(byte_val, self.subset_idx) painter.setPen(fg) - painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) - held_char = 0x20 - else: - # Alphanumeric - char = get_char(byte_val, self.subset_idx) - painter.setPen(fg) - painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) + if double_height: + painter.save() + painter.translate(x, y) + painter.scale(1, 2) + painter.drawText(QRect(0, 0, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) + painter.restore() + else: + painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) # Draw Cursor # Invert the cell at cursor position - if self.cursor_visible and c == self.cursor_x and row == self.cursor_y: + if draw_fg and self.cursor_visible and c == self.cursor_x and row == self.cursor_y: painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Difference) # Difference with white creates inversion + # Note: Cursor follows double height? Probably just the active cell. painter.fillRect(x, y, self.cell_w, self.cell_h, QColor(255, 255, 255)) painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver) - + + return next_occlusion_mask def mousePressEvent(self, event): self.setFocus() # Calculate cell from mouse position @@ -360,7 +429,10 @@ class TeletextCanvas(QWidget): row = int(my / (self.cell_h * scale)) self.set_cursor(col, row) - def draw_mosaic(self, painter, x, y, char_code, color, contiguous): + def draw_mosaic(self, painter, x, y, char_code, color, contiguous, height=None): + if height is None: + height = self.cell_h + val = char_code & 0x7F bits = 0 if val >= 0x20: @@ -368,7 +440,7 @@ class TeletextCanvas(QWidget): # Grid definitions for 2x3 grid x_splits = [0, int(self.cell_w / 2), self.cell_w] - y_splits = [0, int(self.cell_h / 3), int(2 * self.cell_h / 3), self.cell_h] + y_splits = [0, int(height / 3), int(2 * height / 3), height] # Block indices (col, row) for the 6 bits block_indices = [