/// Tile 格式序列化/反序列化模块 /// /// 实现移动端优化的二进制格式,支持: /// - 字符串字典压缩 /// - ID 索引化编码 /// - 坐标差值编码 /// /// @version 0.1.0 /// @date 2026-03-07 import 'dart:convert'; import 'dart:typed_data'; // ============================================================================ // 类型定义 // ============================================================================ /// 坐标类型 (单位:纳米) typedef Coordinate = int; /// 唯一标识符 typedef ID = String; // ============================================================================ // Tile 文件格式头 // ============================================================================ /// Tile 文件魔数 const TILE_MAGIC = 0x54494C45; // "TILE" /// Tile 格式版本 const TILE_VERSION = 0x0001; /// Tile 文件头 (16 字节) class TileHeader { final int magic; // 4 字节 final int version; // 4 字节 final int dataSize; // 4 字节 (压缩后数据大小) final int flags; // 4 字节 (压缩标志位) TileHeader({ required this.magic, required this.version, required this.dataSize, this.flags = 0, }); ByteData toBytes() { final buffer = Uint8List(16).buffer; final data = ByteData.view(buffer); data.setInt32(0, magic, Endian.host); data.setInt32(4, version, Endian.host); data.setInt32(8, dataSize, Endian.host); data.setInt32(12, flags, Endian.host); return data; } static TileHeader fromBytes(ByteData data) { return TileHeader( magic: data.getInt32(0, Endian.host), version: data.getInt32(4, Endian.host), dataSize: data.getInt32(8, Endian.host), flags: data.getInt32(12, Endian.host), ); } } // ============================================================================ // 字符串字典压缩器 // ============================================================================ /// 字符串字典压缩器 /// /// 将重复出现的字符串映射到索引,减少存储空间 class StringDictionary { final Map _stringToIndex = {}; final List _indexToString = []; /// 添加字符串并返回索引 int add(String str) { if (_stringToIndex.containsKey(str)) { return _stringToIndex[str]!; } final index = _indexToString.length; _stringToIndex[str] = index; _indexToString.add(str); return index; } /// 根据索引获取字符串 String get(int index) { return _indexToString[index]; } /// 是否包含字符串 bool contains(String str) { return _stringToIndex.containsKey(str); } /// 字典大小 int get size => _indexToString.length; /// 序列化为字节 Uint8List toBytes() { final stringBytes = []; // 写入字典大小 stringBytes.addAll(_encodeVarInt(size)); // 写入每个字符串 for (final str in _indexToString) { final utf8Bytes = utf8.encode(str); stringBytes.addAll(_encodeVarInt(utf8Bytes.length)); stringBytes.addAll(utf8Bytes); } return Uint8List.fromList(stringBytes); } /// 从字节反序列化 static StringDictionary fromBytes(Uint8List bytes) { final dict = StringDictionary(); int offset = 0; // 读取字典大小 final size = _decodeVarInt(bytes, offset); offset += _getVarIntLength(size); // 读取每个字符串 for (int i = 0; i < size; i++) { final strLen = _decodeVarInt(bytes, offset); offset += _getVarIntLength(strLen); final strBytes = bytes.sublist(offset, offset + strLen); offset += strLen; dict.add(utf8.decode(strBytes)); } return dict; } // Variable-length integer encoding (类似 Protocol Buffers) List _encodeVarInt(int value) { final result = []; while (value > 0x7F) { result.add((value & 0x7F) | 0x80); value >>= 7; } result.add(value & 0x7F); return result; } static int _decodeVarInt(Uint8List bytes, int offset) { int result = 0; int shift = 0; while (true) { final byte = bytes[offset + (shift >> 3)]; result |= (byte & 0x7F) << shift; if ((byte & 0x80) == 0) break; shift += 7; } return result; } static int _getVarIntLength(int value) { if (value == 0) return 1; int length = 0; while (value > 0) { length++; value >>= 7; } return length; } } // ============================================================================ // ID 索引化编码器 // ============================================================================ /// ID 索引化编码器 /// /// 将 ID 映射到紧凑的整数索引,减少重复 ID 的存储 class IdIndexer { final Map _idToIndex = {}; final List _indexToId = []; /// 添加 ID 并返回索引 int add(ID id) { if (_idToIndex.containsKey(id)) { return _idToIndex[id]!; } final index = _indexToId.length; _idToIndex[id] = index; _indexToId.add(id); return index; } /// 根据索引获取 ID ID get(int index) { return _indexToId[index]; } /// 是否包含 ID bool contains(ID id) { return _idToIndex.containsKey(id); } /// 索引器大小 int get size => _indexToId.length; /// 获取 ID 的索引 (如果不存在返回 -1) int getIndex(ID id) { return _idToIndex[id] ?? -1; } } // ============================================================================ // 坐标差值编码器 // ============================================================================ /// 坐标差值编码器 /// /// 对坐标序列使用差值编码,减少存储空间 /// 特别适用于走线路径、多边形顶点等连续坐标 class CoordinateDeltaEncoder { /// 编码坐标列表 (使用差值) /// /// 第一个坐标使用绝对值,后续坐标使用与前一个的差值 static Uint8List encode(List<(int x, int y)> coordinates) { if (coordinates.isEmpty) { return Uint8List(0); } final buffer = BytesBuilder(); // 写入坐标数量 buffer.add(_encodeVarInt(coordinates.length)); // 第一个坐标使用绝对值 var prevX = coordinates.first.$1; var prevY = coordinates.first.$2; buffer.add(_encodeSignedVarInt(prevX)); buffer.add(_encodeSignedVarInt(prevY)); // 后续坐标使用差值 for (int i = 1; i < coordinates.length; i++) { final x = coordinates[i].$1; final y = coordinates[i].$2; final deltaX = x - prevX; final deltaY = y - prevY; buffer.add(_encodeSignedVarInt(deltaX)); buffer.add(_encodeSignedVarInt(deltaY)); prevX = x; prevY = y; } return buffer.toBytes(); } /// 解码坐标列表 static List<(int x, int y)> decode(Uint8List bytes) { if (bytes.isEmpty) { return []; } final coordinates = <(int x, int y)>[]; int offset = 0; // 读取坐标数量 final count = _decodeVarInt(bytes, offset); offset += _getVarIntLength(count); if (count == 0) { return []; } // 读取第一个坐标 (绝对值) var x = _decodeSignedVarInt(bytes, offset); offset += _getSignedVarIntLength(x); var y = _decodeSignedVarInt(bytes, offset); offset += _getSignedVarIntLength(y); coordinates.add((x, y)); // 读取后续坐标 (差值) for (int i = 1; i < count; i++) { final deltaX = _decodeSignedVarInt(bytes, offset); offset += _getSignedVarIntLength(deltaX); final deltaY = _decodeSignedVarInt(bytes, offset); offset += _getSignedVarIntLength(deltaY); x += deltaX; y += deltaY; coordinates.add((x, y)); } return coordinates; } // Variable-length integer encoding for signed integers static List _encodeSignedVarInt(int value) { // Zigzag encoding: (value << 1) ^ (value >> 63) final zigzag = (value << 1) ^ (value >> 63); return _encodeVarInt(zigzag); } static int _decodeSignedVarInt(Uint8List bytes, int offset) { final zigzag = _decodeVarInt(bytes, offset); // Reverse zigzag: (zigzag >> 1) ^ -(zigzag & 1) return (zigzag >> 1) ^ -(zigzag & 1); } static List _encodeVarInt(int value) { final result = []; while (value > 0x7F) { result.add((value & 0x7F) | 0x80); value >>= 7; } result.add(value & 0x7F); return result; } static int _decodeVarInt(Uint8List bytes, int offset) { int result = 0; int shift = 0; while (true) { final byte = bytes[offset + (shift >> 3)]; result |= (byte & 0x7F) << shift; if ((byte & 0x80) == 0) break; shift += 7; } return result; } static int _getVarIntLength(int value) { if (value == 0) return 1; int length = 0; while (value > 0) { length++; value >>= 7; } return length; } static int _getSignedVarIntLength(int value) { final zigzag = (value << 1) ^ (value >> 63); return _getVarIntLength(zigzag); } } // ============================================================================ // Tile 序列化器 // ============================================================================ /// Tile 格式序列化器 /// /// 将 JSON 格式的设计数据转换为紧凑的 Tile 二进制格式 class TileSerializer { final StringDictionary _stringDict = StringDictionary(); final IdIndexer _idIndexer = IdIndexer(); /// 序列化设计数据为 Tile 格式 Uint8List serialize(Map jsonData) { // 1. 构建字符串字典和 ID 索引 _buildIndexes(jsonData); // 2. 序列化数据体 final dataBytes = _serializeData(jsonData); // 3. 构建文件头 final header = TileHeader( magic: TILE_MAGIC, version: TILE_VERSION, dataSize: dataBytes.length, flags: 0x01, // 标志位:使用字符串字典 ); // 4. 组合完整文件 final buffer = BytesBuilder(); buffer.add(header.toBytes().buffer.asUint8List()); buffer.add(_stringDict.toBytes()); buffer.add(_serializeIdIndex()); buffer.add(dataBytes); return buffer.toBytes(); } /// 构建索引 void _buildIndexes(dynamic data) { if (data is Map) { for (final entry in data.entries) { if (entry.key is String) { _stringDict.add(entry.key as String); } if (entry.key == 'id' && entry.value is String) { _idIndexer.add(entry.value as String); } if (entry.key == 'name' && entry.value is String) { _stringDict.add(entry.value as String); } if (entry.key == 'type' && entry.value is String) { _stringDict.add(entry.value as String); } _buildIndexes(entry.value); } } else if (data is List) { for (final item in data) { _buildIndexes(item); } } else if (data is String) { _stringDict.add(data); } } /// 序列化 ID 索引表 Uint8List _serializeIdIndex() { final buffer = BytesBuilder(); // 写入 ID 数量 buffer.add(_encodeVarInt(_idIndexer.size)); // 写入每个 ID for (int i = 0; i < _idIndexer.size; i++) { final id = _idIndexer.get(i); final idBytes = utf8.encode(id); buffer.add(_encodeVarInt(idBytes.length)); buffer.add(idBytes); } return buffer.toBytes(); } /// 序列化数据体 Uint8List _serializeData(Map data) { final buffer = BytesBuilder(); _serializeValue(data, buffer); return buffer.toBytes(); } void _serializeValue(dynamic value, BytesBuilder buffer) { if (value == null) { buffer.add([0x00]); // null 标记 } else if (value is bool) { buffer.add([value ? 0x01 : 0x02]); } else if (value is int) { buffer.add([0x03]); buffer.add(_encodeSignedVarInt(value)); } else if (value is double) { buffer.add([0x04]); final bytes = Uint8List(8); ByteData.view(bytes.buffer).setFloat64(0, value, Endian.host); buffer.add(bytes); } else if (value is String) { buffer.add([0x05]); final index = _stringDict.add(value); buffer.add(_encodeVarInt(index)); } else if (value is List) { buffer.add([0x06]); buffer.add(_encodeVarInt(value.length)); for (final item in value) { _serializeValue(item, buffer); } } else if (value is Map) { buffer.add([0x07]); buffer.add(_encodeVarInt(value.length)); for (final entry in value.entries) { final keyIndex = _stringDict.add(entry.key as String); buffer.add(_encodeVarInt(keyIndex)); _serializeValue(entry.value, buffer); } } } List _encodeVarInt(int value) { final result = []; while (value > 0x7F) { result.add((value & 0x7F) | 0x80); value >>= 7; } result.add(value & 0x7F); return result; } List _encodeSignedVarInt(int value) { final zigzag = (value << 1) ^ (value >> 63); return _encodeVarInt(zigzag); } } // ============================================================================ // Tile 反序列化器 // ============================================================================ /// Tile 格式反序列化器 class TileDeserializer { late StringDictionary _stringDict; late IdIndexer _idIndexer; /// 反序列化 Tile 格式为 JSON Map deserialize(Uint8List bytes) { if (bytes.length < 16) { throw FormatException('Tile file too small'); } // 1. 读取文件头 final header = TileHeader.fromBytes(ByteData.sublistView(bytes, 0, 16)); if (header.magic != TILE_MAGIC) { throw FormatException('Invalid Tile file magic'); } if (header.version != TILE_VERSION) { throw FormatException('Unsupported Tile version: ${header.version}'); } // 2. 读取字符串字典 int offset = 16; _stringDict = StringDictionary.fromBytes( bytes.sublist(offset) ); offset += _estimateDictionarySize(bytes, offset); // 3. 读取 ID 索引表 _idIndexer = _deserializeIdIndex(bytes, offset); offset += _estimateIdIndexSize(bytes, offset); // 4. 读取数据体 final data = _deserializeData(bytes, offset); return data as Map; } int _estimateDictionarySize(Uint8List bytes, int offset) { // 简单估计:读取第一个 varint 作为大小,然后估算 final size = _decodeVarInt(bytes, offset); int pos = offset + _getVarIntLength(size); for (int i = 0; i < size; i++) { final strLen = _decodeVarInt(bytes, pos); pos += _getVarIntLength(strLen) + strLen; } return pos - offset; } int _estimateIdIndexSize(Uint8List bytes, int offset) { final count = _decodeVarInt(bytes, offset); int pos = offset + _getVarIntLength(count); for (int i = 0; i < count; i++) { final idLen = _decodeVarInt(bytes, pos); pos += _getVarIntLength(idLen) + idLen; } return pos - offset; } IdIndexer _deserializeIdIndex(Uint8List bytes, int offset) { final indexer = IdIndexer(); final count = _decodeVarInt(bytes, offset); offset += _getVarIntLength(count); for (int i = 0; i < count; i++) { final idLen = _decodeVarInt(bytes, offset); offset += _getVarIntLength(idLen); final idBytes = bytes.sublist(offset, offset + idLen); offset += idLen; indexer.add(utf8.decode(idBytes)); } return indexer; } dynamic _deserializeData(Uint8List bytes, int offset) { final type = bytes[offset]; offset++; switch (type) { case 0x00: // null return null; case 0x01: // true return true; case 0x02: // false return false; case 0x03: // int return _decodeSignedVarInt(bytes, offset); case 0x04: // double return ByteData.view(bytes.buffer, offset, 8).getFloat64(0, Endian.host); case 0x05: // string final index = _decodeVarInt(bytes, offset); return _stringDict.get(index); case 0x06: // list return _deserializeList(bytes, offset); case 0x07: // map return _deserializeMap(bytes, offset); default: throw FormatException('Unknown type marker: $type'); } } List _deserializeList(Uint8List bytes, int offset) { final count = _decodeVarInt(bytes, offset); offset += _getVarIntLength(count); final list = []; for (int i = 0; i < count; i++) { final value = _deserializeData(bytes, offset); list.add(value); offset += _estimateValueSize(bytes, offset); } return list; } Map _deserializeMap(Uint8List bytes, int offset) { final count = _decodeVarInt(bytes, offset); offset += _getVarIntLength(count); final map = {}; for (int i = 0; i < count; i++) { final keyIndex = _decodeVarInt(bytes, offset); offset += _getVarIntLength(keyIndex); final key = _stringDict.get(keyIndex); final value = _deserializeData(bytes, offset); map[key] = value; offset += _estimateValueSize(bytes, offset); } return map; } int _estimateValueSize(Uint8List bytes, int offset) { final type = bytes[offset]; offset++; switch (type) { case 0x00: // null case 0x01: // true case 0x02: // false return 1; case 0x03: // int int i = offset; while ((bytes[i] & 0x80) != 0) i++; return i - offset + 1; case 0x04: // double return 8; case 0x05: // string return _getVarIntLength(_decodeVarInt(bytes, offset)); case 0x06: // list case 0x07: // map // 递归估计 (简化处理) return 10; default: return 1; } } int _decodeVarInt(Uint8List bytes, int offset) { int result = 0; int shift = 0; while (true) { final byte = bytes[offset + (shift >> 3)]; result |= (byte & 0x7F) << shift; if ((byte & 0x80) == 0) break; shift += 7; } return result; } int _decodeSignedVarInt(Uint8List bytes, int offset) { final zigzag = _decodeVarInt(bytes, offset); return (zigzag >> 1) ^ -(zigzag & 1); } int _getVarIntLength(int value) { if (value == 0) return 1; int length = 0; while (value > 0) { length++; value >>= 7; } return length; } } // ============================================================================ // 工具函数 // ============================================================================ /// 将设计对象转换为 Tile 格式 Uint8List designToTile(Map design) { final serializer = TileSerializer(); return serializer.serialize(design); } /// 将 Tile 格式转换为设计对象 Map tileToDesign(Uint8List tileBytes) { final deserializer = TileDeserializer(); return deserializer.deserialize(tileBytes); }