1 /**
2     INP2 Format Writer
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9     
10     Authors:
11         Luna Nielsen
12 */
13 module inp.format.inp2.writer;
14 import inp.format.node;
15 import inp.format.inp2;
16 import nulib.digest.crc;
17 import nulib.io.stream;
18 import nulib.io.stream.rw;
19 import nulib.math;
20 import numem;
21 
22 @nogc:
23 
24 /**
25     Write the contents of a DataNode into the INP File.
26 
27     Params:
28         stream =    The stream to write to.
29         node =      A DataNode object following the INP structure.
30 */
31 void writeINP2(Stream stream, ref DataNode node) {
32     scope StreamWriter writer = new StreamWriter(stream);
33     writer.writeUTF8(INP2_MAGIC);
34     writer.writeINP2Impl(node);
35 }
36 
37 
38 
39 
40 //
41 //              IMPLEMENTATION DETAILS
42 //
43 private:
44 
45 /// Function which pads null bytes until the alignment is correct.
46 void writeINP2Padding(StreamWriter writer) {
47     size_t location = writer.stream.tell();
48     ubyte[4] null_ = [0x00, 0x00, 0x00, 0x00];
49     writer.stream.write(null_[0..nu_alignup(location, 4)-location]);
50 }
51 
52 void writeINP2Key(StreamWriter writer, string key) {
53     writer.writeLE!uint(cast(uint)(INP2_TAG_KEY | (min(key.length, 255) << 8)));
54     writer.writeUTF8(key[0..min(key.length, 255)]);
55     writer.writeINP2Padding();
56 }
57 
58 void writeINP2Impl()(StreamWriter writer, auto ref DataNode node) {
59     final switch(node.type) {
60         case DataNodeType.undefined:
61             writer.writeLE!uint(INP2_TAG_NIL);
62             return;
63         
64         case DataNodeType.boolean_:
65             writer.writeLE!uint(INP2_TAG_BOOL);
66             writer.writeLE!uint(cast(uint)node.tryCoerce!bool);
67             return;
68         
69         case DataNodeType.int_:
70             writer.writeLE!uint(INP2_TAG_INT);
71             writer.writeLE!int(node.tryCoerce!int);
72             return;
73         
74         case DataNodeType.uint_:
75             writer.writeLE!uint(INP2_TAG_UINT);
76             writer.writeLE!uint(node.tryCoerce!uint);
77             return;
78         
79         case DataNodeType.float_:
80             writer.writeLE!uint(INP2_TAG_FLOAT);
81             writer.writeLE!float(node.tryCoerce!float);
82             return;
83         
84         case DataNodeType.string_:
85             if (node.text.length > 0xFFFFFE) {
86                 writer.writeLE!uint(INP2_TAG_STRING | 0xFFFFFF00);
87                 writer.writeLE!uint(cast(uint)node.text.length);
88                 writer.writeUTF8(node.text);
89                 writer.writeINP2Padding();
90                 return;
91             }
92             
93             writer.writeLE!uint(cast(uint)(INP2_TAG_STRING | (node.text.length << 8)));
94             writer.writeUTF8(node.text);
95             writer.writeINP2Padding();
96             return;
97         
98         case DataNodeType.blob_:
99             if (node.blob.length > 0xFFFFFE) {
100                 writer.writeLE!uint(INP2_TAG_BLOB | 0xFFFFFF00);
101                 writer.writeLE!uint(cast(uint)node.blob.length);
102                 writer.stream.write(node.blob);
103                 writer.writeINP2Padding();
104 
105                 // Blobs have crc32 checksums.
106                 writer.writeLE(crc32(node.blob));
107                 return;
108             }
109 
110             writer.writeLE!uint(cast(uint)(INP2_TAG_BLOB | (node.blob.length << 8)));
111             writer.stream.write(node.blob);
112             writer.writeINP2Padding();
113 
114             // Blobs have crc32 checksums.
115             writer.writeLE(crc32(node.blob));
116             return;
117         
118         case DataNodeType.array_:
119             writer.writeLE!uint(cast(uint)(INP2_TAG_ARRAY_BEGIN | node.length << 8));
120             foreach(n; node.array) {
121                 writer.writeINP2Impl(n);
122             }
123             writer.writeLE!uint(INP2_TAG_ARRAY_END);
124             return;
125         
126         case DataNodeType.object_:
127             writer.writeLE!uint(cast(uint)(INP2_TAG_OBJECT_BEGIN | node.length << 8));
128             foreach(string key, ref DataNode value; node.object) {
129                 writer.writeINP2Key(key);
130                 writer.writeINP2Impl(value);
131             }
132             writer.writeLE!uint(INP2_TAG_OBJECT_END);
133             return;
134     }
135 }