经前面的结构性解码,dwg中的section已经呈现为位编码模式了。从位流的视角看,我们需要按照dwg中各种对象的组织形式,连续从位流中解读位码。但是,我们知道,电脑处理的最小的单位是字节,我们也没有对连续位流的读取能力,所以终究还是要从字节上处理。
1,位流解析类 CBitChain
不妨把我们解析位流的类叫CBitChain,它的功能就是解码位流。
class CBitChain
{
...
private:
uint8_t* m_chain; // 位流数据
uint32_t m_size; // 数据大小(bytes)
uint32_t m_byte; // 读取指针所在字节
uint8_t m_bit; // 读取指针所在字节中的位
}
这里要注意的是,m_bit的值与字节本身的位的值不同,顺序刚好相反,这样定义符合我们从左到右的阅读习惯。
bit of byte in computer: 76543210 76543210 76543210
bit of byte in CBitChain: 01234567 01234567 01234567
此类能够从当前位置处,读取不同编码格式的数据,并更新读取指针的值(即m_byte和m_bit的值)。这里贴一个读取RawChar的代码,其他代码就不贴了,按照编码规则使用位运算,不难实现。
// Read 1 byte (raw char)
BITCODE_RC CBitChain::bit_read_RC()
{
uint8_t result;
uint8_t byte = m_chain[m_byte];
if (m_bit == 0)
result = byte;
else
{
result = byte << m_bit;
if (m_byte < m_size - 1)
{
byte = m_chain[m_byte + 1];
result |= byte >> (8 - m_bit);
}
}
bit_advance_position(8);
return ((uint8_t)result);
}
2,字符串
2007之前的版本,字符串都是Ansi编码且夹杂在数据中间,读取时按照对象规格,遇到字符串就从当前位流处读取。从2007开始,字符串为UCS-2编码,且一个对象的所有字符串都统一集中到对象的尾部存放。这样,如果仍然使用一个CBitChian来读,势必会引起不便。因此,在解码对象时,无论什么版本,都提供2个位流解码器。假设一个叫chain,一个叫strchain,它们的初始值相同,对于2007+的dwg,对象编码数据中会提供参数计算strchain的值。
以AcDb:Classes section 为例,其数据布局为:
SN : 8D A1 C4 B8 C4 A9 F8 C5 C0 DC F4 5F E7 CF B6 8A
RL : bytesize, total bytes of data (data的范围为[bitsize,CRC)), RLL for 2010+
RL : bitsize, total bit size of data(bytesize中有效的bit数)
BL : Maxiumum class number
B : bool value
X : Class Data (format described below)
X : String stream data
B : bool value (true if string stream data is present)
RS : CRC
SN : 72 5E 3B 47 3B 56 07 3A 3F 23 0B A0 18 30 49 75
看实例:
AcDb:Classes的section map
name: AcDb:Classes
name length: 1a
data size: 529
max size: f800
encrption: 0
hashcode: 3f54045f
encoding: 4
page count: 1
page id: 13
offset: 0
size: ed00
uncompressed size: 529
compressed size: 36c
check sum: be37c4fb
crc: 27eb3fe9256530aa
AcDb:Classes section的数据
00000000 8D A1 C4 B8 C4 A9 F8 C5 C0 DC F4 5F E7 CF B6 8A
00000010 FB 04 00 00 D8 27 00 00 3F C0 40 00 27 A0 0C 3C
00000020 C0 50 14 59 2A A3 D4 06 1E 60 28 0A 2C 95 51 EC
00000030 03 0F 30 14 0D 16 4A A8 F7 01 3F C3 C7 98 0A 02
...
00000090 01 07 98 0A 04 8D A0 34 88 A7 80 31 00 35 00 32
000000A0 80 31 80 3A 00 22 00 21 00 2C 00 10 00 21 80 36
000000B0 00 30 80 39 80 39 80 32 80 39 80 23 28 20 0C 60
000000C0 08 80 0C 40 08 80 0D 20 0C 60 0E 80 0D 20 0D E0
000000D0 0D C0 0C 20 0E 40 0F 20 0A E0 0D 20 0E 80 0D 00
...
000004E0 60 03 60 02 98 03 A0 03 C8 03 60 03 28 02 68 03
000004F0 08 03 80 02 18 86 00 8A 00 98 00 98 00 A6 00 A8
00000500 00 B2 00 98 00 8A 00 9A 00 82 00 A0 01 50 47 1B
00000510 02 72 5E 3B 47 3B 56 07 3A 3F 23 0B A0 18 30 49
00000520 75 00 00 00 00 00 00 00 00
0x10:size=0x4FB
0x14:bitsize=0x27D8
0x14+0x4FB = 0x50F,以byte为边界,class的data到0x50E结束
0x14+0x27D8/8 = 0x50F,以bit为边界,class的data也到0x50E结束
本例中两个边界一致,只是巧合。一般来说,bit的边界小于等于byte的边界。
strchain起始位置的计算,要从bit边界的尾部向前倒推。
将strchain的指针移到尾部(0x50E, 7),并记str_chain的当前位置为p。
unsigned long p = strchain.position();
char has_string = strchain.read_B();
if (has_string == 0) return;
p -= 16;
strchain.bit_set_position(p);
long str_bit_size = strchain.bit_read_RS();
if (str_bit_size && 0x8000)
{
str_bit_size &= 0x7FFF;
p -= 16;
strchain.bit_set_position(p);
long high = strchain.bit_read_RS();
str_bit_size |= (high << 15);
}
strchain.bit_set_position(p - str_bit_size);
从代码可知,字符串区的大小是分两步得到的,先看short能否记录其大小,若不能,则再补充一个short组合成long型表示大小,尽可能的压榨存储空间。代码中高位的short值向左移15位,是因为,低位的short其最高位是标志位,要用高位的short的0位替换。
截取实例中0x50A到0x50E的内容来分析:
最后,计算strchain的起始位置:
字符串区大小 = 0x23A8 bits
bit position = 0x50E*8 + 7 - 0x10 - 0x23A8 = 0x4BF
byte = 0x4BF/8 = 0x97
bit = 0x4BF - 0x97 * 8 = 7
让我们定位到0x97字节,取一些数据加以验证。按照unicode字符串的编码格式解读如下:
对应的是第一个class的app name:ObjectDBX Classes
number: 500(1F4)
proxyflag: 0
app name: ObjectDBX Classes
cpp name: AcDbDictionaryWithDefault
dxf name: ACDBDICTIONARYWDFLT
class id: 0x1F3
num instance: 1
dwg ver: 22
maint ver: 42
3,句柄
对于Object/Entity除了跟字符串类似,也要计算其字符串区的起始位置(2007+)外,对象引用的句柄也是集中在句柄区,同样要计算其起始位置。不过,句柄区一般都在对象其他属性读完之后,再读取的,因此,不必另外使用一个句柄位流对象,借用chian,再定位到句柄区即可。
句柄区位置的计算比较简单,不详述了。
void CDwgObjectDecoderBase::calc_handle_stream_pos()
{
SINCE(R2010)
m_handlestream_pos = m_chain.size() * 8 - m_handlestream_size;
OTHER_VERSIONS
m_handlestream_pos = m_data_start * 8 + m_bitsize;
}
今天有点累,就写到这里了。