mobile-eda/lib/data/format/tile_format.dart

713 lines
19 KiB
Dart

/// 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<String, int> _stringToIndex = {};
final List<String> _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 = <int>[];
// 写入字典大小
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<int> _encodeVarInt(int value) {
final result = <int>[];
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<ID, int> _idToIndex = {};
final List<ID> _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<int> _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<int> _encodeVarInt(int value) {
final result = <int>[];
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<String, dynamic> 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<String, dynamic> 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<int> _encodeVarInt(int value) {
final result = <int>[];
while (value > 0x7F) {
result.add((value & 0x7F) | 0x80);
value >>= 7;
}
result.add(value & 0x7F);
return result;
}
List<int> _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<String, dynamic> 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<String, dynamic>;
}
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<dynamic> _deserializeList(Uint8List bytes, int offset) {
final count = _decodeVarInt(bytes, offset);
offset += _getVarIntLength(count);
final list = <dynamic>[];
for (int i = 0; i < count; i++) {
final value = _deserializeData(bytes, offset);
list.add(value);
offset += _estimateValueSize(bytes, offset);
}
return list;
}
Map<String, dynamic> _deserializeMap(Uint8List bytes, int offset) {
final count = _decodeVarInt(bytes, offset);
offset += _getVarIntLength(count);
final map = <String, dynamic>{};
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<String, dynamic> design) {
final serializer = TileSerializer();
return serializer.serialize(design);
}
/// 将 Tile 格式转换为设计对象
Map<String, dynamic> tileToDesign(Uint8List tileBytes) {
final deserializer = TileDeserializer();
return deserializer.deserialize(tileBytes);
}