feat: implement Packet 26 enhancement support with Hamming 24/18 decoding
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
from typing import List, Callable, Optional
|
||||
from .models import Packet, Page, TeletextService, decode_hamming_8_4
|
||||
from .models import Packet, Page, TeletextService, decode_hamming_8_4, decode_hamming_24_18, Packet26Enhancement
|
||||
|
||||
def load_t42(file_path: str, progress_callback: Optional[Callable[[int, int], None]] = None) -> TeletextService:
|
||||
service = TeletextService()
|
||||
@@ -70,6 +70,35 @@ def load_t42(file_path: str, progress_callback: Optional[Callable[[int, int], No
|
||||
# Update tracking
|
||||
current_pages_by_mag[mag] = new_page
|
||||
last_row_by_mag[mag] = 0
|
||||
elif row == 26:
|
||||
# Enhancement packet
|
||||
target_page = current_pages_by_mag.get(mag)
|
||||
if target_page:
|
||||
target_page.packets.append(packet)
|
||||
# Track active row within this packet/page for enhancements
|
||||
# Default active row is usually the last one?
|
||||
# Spec: "The active row is set to 0 at the beginning of each page."
|
||||
# We should probably keep state in the Page object during loading.
|
||||
if not hasattr(target_page, '_active_row'):
|
||||
target_page._active_row = 0
|
||||
|
||||
for i in range(13):
|
||||
b1 = packet.data[1 + i*3]
|
||||
b2 = packet.data[2 + i*3]
|
||||
b3 = packet.data[3 + i*3]
|
||||
triplet = decode_hamming_24_18(b1, b2, b3)
|
||||
if triplet is not None:
|
||||
address = triplet & 0x3F
|
||||
mode = (triplet >> 6) & 0x1F
|
||||
data = (triplet >> 11) & 0x7F
|
||||
|
||||
if 0x01 <= mode <= 0x07:
|
||||
# Set Active Row (bits 0-4 of data = row number)
|
||||
target_page._active_row = data & 0x1F
|
||||
elif mode == 0x00:
|
||||
# Overwrite character at (active_row, address)
|
||||
enh = Packet26Enhancement(row=target_page._active_row, col=address, mode=mode, data=data)
|
||||
target_page.packet26_enhancements.append(enh)
|
||||
else:
|
||||
# Add to the "current" page of this magazine.
|
||||
target_page = current_pages_by_mag.get(mag)
|
||||
|
||||
@@ -39,6 +39,52 @@ def decode_hamming_8_4(byte_val):
|
||||
# Return 4 data bits: D1, D2, D3, D4
|
||||
return b[1] | (b[3] << 1) | (b[5] << 2) | (b[7] << 3)
|
||||
|
||||
def decode_hamming_24_18(b1, b2, b3):
|
||||
"""
|
||||
Decodes a 24/18 Hamming triplet.
|
||||
Returns 18 bits of data or None if uncorrectable error.
|
||||
"""
|
||||
v = b1 | (b2 << 8) | (b3 << 16)
|
||||
|
||||
syndrome = 0
|
||||
for i in range(5):
|
||||
check = 0
|
||||
for j in range(23):
|
||||
if ((j + 1) >> i) & 1:
|
||||
check ^= (v >> j) & 1
|
||||
if check:
|
||||
syndrome |= (1 << i)
|
||||
|
||||
overall_parity = 0
|
||||
for j in range(24):
|
||||
overall_parity ^= (v >> j) & 1
|
||||
|
||||
if overall_parity == 0:
|
||||
if syndrome != 0:
|
||||
return None # Double error
|
||||
else:
|
||||
if syndrome != 0:
|
||||
v ^= (1 << (syndrome - 1))
|
||||
|
||||
# Extract 18 data bits
|
||||
d_indices = [2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22]
|
||||
res = 0
|
||||
for i, idx in enumerate(d_indices):
|
||||
if (v >> idx) & 1:
|
||||
res |= (1 << i)
|
||||
return res
|
||||
|
||||
@dataclass
|
||||
class Packet26Enhancement:
|
||||
"""
|
||||
Represents an enhancement from Packet 26.
|
||||
Usually a character replacement or attribute at a specific (row, col).
|
||||
"""
|
||||
row: int
|
||||
col: int
|
||||
mode: int
|
||||
data: int
|
||||
|
||||
@dataclass
|
||||
class Packet:
|
||||
"""
|
||||
@@ -100,6 +146,7 @@ class Page:
|
||||
sub_code: int = 0 # Subpage code (0000 to 3F7F hex usually, simplest is 0-99 equivalent)
|
||||
language: int = 0 # National Option (0-7)
|
||||
packets: List[Packet] = field(default_factory=list)
|
||||
packet26_enhancements: List[Packet26Enhancement] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def full_page_number(self):
|
||||
|
||||
@@ -333,9 +333,28 @@ class TeletextCanvas(QWidget):
|
||||
byte_val = ord(header_prefix[c])
|
||||
else:
|
||||
byte_val = data[c] if c < len(data) else 0x20
|
||||
|
||||
|
||||
byte_val &= 0x7F # Strip parity
|
||||
|
||||
# Packet 26 Overwrite?
|
||||
# Check if there is an enhancement for this (row, col)
|
||||
p26_data = None
|
||||
if self.page:
|
||||
for enh in self.page.packet26_enhancements:
|
||||
if enh.row == row and enh.col == c:
|
||||
if enh.mode == 0x00:
|
||||
# Overwrite character
|
||||
p26_data = enh.data
|
||||
break
|
||||
|
||||
# If we have P26 overwrite, we use it for display, but it doesn't change
|
||||
# the serial attribute processing of the 'original' byte_val if it was a control code?
|
||||
# Actually, P26 overwrites a displayable character.
|
||||
# If the original byte_val was a control code, does P26 replace it?
|
||||
# Usually yes, it's a "display at" operation.
|
||||
|
||||
display_byte = p26_data if p26_data is not None else byte_val
|
||||
|
||||
# Control Codes
|
||||
is_control = False
|
||||
|
||||
@@ -460,6 +479,20 @@ class TeletextCanvas(QWidget):
|
||||
|
||||
if drcs_char:
|
||||
self.draw_drcs(painter, x, y, drcs_char, fg, double_height)
|
||||
elif p26_data is not None:
|
||||
# Level 2.5 character from G2 supplementary set
|
||||
# (Technically P26 data can point to other sets, but G2 is common)
|
||||
from .charsets import get_char_g2
|
||||
char = get_char_g2(p26_data)
|
||||
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)
|
||||
elif graphics_mode:
|
||||
# Mosaic Graphics
|
||||
h_mos = self.cell_h * 2 if double_height else self.cell_h
|
||||
|
||||
Reference in New Issue
Block a user