/src/gdal/ogr/ogrsf_frmts/mitab/mitab_maptoolblock.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * Name: mitab_maptoollock.cpp |
4 | | * Project: MapInfo TAB Read/Write library |
5 | | * Language: C++ |
6 | | * Purpose: Implementation of the TABMAPToolBlock class used to handle |
7 | | * reading/writing of the .MAP files' drawing tool blocks |
8 | | * Author: Daniel Morissette, dmorissette@dmsolutions.ca |
9 | | * |
10 | | ********************************************************************** |
11 | | * Copyright (c) 1999, 2000, Daniel Morissette |
12 | | * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com> |
13 | | * |
14 | | * SPDX-License-Identifier: MIT |
15 | | **********************************************************************/ |
16 | | |
17 | | #include "cpl_port.h" |
18 | | #include "mitab.h" |
19 | | |
20 | | #include <cstddef> |
21 | | |
22 | | #include "cpl_conv.h" |
23 | | #include "cpl_error.h" |
24 | | #include "cpl_vsi.h" |
25 | | #include "mitab_priv.h" |
26 | | |
27 | | /*===================================================================== |
28 | | * class TABMAPToolBlock |
29 | | *====================================================================*/ |
30 | | |
31 | | constexpr int MAP_TOOL_HEADER_SIZE = 8; |
32 | | |
33 | | /********************************************************************** |
34 | | * TABMAPToolBlock::TABMAPToolBlock() |
35 | | * |
36 | | * Constructor. |
37 | | **********************************************************************/ |
38 | | TABMAPToolBlock::TABMAPToolBlock(TABAccess eAccessMode /*= TABRead*/) |
39 | 288 | : TABRawBinBlock(eAccessMode, TRUE), m_numDataBytes(0), m_nNextToolBlock(0), |
40 | 288 | m_numBlocksInChain(1), // Current block counts as 1 |
41 | 288 | m_poBlockManagerRef(nullptr) |
42 | 288 | { |
43 | 288 | } |
44 | | |
45 | | /********************************************************************** |
46 | | * TABMAPToolBlock::~TABMAPToolBlock() |
47 | | * |
48 | | * Destructor. |
49 | | **********************************************************************/ |
50 | | TABMAPToolBlock::~TABMAPToolBlock() |
51 | 288 | { |
52 | 288 | } |
53 | | |
54 | | /********************************************************************** |
55 | | * TABMAPToolBlock::EndOfChain() |
56 | | * |
57 | | * Return TRUE if we reached the end of the last block in the chain |
58 | | * TABMAPToolBlocks, or FALSE if there is still data to be read from |
59 | | * this chain. |
60 | | **********************************************************************/ |
61 | | GBool TABMAPToolBlock::EndOfChain() |
62 | 137 | { |
63 | 137 | if (m_pabyBuf && (m_nCurPos < (m_numDataBytes + MAP_TOOL_HEADER_SIZE) || |
64 | 21 | m_nNextToolBlock > 0)) |
65 | 116 | { |
66 | 116 | return FALSE; // There is still data to be read. |
67 | 116 | } |
68 | | |
69 | 21 | return TRUE; |
70 | 137 | } |
71 | | |
72 | | /********************************************************************** |
73 | | * TABMAPToolBlock::InitBlockFromData() |
74 | | * |
75 | | * Perform some initialization on the block after its binary data has |
76 | | * been set or changed (or loaded from a file). |
77 | | * |
78 | | * Returns 0 if successful or -1 if an error happened, in which case |
79 | | * CPLError() will have been called. |
80 | | **********************************************************************/ |
81 | | int TABMAPToolBlock::InitBlockFromData(GByte *pabyBuf, int nBlockSize, |
82 | | int nSizeUsed, |
83 | | GBool bMakeCopy /* = TRUE */, |
84 | | VSILFILE *fpSrc /* = NULL */, |
85 | | int nOffset /* = 0 */) |
86 | 42 | { |
87 | | /*----------------------------------------------------------------- |
88 | | * First of all, we must call the base class' InitBlockFromData() |
89 | | *----------------------------------------------------------------*/ |
90 | 42 | const int nStatus = TABRawBinBlock::InitBlockFromData( |
91 | 42 | pabyBuf, nBlockSize, nSizeUsed, bMakeCopy, fpSrc, nOffset); |
92 | 42 | if (nStatus != 0) |
93 | 0 | return nStatus; |
94 | | |
95 | | /*----------------------------------------------------------------- |
96 | | * Validate block type |
97 | | *----------------------------------------------------------------*/ |
98 | 42 | if (m_nBlockType != TABMAP_TOOL_BLOCK) |
99 | 12 | { |
100 | 12 | CPLError(CE_Failure, CPLE_FileIO, |
101 | 12 | "InitBlockFromData(): Invalid Block Type: got %d expected %d", |
102 | 12 | m_nBlockType, TABMAP_TOOL_BLOCK); |
103 | 12 | CPLFree(m_pabyBuf); |
104 | 12 | m_pabyBuf = nullptr; |
105 | 12 | return -1; |
106 | 12 | } |
107 | | |
108 | | /*----------------------------------------------------------------- |
109 | | * Init member variables |
110 | | *----------------------------------------------------------------*/ |
111 | 30 | GotoByteInBlock(0x002); |
112 | 30 | m_numDataBytes = ReadInt16(); /* Excluding 8 bytes header */ |
113 | 30 | if (m_numDataBytes < 0 || |
114 | 29 | m_numDataBytes + MAP_TOOL_HEADER_SIZE > nBlockSize) |
115 | 2 | { |
116 | 2 | CPLError(CE_Failure, CPLE_FileIO, |
117 | 2 | "TABMAPToolBlock::InitBlockFromData(): m_numDataBytes=%d " |
118 | 2 | "incompatible with block size %d", |
119 | 2 | m_numDataBytes, nBlockSize); |
120 | 2 | CPLFree(m_pabyBuf); |
121 | 2 | m_pabyBuf = nullptr; |
122 | 2 | return -1; |
123 | 2 | } |
124 | | |
125 | 28 | m_nNextToolBlock = ReadInt32(); |
126 | 28 | if (m_nNextToolBlock != 0 && |
127 | 0 | (m_nNextToolBlock / m_nBlockSize) * m_nBlockSize == nOffset) |
128 | 0 | { |
129 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
130 | 0 | "InitBlockFromData(): self referencing block"); |
131 | 0 | CPLFree(m_pabyBuf); |
132 | 0 | m_pabyBuf = nullptr; |
133 | 0 | return -1; |
134 | 0 | } |
135 | | |
136 | | /*----------------------------------------------------------------- |
137 | | * The read ptr is now located at the beginning of the data part. |
138 | | *----------------------------------------------------------------*/ |
139 | 28 | GotoByteInBlock(MAP_TOOL_HEADER_SIZE); |
140 | | |
141 | 28 | return 0; |
142 | 28 | } |
143 | | |
144 | | /********************************************************************** |
145 | | * TABMAPToolBlock::CommitToFile() |
146 | | * |
147 | | * Commit the current state of the binary block to the file to which |
148 | | * it has been previously attached. |
149 | | * |
150 | | * This method makes sure all values are properly set in the map object |
151 | | * block header and then calls TABRawBinBlock::CommitToFile() to do |
152 | | * the actual writing to disk. |
153 | | * |
154 | | * Returns 0 if successful or -1 if an error happened, in which case |
155 | | * CPLError() will have been called. |
156 | | **********************************************************************/ |
157 | | int TABMAPToolBlock::CommitToFile() |
158 | 189 | { |
159 | 189 | if (m_pabyBuf == nullptr) |
160 | 0 | { |
161 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
162 | 0 | "CommitToFile(): Block has not been initialized yet!"); |
163 | 0 | return -1; |
164 | 0 | } |
165 | | |
166 | | /*----------------------------------------------------------------- |
167 | | * Nothing to do here if block has not been modified |
168 | | *----------------------------------------------------------------*/ |
169 | 189 | if (!m_bModified) |
170 | 0 | return 0; |
171 | | |
172 | | /*----------------------------------------------------------------- |
173 | | * Make sure 8 bytes block header is up to date. |
174 | | *----------------------------------------------------------------*/ |
175 | 189 | GotoByteInBlock(0x000); |
176 | | |
177 | 189 | WriteInt16(TABMAP_TOOL_BLOCK); // Block type code |
178 | 189 | CPLAssert(m_nSizeUsed >= MAP_TOOL_HEADER_SIZE && |
179 | 189 | m_nSizeUsed < MAP_TOOL_HEADER_SIZE + 32768); |
180 | 189 | WriteInt16(static_cast<GInt16>(m_nSizeUsed - |
181 | 189 | MAP_TOOL_HEADER_SIZE)); // num. bytes used |
182 | 189 | WriteInt32(m_nNextToolBlock); |
183 | | |
184 | 189 | int nStatus = CPLGetLastErrorType() == CE_Failure ? -1 : 0; |
185 | | |
186 | | /*----------------------------------------------------------------- |
187 | | * OK, call the base class to write the block to disk. |
188 | | *----------------------------------------------------------------*/ |
189 | 189 | if (nStatus == 0) |
190 | 189 | { |
191 | | #ifdef DEBUG_VERBOSE |
192 | | CPLDebug("MITAB", "Committing TOOL block to offset %d", m_nFileOffset); |
193 | | #endif |
194 | 189 | nStatus = TABRawBinBlock::CommitToFile(); |
195 | 189 | } |
196 | | |
197 | 189 | return nStatus; |
198 | 189 | } |
199 | | |
200 | | /********************************************************************** |
201 | | * TABMAPToolBlock::InitNewBlock() |
202 | | * |
203 | | * Initialize a newly created block so that it knows to which file it |
204 | | * is attached, its block size, etc . and then perform any specific |
205 | | * initialization for this block type, including writing a default |
206 | | * block header, etc. and leave the block ready to receive data. |
207 | | * |
208 | | * This is an alternative to calling ReadFromFile() or InitBlockFromData() |
209 | | * that puts the block in a stable state without loading any initial |
210 | | * data in it. |
211 | | * |
212 | | * Returns 0 if successful or -1 if an error happened, in which case |
213 | | * CPLError() will have been called. |
214 | | **********************************************************************/ |
215 | | int TABMAPToolBlock::InitNewBlock(VSILFILE *fpSrc, int nBlockSize, |
216 | | int nFileOffset /* = 0*/) |
217 | 288 | { |
218 | | #ifdef DEBUG_VERBOSE |
219 | | CPLDebug("MITAB", "Instantiating new TOOL block at offset %d", nFileOffset); |
220 | | #endif |
221 | | |
222 | | /*----------------------------------------------------------------- |
223 | | * Start with the default initialization |
224 | | *----------------------------------------------------------------*/ |
225 | 288 | if (TABRawBinBlock::InitNewBlock(fpSrc, nBlockSize, nFileOffset) != 0) |
226 | 0 | return -1; |
227 | | |
228 | | /*----------------------------------------------------------------- |
229 | | * And then set default values for the block header. |
230 | | *----------------------------------------------------------------*/ |
231 | 288 | m_nNextToolBlock = 0; |
232 | | |
233 | 288 | m_numDataBytes = 0; |
234 | | |
235 | 288 | GotoByteInBlock(0x000); |
236 | | |
237 | 288 | if (m_eAccess != TABRead) |
238 | 189 | { |
239 | 189 | WriteInt16(TABMAP_TOOL_BLOCK); // Block type code |
240 | 189 | WriteInt16(0); // num. bytes used, excluding header |
241 | 189 | WriteInt32(0); // Pointer to next tool block |
242 | 189 | } |
243 | | |
244 | 288 | if (CPLGetLastErrorType() == CE_Failure) |
245 | 0 | return -1; |
246 | | |
247 | 288 | return 0; |
248 | 288 | } |
249 | | |
250 | | /********************************************************************** |
251 | | * TABMAPToolBlock::SetNextToolBlock() |
252 | | * |
253 | | * Set the address (offset from beginning of file) of the drawing tool block |
254 | | * that follows the current one. |
255 | | **********************************************************************/ |
256 | | void TABMAPToolBlock::SetNextToolBlock(GInt32 nNextToolBlockAddress) |
257 | 0 | { |
258 | 0 | m_nNextToolBlock = nNextToolBlockAddress; |
259 | 0 | } |
260 | | |
261 | | /********************************************************************** |
262 | | * TABMAPToolBlock::SetMAPBlockManagerRef() |
263 | | * |
264 | | * Pass a reference to the block manager object for the file this |
265 | | * block belongs to. The block manager will be used by this object |
266 | | * when it needs to automatically allocate a new block. |
267 | | **********************************************************************/ |
268 | | void TABMAPToolBlock::SetMAPBlockManagerRef(TABBinBlockManager *poBlockMgr) |
269 | 189 | { |
270 | 189 | m_poBlockManagerRef = poBlockMgr; |
271 | 189 | } |
272 | | |
273 | | /********************************************************************** |
274 | | * TABMAPToolBlock::ReadBytes() |
275 | | * |
276 | | * Cover function for TABRawBinBlock::ReadBytes() that will automagically |
277 | | * load the next coordinate block in the chain before reading the |
278 | | * requested bytes if we are at the end of the current block and if |
279 | | * m_nNextToolBlock is a valid block. |
280 | | * |
281 | | * Then the control is passed to TABRawBinBlock::ReadBytes() to finish the |
282 | | * work: |
283 | | * Copy the number of bytes from the data block's internal buffer to |
284 | | * the user's buffer pointed by pabyDstBuf. |
285 | | * |
286 | | * Passing pabyDstBuf = NULL will only move the read pointer by the |
287 | | * specified number of bytes as if the copy had happened... but it |
288 | | * won't crash. |
289 | | * |
290 | | * Returns 0 if successful or -1 if an error happened, in which case |
291 | | * CPLError() will have been called. |
292 | | **********************************************************************/ |
293 | | int TABMAPToolBlock::ReadBytes(int numBytes, GByte *pabyDstBuf) |
294 | 911 | { |
295 | 911 | if (m_pabyBuf && m_nCurPos >= (m_numDataBytes + MAP_TOOL_HEADER_SIZE) && |
296 | 10 | m_nNextToolBlock > 0) |
297 | 0 | { |
298 | 0 | int nStatus = GotoByteInFile(m_nNextToolBlock); |
299 | 0 | if (nStatus != 0) |
300 | 0 | { |
301 | | // Failed.... an error has already been reported. |
302 | 0 | return nStatus; |
303 | 0 | } |
304 | | |
305 | 0 | GotoByteInBlock(MAP_TOOL_HEADER_SIZE); // Move pointer past header |
306 | | |
307 | | // FIXME ? what about a corrupted tab file that would have more than |
308 | | // 255 blocks |
309 | 0 | m_numBlocksInChain++; |
310 | 0 | } |
311 | | |
312 | 911 | return TABRawBinBlock::ReadBytes(numBytes, pabyDstBuf); |
313 | 911 | } |
314 | | |
315 | | /********************************************************************** |
316 | | * TABMAPToolBlock::WriteBytes() |
317 | | * |
318 | | * Cover function for TABRawBinBlock::WriteBytes() that will automagically |
319 | | * CommitToFile() the current block and create a new one if we are at |
320 | | * the end of the current block. |
321 | | * |
322 | | * Then the control is passed to TABRawBinBlock::WriteBytes() to finish the |
323 | | * work. |
324 | | * |
325 | | * Passing pabySrcBuf = NULL will only move the write pointer by the |
326 | | * specified number of bytes as if the copy had happened... but it |
327 | | * won't crash. |
328 | | * |
329 | | * Returns 0 if successful or -1 if an error happened, in which case |
330 | | * CPLError() will have been called. |
331 | | **********************************************************************/ |
332 | | int TABMAPToolBlock::WriteBytes(int nBytesToWrite, const GByte *pabySrcBuf) |
333 | 5.48k | { |
334 | 5.48k | if (m_eAccess == TABWrite && m_poBlockManagerRef && |
335 | 4.91k | (m_nBlockSize - m_nCurPos) < nBytesToWrite) |
336 | 0 | { |
337 | 0 | if (m_numBlocksInChain >= 255) |
338 | 0 | { |
339 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
340 | 0 | "Maximum number of 255 tool blocks reached"); |
341 | 0 | return -1; |
342 | 0 | } |
343 | | |
344 | 0 | int nNewBlockOffset = m_poBlockManagerRef->AllocNewBlock("TOOL"); |
345 | 0 | SetNextToolBlock(nNewBlockOffset); |
346 | |
|
347 | 0 | if (CommitToFile() != 0 || |
348 | 0 | InitNewBlock(m_fp, m_nBlockSize, nNewBlockOffset) != 0) |
349 | 0 | { |
350 | | // An error message should have already been reported. |
351 | 0 | return -1; |
352 | 0 | } |
353 | | |
354 | 0 | m_numBlocksInChain++; |
355 | 0 | } |
356 | | |
357 | 5.48k | return TABRawBinBlock::WriteBytes(nBytesToWrite, pabySrcBuf); |
358 | 5.48k | } |
359 | | |
360 | | /********************************************************************** |
361 | | * TABMAPToolBlock::CheckAvailableSpace() |
362 | | * |
363 | | * Check if an object of the specified type can fit in |
364 | | * current block. If it can't fit then force committing current block |
365 | | * and allocating a new one. |
366 | | * |
367 | | * Returns 0 if successful or -1 if an error happened, in which case |
368 | | * CPLError() will have been called. |
369 | | **********************************************************************/ |
370 | | int TABMAPToolBlock::CheckAvailableSpace(int nToolType) |
371 | 505 | { |
372 | 505 | int nBytesToWrite = 0; |
373 | | |
374 | 505 | switch (nToolType) |
375 | 505 | { |
376 | 174 | case TABMAP_TOOL_PEN: |
377 | 174 | nBytesToWrite = 11; |
378 | 174 | break; |
379 | 154 | case TABMAP_TOOL_BRUSH: |
380 | 154 | nBytesToWrite = 13; |
381 | 154 | break; |
382 | 0 | case TABMAP_TOOL_FONT: |
383 | 0 | nBytesToWrite = 37; |
384 | 0 | break; |
385 | 177 | case TABMAP_TOOL_SYMBOL: |
386 | 177 | nBytesToWrite = 13; |
387 | 177 | break; |
388 | 0 | default: |
389 | 0 | CPLAssert(false); |
390 | 505 | } |
391 | | |
392 | 505 | if (GetNumUnusedBytes() < nBytesToWrite) |
393 | 0 | { |
394 | 0 | if (m_numBlocksInChain >= 255) |
395 | 0 | { |
396 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
397 | 0 | "Maximum number of 255 tool blocks reached"); |
398 | 0 | return -1; |
399 | 0 | } |
400 | 0 | int nNewBlockOffset = m_poBlockManagerRef->AllocNewBlock("TOOL"); |
401 | 0 | SetNextToolBlock(nNewBlockOffset); |
402 | |
|
403 | 0 | if (CommitToFile() != 0 || |
404 | 0 | InitNewBlock(m_fp, m_nBlockSize, nNewBlockOffset) != 0) |
405 | 0 | { |
406 | | // An error message should have already been reported. |
407 | 0 | return -1; |
408 | 0 | } |
409 | | |
410 | 0 | m_numBlocksInChain++; |
411 | 0 | } |
412 | | |
413 | 505 | return 0; |
414 | 505 | } |
415 | | |
416 | | /********************************************************************** |
417 | | * TABMAPToolBlock::Dump() |
418 | | * |
419 | | * Dump block contents... available only in DEBUG mode. |
420 | | **********************************************************************/ |
421 | | #ifdef DEBUG |
422 | | |
423 | | void TABMAPToolBlock::Dump(FILE *fpOut /*=NULL*/) |
424 | | { |
425 | | if (fpOut == nullptr) |
426 | | fpOut = stdout; |
427 | | |
428 | | fprintf(fpOut, "----- TABMAPToolBlock::Dump() -----\n"); |
429 | | if (m_pabyBuf == nullptr) |
430 | | { |
431 | | fprintf(fpOut, "Block has not been initialized yet."); |
432 | | } |
433 | | else |
434 | | { |
435 | | fprintf(fpOut, "Tool Block (type %d) at offset %d.\n", m_nBlockType, |
436 | | m_nFileOffset); |
437 | | fprintf(fpOut, " m_numDataBytes = %d\n", m_numDataBytes); |
438 | | fprintf(fpOut, " m_nNextToolBlock = %d\n", m_nNextToolBlock); |
439 | | } |
440 | | |
441 | | fflush(fpOut); |
442 | | } |
443 | | |
444 | | #endif // DEBUG |