diff --git a/bemani/format/afp/swf.py b/bemani/format/afp/swf.py index 85df4e7..575dc5f 100644 --- a/bemani/format/afp/swf.py +++ b/bemani/format/afp/swf.py @@ -111,7 +111,7 @@ class AP2ShapeTag(Tag): class AP2DefineFontTag(Tag): - def __init__(self, id: int, fontname: str, xml_prefix: str, heights: List[int]) -> None: + def __init__(self, id: int, fontname: str, xml_prefix: str, heights: List[int], text_indexes: List[int]) -> None: super().__init__(id) # The font name is just the pretty name of the font. @@ -126,12 +126,66 @@ class AP2DefineFontTag(Tag): # in the texture map. self.heights = heights + # The list of text indexes are concatenated with the above prefix and height + # as a hex value to grab the actual character location in the font. It can + # be interpreted as an ascii value using chr() most of the time. + self.text_indexes = text_indexes + def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), 'fontname': self.fontname, 'xml_prefix': self.xml_prefix, 'heights': self.heights, + 'text_indexes': self.text_indexes, + } + + +class AP2TextChar: + def __init__(self, font_text_index: int, width: float) -> None: + # Given the parent line's font, this is an offset into the font's text indexes. + # This allows you to look up what actual character is being displayed at this + # location. + self.font_text_index = font_text_index + + # This is the width of the character. Don't know why this isn't looked up in + # the font? + self.width = width + + def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: + return { + 'font_text_index': self.font_text_index, + 'width': self.width, + } + + +class AP2TextLine: + def __init__(self, font_tag: Optional[int], height: int, xpos: float, ypos: float, entries: List[AP2TextChar]) -> None: + self.font_tag = font_tag + self.font_height = height + self.xpos = xpos + self.ypos = ypos + self.entries = entries + + def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: + return { + 'font_tag': self.font_tag, + 'font_height': self.font_height, + 'xpos': self.xpos, + 'ypos': self.ypos, + 'entries': [e.as_dict(*args, **kwargs) for e in self.entries], + } + + +class AP2DefineTextTag(Tag): + def __init__(self, id: int, lines: List[AP2TextLine]) -> None: + super().__init__(id) + + self.lines = lines + + def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: + return { + 'lines': [line.as_dict(*args, **kwargs) for line in self.lines], } @@ -871,24 +925,36 @@ class SWF(TrackedCoverage, VerboseOutput): return AP2DefineSpriteTag(sprite_id, tags, frames) elif tagid == AP2Tag.AP2_DEFINE_FONT: - unk, font_id, fontname_offset, xml_prefix_offset, data_offset, data_count = struct.unpack("> 24) & 0xFF) * 0.003921569 - multcolor.g = float((rgba >> 16) & 0xFF) * 0.003921569 - multcolor.b = float((rgba >> 8) & 0xFF) * 0.003921569 - multcolor.a = float(rgba & 0xFF) * 0.003921569 + multcolor.r = float((rgba >> 24) & 0xFF) / 255.0 + multcolor.g = float((rgba >> 16) & 0xFF) / 255.0 + multcolor.b = float((rgba >> 8) & 0xFF) / 255.0 + multcolor.a = float(rgba & 0xFF) / 255.0 self.vprint(f"{prefix} Mult Color: {multcolor}") if flags & 0x4000: @@ -1044,10 +1110,10 @@ class SWF(TrackedCoverage, VerboseOutput): self.add_coverage(dataoffset + running_pointer, 4) running_pointer += 4 - addcolor.r = float((rgba >> 24) & 0xFF) * 0.003921569 - addcolor.g = float((rgba >> 16) & 0xFF) * 0.003921569 - addcolor.b = float((rgba >> 8) & 0xFF) * 0.003921569 - addcolor.a = float(rgba & 0xFF) * 0.003921569 + addcolor.r = float((rgba >> 24) & 0xFF) / 255.0 + addcolor.g = float((rgba >> 16) & 0xFF) / 255.0 + addcolor.b = float((rgba >> 8) & 0xFF) / 255.0 + addcolor.a = float(rgba & 0xFF) / 255.0 self.vprint(f"{prefix} Add Color: {addcolor}") bytecodes: Dict[int, List[ByteCode]] = {} @@ -1228,9 +1294,85 @@ class SWF(TrackedCoverage, VerboseOutput): self.add_coverage(dataoffset, 4) return AP2RemoveObjectTag(object_id, depth) + elif tagid == AP2Tag.AP2_DEFINE_TEXT: + flags, text_id, text_data_count, sub_data_total_count, text_data_offset, sub_data_base_offset = struct.unpack( + " extra_data: + # There seems to be some amount of data left over at the end, not sure what it + # is or does. I don't see any references to it being used in the tag loader. + pass + + self.vprint(f"{prefix} Tag ID: {text_id}, Count of Entries: {text_data_count}, Count of Sub Entries: {sub_data_total_count}") + lines: List[AP2TextLine] = [] + for i in range(text_data_count): + chunk_data_offset = dataoffset + text_data_offset + (20 * i) + chunk_flags, sub_data_count, font_tag, font_height, xpos, ypos, sub_data_offset, rgba = struct.unpack( + "> 8) & 0xFF) / 255.0, + float((rgba >> 16) & 0xFF) / 255.0, + float((rgba >> 24) & 0xFF) / 255.0, + ) + + self.vprint(f"{prefix} Font Tag: {font_tag}, Font Height: {font_height}, X: {xpos}, Y: {ypos}, Count of Sub-Entries: {sub_data_count}, Color: {color}") + + base_offset = dataoffset + (sub_data_offset * 4) + sub_data_base_offset + offsets: List[AP2TextChar] = [] + for i in range(sub_data_count): + sub_chunk_offset = base_offset + (i * 4) + font_text_index, xoff = struct.unpack( + " Tuple[List[Tag], List[Frame]]: