Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | #include "config.h" |
4 | | |
5 | | // included header files |
6 | | #include "value.hpp" |
7 | | |
8 | | #include "convert.hpp" |
9 | | #include "enforce.hpp" |
10 | | #include "error.hpp" |
11 | | #include "types.hpp" |
12 | | |
13 | | #include "image_int.hpp" |
14 | | |
15 | | // + standard includes |
16 | | #include <iterator> |
17 | | #include <sstream> |
18 | | |
19 | | // ***************************************************************************** |
20 | | // class member definitions |
21 | | namespace Exiv2 { |
22 | 6.16M | Value::Value(TypeId typeId) : type_(typeId) { |
23 | 6.16M | } |
24 | | |
25 | 4.53M | Value::UniquePtr Value::create(TypeId typeId) { |
26 | 4.53M | switch (typeId) { |
27 | 0 | case invalidTypeId: |
28 | 2.22k | case signedByte: |
29 | 3.14M | case unsignedByte: |
30 | 3.14M | return std::make_unique<DataValue>(typeId); |
31 | 43.9k | case asciiString: |
32 | 43.9k | return std::make_unique<AsciiValue>(); |
33 | 211k | case unsignedShort: |
34 | 211k | return std::make_unique<ValueType<uint16_t>>(); |
35 | 28.3k | case unsignedLong: |
36 | 32.2k | case tiffIfd: |
37 | 32.2k | return std::make_unique<ValueType<uint32_t>>(typeId); |
38 | 17.6k | case unsignedRational: |
39 | 17.6k | return std::make_unique<ValueType<URational>>(); |
40 | 48.1k | case undefined: |
41 | 48.1k | return std::make_unique<DataValue>(); |
42 | 275k | case signedShort: |
43 | 275k | return std::make_unique<ValueType<int16_t>>(); |
44 | 28.0k | case signedLong: |
45 | 28.0k | return std::make_unique<ValueType<int32_t>>(); |
46 | 7.86k | case signedRational: |
47 | 7.86k | return std::make_unique<ValueType<Rational>>(); |
48 | 5.73k | case tiffFloat: |
49 | 5.73k | return std::make_unique<ValueType<float>>(); |
50 | 11.5k | case tiffDouble: |
51 | 11.5k | return std::make_unique<ValueType<double>>(); |
52 | 23.0k | case string: |
53 | 23.0k | return std::make_unique<StringValue>(); |
54 | 2.28k | case date: |
55 | 2.28k | return std::make_unique<DateValue>(); |
56 | 6.59k | case time: |
57 | 6.59k | return std::make_unique<TimeValue>(); |
58 | 6.37k | case comment: |
59 | 6.37k | return std::make_unique<CommentValue>(); |
60 | 41.2k | case xmpText: |
61 | 41.2k | return std::make_unique<XmpTextValue>(); |
62 | 0 | case xmpBag: |
63 | 3.08k | case xmpSeq: |
64 | 3.08k | case xmpAlt: |
65 | 3.08k | return std::make_unique<XmpArrayValue>(typeId); |
66 | 0 | case langAlt: |
67 | 0 | return std::make_unique<LangAltValue>(); |
68 | 624k | default: |
69 | 624k | return std::make_unique<DataValue>(typeId); |
70 | 4.53M | } |
71 | 4.53M | } // Value::create |
72 | | |
73 | 326 | int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) { |
74 | 326 | return -1; |
75 | 326 | } |
76 | | |
77 | 182k | std::string Value::toString() const { |
78 | 182k | std::ostringstream os; |
79 | 182k | write(os); |
80 | 182k | ok_ = !os.fail(); |
81 | 182k | return os.str(); |
82 | 182k | } |
83 | | |
84 | 149k | std::string Value::toString(size_t /*n*/) const { |
85 | 149k | return toString(); |
86 | 149k | } |
87 | | |
88 | 19.8k | size_t Value::sizeDataArea() const { |
89 | 19.8k | return 0; |
90 | 19.8k | } |
91 | | |
92 | 1.31k | DataBuf Value::dataArea() const { |
93 | 1.31k | return {nullptr, 0}; |
94 | 1.31k | } |
95 | | |
96 | 3.81M | DataValue::DataValue(TypeId typeId) : Value(typeId) { |
97 | 3.81M | } |
98 | | |
99 | 16 | DataValue::DataValue(const byte* buf, size_t len, ByteOrder byteOrder, TypeId typeId) : Value(typeId) { |
100 | 16 | read(buf, len, byteOrder); |
101 | 16 | } |
102 | | |
103 | 8.33M | size_t DataValue::count() const { |
104 | 8.33M | return size(); |
105 | 8.33M | } |
106 | | |
107 | 3.81M | int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
108 | | // byteOrder not needed |
109 | 3.81M | value_.assign(buf, buf + len); |
110 | 3.81M | return 0; |
111 | 3.81M | } |
112 | | |
113 | 0 | int DataValue::read(const std::string& buf) { |
114 | 0 | std::istringstream is(buf); |
115 | 0 | int tmp = 0; |
116 | 0 | ValueType val; |
117 | 0 | while (is >> tmp) |
118 | 0 | val.push_back(tmp); |
119 | 0 | if (!is.eof()) |
120 | 0 | return 1; |
121 | 0 | value_ = std::move(val); |
122 | 0 | return 0; |
123 | 0 | } |
124 | | |
125 | 3.49M | size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
126 | | // byteOrder not needed |
127 | 3.49M | return std::copy(value_.begin(), value_.end(), buf) - buf; |
128 | 3.49M | } |
129 | | |
130 | 22.8M | size_t DataValue::size() const { |
131 | 22.8M | return value_.size(); |
132 | 22.8M | } |
133 | | |
134 | 9.78M | DataValue* DataValue::clone_() const { |
135 | 9.78M | return new DataValue(*this); |
136 | 9.78M | } |
137 | | |
138 | 1.30k | std::ostream& DataValue::write(std::ostream& os) const { |
139 | 1.30k | if (!value_.empty()) { |
140 | 825 | std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<int>(os, " ")); |
141 | 825 | os << static_cast<int>(value_.back()); |
142 | 825 | } |
143 | 1.30k | return os; |
144 | 1.30k | } |
145 | | |
146 | 0 | std::string DataValue::toString(size_t n) const { |
147 | 0 | ok_ = true; |
148 | 0 | return std::to_string(value_.at(n)); |
149 | 0 | } |
150 | | |
151 | 8.99k | int64_t DataValue::toInt64(size_t n) const { |
152 | 8.99k | ok_ = true; |
153 | 8.99k | return value_.at(n); |
154 | 8.99k | } |
155 | | |
156 | 1.87M | uint32_t DataValue::toUint32(size_t n) const { |
157 | 1.87M | ok_ = true; |
158 | 1.87M | return value_.at(n); |
159 | 1.87M | } |
160 | | |
161 | 0 | float DataValue::toFloat(size_t n) const { |
162 | 0 | ok_ = true; |
163 | 0 | return value_.at(n); |
164 | 0 | } |
165 | | |
166 | 0 | Rational DataValue::toRational(size_t n) const { |
167 | 0 | ok_ = true; |
168 | 0 | return {value_.at(n), 1}; |
169 | 0 | } |
170 | | |
171 | 0 | StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) { |
172 | 0 | read(buf); |
173 | 0 | } |
174 | | |
175 | 5.48k | int StringValueBase::read(const std::string& buf) { |
176 | 5.48k | value_ = buf; |
177 | 5.48k | return 0; |
178 | 5.48k | } |
179 | | |
180 | 66.0k | int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
181 | | // byteOrder not needed |
182 | 66.0k | if (buf) |
183 | 66.0k | value_ = std::string(reinterpret_cast<const char*>(buf), len); |
184 | 66.0k | return 0; |
185 | 66.0k | } |
186 | | |
187 | 57.4k | size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
188 | 57.4k | if (value_.empty()) |
189 | 30.5k | return 0; |
190 | | // byteOrder not needed |
191 | 26.8k | return value_.copy(reinterpret_cast<char*>(buf), value_.size()); |
192 | 57.4k | } |
193 | | |
194 | 122k | size_t StringValueBase::count() const { |
195 | 122k | return size(); |
196 | 122k | } |
197 | | |
198 | 262k | size_t StringValueBase::size() const { |
199 | 262k | return value_.size(); |
200 | 262k | } |
201 | | |
202 | 2.55k | std::ostream& StringValueBase::write(std::ostream& os) const { |
203 | 2.55k | return os << value_; |
204 | 2.55k | } |
205 | | |
206 | 11.3k | int64_t StringValueBase::toInt64(size_t n) const { |
207 | 11.3k | ok_ = true; |
208 | 11.3k | return value_.at(n); |
209 | 11.3k | } |
210 | | |
211 | 64.4k | uint32_t StringValueBase::toUint32(size_t n) const { |
212 | 64.4k | ok_ = true; |
213 | 64.4k | return value_.at(n); |
214 | 64.4k | } |
215 | | |
216 | 0 | float StringValueBase::toFloat(size_t n) const { |
217 | 0 | ok_ = true; |
218 | 0 | return value_.at(n); |
219 | 0 | } |
220 | | |
221 | 0 | Rational StringValueBase::toRational(size_t n) const { |
222 | 0 | ok_ = true; |
223 | 0 | return {value_.at(n), 1}; |
224 | 0 | } |
225 | | |
226 | 23.0k | StringValue::StringValue() : StringValueBase(string) { |
227 | 23.0k | } |
228 | | |
229 | 0 | StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) { |
230 | 0 | } |
231 | | |
232 | 222k | StringValue* StringValue::clone_() const { |
233 | 222k | return new StringValue(*this); |
234 | 222k | } |
235 | | |
236 | 43.9k | AsciiValue::AsciiValue() : StringValueBase(asciiString) { |
237 | 43.9k | } |
238 | | |
239 | 0 | AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) { |
240 | 0 | } |
241 | | |
242 | 3.21k | int AsciiValue::read(const std::string& buf) { |
243 | 3.21k | value_ = buf; |
244 | | // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484 |
245 | 3.21k | if (value_.empty() || value_.back() != '\0') { |
246 | 3.21k | value_ += '\0'; |
247 | 3.21k | } |
248 | 3.21k | return 0; |
249 | 3.21k | } |
250 | | |
251 | 115k | AsciiValue* AsciiValue::clone_() const { |
252 | 115k | return new AsciiValue(*this); |
253 | 115k | } |
254 | | |
255 | 17.2k | std::ostream& AsciiValue::write(std::ostream& os) const { |
256 | | // Write only up to the first '\0' (if any) |
257 | 17.2k | std::string::size_type pos = value_.find_first_of('\0'); |
258 | 17.2k | if (pos == std::string::npos) |
259 | 3.56k | pos = value_.size(); |
260 | 17.2k | return os << value_.substr(0, pos); |
261 | 17.2k | } |
262 | | |
263 | | //! Lookup list of supported IFD type information |
264 | | constexpr CommentValue::CharsetTable CommentValue::CharsetInfo::charsetTable_[] = { |
265 | | {ascii, "Ascii", "ASCII\0\0\0"}, |
266 | | {jis, "Jis", "JIS\0\0\0\0\0"}, |
267 | | {unicode, "Unicode", "UNICODE\0"}, |
268 | | {undefined, "Undefined", "\0\0\0\0\0\0\0\0"}, |
269 | | {invalidCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"}, |
270 | | {lastCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"}, |
271 | | }; |
272 | | |
273 | 0 | const char* CommentValue::CharsetInfo::name(CharsetId charsetId) { |
274 | 0 | return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_; |
275 | 0 | } |
276 | | |
277 | 0 | const char* CommentValue::CharsetInfo::code(CharsetId charsetId) { |
278 | 0 | return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].code_; |
279 | 0 | } |
280 | | |
281 | 0 | CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByName(const std::string& name) { |
282 | 0 | int i = 0; |
283 | 0 | for (; charsetTable_[i].charsetId_ != lastCharsetId && charsetTable_[i].name_ != name; ++i) { |
284 | 0 | } |
285 | 0 | return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; |
286 | 0 | } |
287 | | |
288 | 3.73k | CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) { |
289 | 3.73k | int i = 0; |
290 | 13.3k | for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) { |
291 | 9.63k | } |
292 | 3.73k | return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; |
293 | 3.73k | } |
294 | | |
295 | 6.37k | CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) { |
296 | 6.37k | } |
297 | | |
298 | 0 | CommentValue::CommentValue(const std::string& comment) : StringValueBase(Exiv2::undefined) { |
299 | 0 | read(comment); |
300 | 0 | } |
301 | | |
302 | 0 | int CommentValue::read(const std::string& comment) { |
303 | 0 | std::string c = comment; |
304 | 0 | CharsetId charsetId = undefined; |
305 | 0 | if (comment.starts_with("charset=")) { |
306 | 0 | const std::string::size_type pos = comment.find_first_of(' '); |
307 | 0 | std::string name = comment.substr(8, pos - 8); |
308 | | // Strip quotes (so you can also specify the charset without quotes) |
309 | 0 | if (!name.empty() && name.front() == '"') |
310 | 0 | name = name.substr(1); |
311 | 0 | if (!name.empty() && name.back() == '"') |
312 | 0 | name.pop_back(); |
313 | 0 | charsetId = CharsetInfo::charsetIdByName(name); |
314 | 0 | if (charsetId == invalidCharsetId) { |
315 | 0 | #ifndef SUPPRESS_WARNINGS |
316 | 0 | EXV_WARNING << Error(ErrorCode::kerInvalidCharset, name) << "\n"; |
317 | 0 | #endif |
318 | 0 | return 1; |
319 | 0 | } |
320 | 0 | c.clear(); |
321 | 0 | if (pos != std::string::npos) |
322 | 0 | c = comment.substr(pos + 1); |
323 | 0 | } |
324 | 0 | if (charsetId == unicode) { |
325 | 0 | const char* to = byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; |
326 | 0 | convertStringCharset(c, "UTF-8", to); |
327 | 0 | } |
328 | 0 | const std::string code(CharsetInfo::code(charsetId), 8); |
329 | 0 | return StringValueBase::read(code + c); |
330 | 0 | } |
331 | | |
332 | 6.37k | int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) { |
333 | 6.37k | byteOrder_ = byteOrder; |
334 | 6.37k | return StringValueBase::read(buf, len, byteOrder); |
335 | 6.37k | } |
336 | | |
337 | 6.23k | size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const { |
338 | 6.23k | std::string c = value_; |
339 | 6.23k | if (charsetId() == unicode) { |
340 | 1.45k | c = value_.substr(8); |
341 | 1.45k | [[maybe_unused]] const size_t sz = c.size(); |
342 | 1.45k | if (byteOrder_ == littleEndian && byteOrder == bigEndian) { |
343 | 0 | convertStringCharset(c, "UCS-2LE", "UCS-2BE"); |
344 | 1.45k | } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) { |
345 | 122 | convertStringCharset(c, "UCS-2BE", "UCS-2LE"); |
346 | 122 | } |
347 | 1.45k | c = value_.substr(0, 8) + c; |
348 | 1.45k | } |
349 | 6.23k | if (c.empty()) |
350 | 2.12k | return 0; |
351 | 4.11k | return c.copy(reinterpret_cast<char*>(buf), c.size()); |
352 | 6.23k | } |
353 | | |
354 | 0 | std::ostream& CommentValue::write(std::ostream& os) const { |
355 | 0 | CharsetId csId = charsetId(); |
356 | 0 | std::string text = comment(); |
357 | 0 | if (csId != undefined) { |
358 | 0 | os << "charset=" << CharsetInfo::name(csId) << " "; |
359 | 0 | } |
360 | 0 | return os << text; |
361 | 0 | } |
362 | | |
363 | 0 | std::string CommentValue::comment(const char* encoding) const { |
364 | 0 | std::string c; |
365 | 0 | if (value_.length() < 8) { |
366 | 0 | return c; |
367 | 0 | } |
368 | 0 | c = value_.substr(8); |
369 | 0 | if (charsetId() == unicode) { |
370 | 0 | const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding; |
371 | 0 | if (!convertStringCharset(c, from, "UTF-8")) |
372 | 0 | throw Error(ErrorCode::kerInvalidIconvEncoding, from, "UTF-8"); |
373 | 0 | } |
374 | | |
375 | | // # 1266 Remove trailing nulls |
376 | 0 | if (charsetId() == undefined || charsetId() == ascii) { |
377 | 0 | auto n = c.find('\0'); |
378 | 0 | if (n != std::string::npos) |
379 | 0 | c.resize(n); |
380 | 0 | } |
381 | 0 | return c; |
382 | 0 | } |
383 | | |
384 | 6.23k | CommentValue::CharsetId CommentValue::charsetId() const { |
385 | 6.23k | CharsetId charsetId = undefined; |
386 | 6.23k | if (value_.length() >= 8) { |
387 | 3.73k | const std::string code = value_.substr(0, 8); |
388 | 3.73k | charsetId = CharsetInfo::charsetIdByCode(code); |
389 | 3.73k | } |
390 | 6.23k | return charsetId; |
391 | 6.23k | } |
392 | | |
393 | 0 | const char* CommentValue::detectCharset(std::string& c) const { |
394 | | // Interpret a BOM if there is one |
395 | 0 | if (c.front() == '\xef' && c[1] == '\xbb' && c[2] == '\xbf') { |
396 | 0 | c = c.substr(3); |
397 | 0 | return "UTF-8"; |
398 | 0 | } |
399 | 0 | if (c.front() == '\xff' && c[1] == '\xfe') { |
400 | 0 | c = c.substr(2); |
401 | 0 | return "UCS-2LE"; |
402 | 0 | } |
403 | 0 | if (c.front() == '\xfe' && c[1] == '\xff') { |
404 | 0 | c = c.substr(2); |
405 | 0 | return "UCS-2BE"; |
406 | 0 | } |
407 | | |
408 | | // Todo: Add logic to guess if the comment is encoded in UTF-8 |
409 | | |
410 | 0 | return byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; |
411 | 0 | } |
412 | | |
413 | 17.5k | CommentValue* CommentValue::clone_() const { |
414 | 17.5k | return new CommentValue(*this); |
415 | 17.5k | } |
416 | | |
417 | 20.5k | void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) { |
418 | 20.5k | xmpArrayType_ = xmpArrayType; |
419 | 20.5k | } |
420 | | |
421 | 12.1k | void XmpValue::setXmpStruct(XmpStruct xmpStruct) { |
422 | 12.1k | xmpStruct_ = xmpStruct; |
423 | 12.1k | } |
424 | | |
425 | 244k | XmpValue::XmpArrayType XmpValue::xmpArrayType() const { |
426 | 244k | return xmpArrayType_; |
427 | 244k | } |
428 | | |
429 | 20.2k | XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) { |
430 | 20.2k | XmpArrayType xa = xaNone; |
431 | 20.2k | switch (typeId) { |
432 | 972 | case xmpAlt: |
433 | 972 | xa = xaAlt; |
434 | 972 | break; |
435 | 343 | case xmpBag: |
436 | 343 | xa = xaBag; |
437 | 343 | break; |
438 | 7.52k | case xmpSeq: |
439 | 7.52k | xa = xaSeq; |
440 | 7.52k | break; |
441 | 11.4k | default: |
442 | 11.4k | break; |
443 | 20.2k | } |
444 | 20.2k | return xa; |
445 | 20.2k | } |
446 | | |
447 | 244k | XmpValue::XmpStruct XmpValue::xmpStruct() const { |
448 | 244k | return xmpStruct_; |
449 | 244k | } |
450 | | |
451 | 0 | size_t XmpValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
452 | 0 | std::ostringstream os; |
453 | 0 | write(os); |
454 | 0 | std::string s = os.str(); |
455 | 0 | if (!s.empty()) |
456 | 0 | std::copy(s.begin(), s.end(), buf); |
457 | 0 | return s.size(); |
458 | 0 | } |
459 | | |
460 | 0 | int XmpValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
461 | 0 | std::string s(reinterpret_cast<const char*>(buf), len); |
462 | 0 | return read(s); |
463 | 0 | } |
464 | | |
465 | 0 | size_t XmpValue::size() const { |
466 | 0 | std::ostringstream os; |
467 | 0 | write(os); |
468 | 0 | return os.str().size(); |
469 | 0 | } |
470 | | |
471 | 135k | XmpTextValue::XmpTextValue() : XmpValue(xmpText) { |
472 | 135k | } |
473 | | |
474 | 0 | XmpTextValue::XmpTextValue(const std::string& buf) : XmpValue(xmpText) { |
475 | 0 | read(buf); |
476 | 0 | } |
477 | | |
478 | 837k | int XmpTextValue::read(const std::string& buf) { |
479 | | // support a type=Alt,Bag,Seq,Struct indicator |
480 | 837k | std::string b = buf; |
481 | 837k | std::string type; |
482 | 837k | if (buf.starts_with("type=")) { |
483 | 964 | std::string::size_type pos = buf.find_first_of(' '); |
484 | 964 | type = buf.substr(5, pos - 5); |
485 | | // Strip quotes (so you can also specify the type without quotes) |
486 | 964 | if (!type.empty() && type.front() == '"') |
487 | 690 | type = type.substr(1); |
488 | 964 | if (!type.empty() && type.back() == '"') |
489 | 440 | type.pop_back(); |
490 | 964 | b.clear(); |
491 | 964 | if (pos != std::string::npos) |
492 | 457 | b = buf.substr(pos + 1); |
493 | 964 | } |
494 | 837k | if (!type.empty()) { |
495 | 890 | if (type == "Alt") { |
496 | 161 | setXmpArrayType(XmpValue::xaAlt); |
497 | 729 | } else if (type == "Bag") { |
498 | 14 | setXmpArrayType(XmpValue::xaBag); |
499 | 715 | } else if (type == "Seq") { |
500 | 138 | setXmpArrayType(XmpValue::xaSeq); |
501 | 577 | } else if (type == "Struct") { |
502 | 312 | setXmpStruct(); |
503 | 312 | } else { |
504 | 265 | throw Error(ErrorCode::kerInvalidXmpText, type); |
505 | 265 | } |
506 | 890 | } |
507 | 836k | value_ = std::move(b); |
508 | 836k | return 0; |
509 | 837k | } |
510 | | |
511 | 0 | XmpTextValue::UniquePtr XmpTextValue::clone() const { |
512 | 0 | return UniquePtr(clone_()); |
513 | 0 | } |
514 | | |
515 | 87.2k | size_t XmpTextValue::size() const { |
516 | 87.2k | return value_.size(); |
517 | 87.2k | } |
518 | | |
519 | 87.2k | size_t XmpTextValue::count() const { |
520 | 87.2k | return size(); |
521 | 87.2k | } |
522 | | |
523 | 152k | std::ostream& XmpTextValue::write(std::ostream& os) const { |
524 | 152k | bool del = false; |
525 | 152k | if (xmpArrayType() != XmpValue::xaNone) { |
526 | 49 | switch (xmpArrayType()) { |
527 | 39 | case XmpValue::xaAlt: |
528 | 39 | os << "type=\"Alt\""; |
529 | 39 | break; |
530 | 2 | case XmpValue::xaBag: |
531 | 2 | os << "type=\"Bag\""; |
532 | 2 | break; |
533 | 8 | case XmpValue::xaSeq: |
534 | 8 | os << "type=\"Seq\""; |
535 | 8 | break; |
536 | 0 | case XmpValue::xaNone: |
537 | 0 | break; // just to suppress the warning |
538 | 49 | } |
539 | 49 | del = true; |
540 | 152k | } else if (xmpStruct() != XmpValue::xsNone) { |
541 | 55 | switch (xmpStruct()) { |
542 | 55 | case XmpValue::xsStruct: |
543 | 55 | os << "type=\"Struct\""; |
544 | 55 | break; |
545 | 0 | case XmpValue::xsNone: |
546 | 0 | break; // just to suppress the warning |
547 | 55 | } |
548 | 55 | del = true; |
549 | 55 | } |
550 | 152k | if (del && !value_.empty()) |
551 | 50 | os << " "; |
552 | 152k | return os << value_; |
553 | 152k | } |
554 | | |
555 | 0 | int64_t XmpTextValue::toInt64(size_t /*n*/) const { |
556 | 0 | return parseInt64(value_, ok_); |
557 | 0 | } |
558 | | |
559 | 0 | uint32_t XmpTextValue::toUint32(size_t /*n*/) const { |
560 | 0 | return parseUint32(value_, ok_); |
561 | 0 | } |
562 | | |
563 | 0 | float XmpTextValue::toFloat(size_t /*n*/) const { |
564 | 0 | return parseFloat(value_, ok_); |
565 | 0 | } |
566 | | |
567 | 0 | Rational XmpTextValue::toRational(size_t /*n*/) const { |
568 | 0 | return parseRational(value_, ok_); |
569 | 0 | } |
570 | | |
571 | 439k | XmpTextValue* XmpTextValue::clone_() const { |
572 | 439k | return new XmpTextValue(*this); |
573 | 439k | } |
574 | | |
575 | 8.37k | XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) { |
576 | 8.37k | setXmpArrayType(xmpArrayType(typeId)); |
577 | 8.37k | } |
578 | | |
579 | 53.4k | int XmpArrayValue::read(const std::string& buf) { |
580 | 53.4k | if (!buf.empty()) |
581 | 43.1k | value_.push_back(buf); |
582 | 53.4k | return 0; |
583 | 53.4k | } |
584 | | |
585 | 0 | XmpArrayValue::UniquePtr XmpArrayValue::clone() const { |
586 | 0 | return UniquePtr(clone_()); |
587 | 0 | } |
588 | | |
589 | 28.3k | size_t XmpArrayValue::count() const { |
590 | 28.3k | return value_.size(); |
591 | 28.3k | } |
592 | | |
593 | 331 | std::ostream& XmpArrayValue::write(std::ostream& os) const { |
594 | 331 | if (!value_.empty()) { |
595 | 319 | std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<std::string>(os, ", ")); |
596 | 319 | os << value_.back(); |
597 | 319 | } |
598 | 331 | return os; |
599 | 331 | } |
600 | | |
601 | 51.9k | std::string XmpArrayValue::toString(size_t n) const { |
602 | 51.9k | ok_ = true; |
603 | 51.9k | return value_.at(n); |
604 | 51.9k | } |
605 | | |
606 | 0 | int64_t XmpArrayValue::toInt64(size_t n) const { |
607 | 0 | return parseInt64(value_.at(n), ok_); |
608 | 0 | } |
609 | | |
610 | 0 | uint32_t XmpArrayValue::toUint32(size_t n) const { |
611 | 0 | return parseUint32(value_.at(n), ok_); |
612 | 0 | } |
613 | | |
614 | 0 | float XmpArrayValue::toFloat(size_t n) const { |
615 | 0 | return parseFloat(value_.at(n), ok_); |
616 | 0 | } |
617 | | |
618 | 0 | Rational XmpArrayValue::toRational(size_t n) const { |
619 | 0 | return parseRational(value_.at(n), ok_); |
620 | 0 | } |
621 | | |
622 | 24.8k | XmpArrayValue* XmpArrayValue::clone_() const { |
623 | 24.8k | return new XmpArrayValue(*this); |
624 | 24.8k | } |
625 | | |
626 | 1.32k | LangAltValue::LangAltValue() : XmpValue(langAlt) { |
627 | 1.32k | } |
628 | | |
629 | 0 | LangAltValue::LangAltValue(const std::string& buf) : XmpValue(langAlt) { |
630 | 0 | read(buf); |
631 | 0 | } |
632 | | |
633 | 0 | int LangAltValue::read(const std::string& buf) { |
634 | 0 | std::string b = buf; |
635 | 0 | std::string lang = "x-default"; |
636 | 0 | if (buf.starts_with("lang=")) { |
637 | 0 | static constexpr auto ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
638 | |
|
639 | 0 | const std::string::size_type pos = buf.find_first_of(' '); |
640 | 0 | if (pos == std::string::npos) { |
641 | 0 | lang = buf.substr(5); |
642 | 0 | } else { |
643 | 0 | lang = buf.substr(5, pos - 5); |
644 | 0 | } |
645 | 0 | if (lang.empty()) |
646 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
647 | | // Strip quotes (so you can also specify the language without quotes) |
648 | 0 | if (lang.front() == '"') { |
649 | 0 | lang = lang.substr(1); |
650 | |
|
651 | 0 | if (lang.empty() || lang.back() != '"') |
652 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
653 | | |
654 | 0 | lang.pop_back(); |
655 | 0 | } |
656 | | |
657 | 0 | if (lang.empty()) |
658 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
659 | | |
660 | | // Check language is in the correct format (see https://www.ietf.org/rfc/rfc3066.txt) |
661 | 0 | if (auto charPos = lang.find_first_not_of(ALPHA); charPos != std::string::npos) { |
662 | 0 | static constexpr auto ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
663 | 0 | if (lang.at(charPos) != '-' || lang.find_first_not_of(ALPHA_NUM, charPos + 1) != std::string::npos) |
664 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
665 | 0 | } |
666 | | |
667 | 0 | b.clear(); |
668 | 0 | if (pos != std::string::npos) |
669 | 0 | b = buf.substr(pos + 1); |
670 | 0 | } |
671 | | |
672 | 0 | value_[lang] = std::move(b); |
673 | 0 | return 0; |
674 | 0 | } |
675 | | |
676 | 0 | LangAltValue::UniquePtr LangAltValue::clone() const { |
677 | 0 | return UniquePtr(clone_()); |
678 | 0 | } |
679 | | |
680 | 360 | size_t LangAltValue::count() const { |
681 | 360 | return value_.size(); |
682 | 360 | } |
683 | | |
684 | 189 | std::ostream& LangAltValue::write(std::ostream& os) const { |
685 | 189 | bool first = true; |
686 | | |
687 | | // Write the default entry first |
688 | 189 | if (auto i = value_.find("x-default"); i != value_.end()) { |
689 | 0 | os << "lang=\"" << i->first << "\" " << i->second; |
690 | 0 | first = false; |
691 | 0 | } |
692 | | |
693 | | // Write the others |
694 | 189 | for (const auto& [lang, s] : value_) { |
695 | 189 | if (lang != "x-default") { |
696 | 189 | if (!first) |
697 | 0 | os << ", "; |
698 | 189 | os << "lang=\"" << lang << "\" " << s; |
699 | 189 | first = false; |
700 | 189 | } |
701 | 189 | } |
702 | 189 | return os; |
703 | 189 | } |
704 | | |
705 | 447 | std::string LangAltValue::toString(size_t /*n*/) const { |
706 | 447 | return toString("x-default"); |
707 | 447 | } |
708 | | |
709 | 447 | std::string LangAltValue::toString(const std::string& qualifier) const { |
710 | 447 | if (auto i = value_.find(qualifier); i != value_.end()) { |
711 | 87 | ok_ = true; |
712 | 87 | return i->second; |
713 | 87 | } |
714 | 360 | ok_ = false; |
715 | 360 | return ""; |
716 | 447 | } |
717 | | |
718 | 0 | int64_t LangAltValue::toInt64(size_t /*n*/) const { |
719 | 0 | ok_ = false; |
720 | 0 | return 0; |
721 | 0 | } |
722 | | |
723 | 0 | uint32_t LangAltValue::toUint32(size_t /*n*/) const { |
724 | 0 | ok_ = false; |
725 | 0 | return 0; |
726 | 0 | } |
727 | | |
728 | 0 | float LangAltValue::toFloat(size_t /*n*/) const { |
729 | 0 | ok_ = false; |
730 | 0 | return 0.0F; |
731 | 0 | } |
732 | | |
733 | 0 | Rational LangAltValue::toRational(size_t /*n*/) const { |
734 | 0 | ok_ = false; |
735 | 0 | return {0, 0}; |
736 | 0 | } |
737 | | |
738 | 4.49k | LangAltValue* LangAltValue::clone_() const { |
739 | 4.49k | return new LangAltValue(*this); |
740 | 4.49k | } |
741 | | |
742 | 2.28k | DateValue::DateValue() : Value(date) { |
743 | 2.28k | date_ = {}; |
744 | 2.28k | } |
745 | | |
746 | 0 | DateValue::DateValue(int32_t year, int32_t month, int32_t day) : Value(date) { |
747 | 0 | date_ = {year, month, day}; |
748 | 0 | } |
749 | | |
750 | 1.94k | int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
751 | 1.94k | const std::string str(reinterpret_cast<const char*>(buf), len); |
752 | 1.94k | return read(str); |
753 | 1.94k | } |
754 | | |
755 | 2.28k | int DateValue::read(const std::string& buf) { |
756 | | // ISO 8601 date formats: |
757 | | // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf |
758 | 2.28k | size_t monthPos = 0; |
759 | 2.28k | size_t dayPos = 0; |
760 | | |
761 | 2.93k | auto printWarning = [] { |
762 | 2.93k | #ifndef SUPPRESS_WARNINGS |
763 | 2.93k | EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n"; |
764 | 2.93k | #endif |
765 | 2.93k | }; |
766 | | |
767 | 2.28k | if (buf.size() < 8) { |
768 | 455 | printWarning(); |
769 | 455 | return 1; |
770 | 455 | } |
771 | | |
772 | 1.82k | if ((buf.size() >= 10 && buf[4] == '-' && buf[7] == '-') || (buf.size() == 8)) { |
773 | 1.13k | if (buf.size() >= 10) { |
774 | 131 | monthPos = 5; |
775 | 131 | dayPos = 8; |
776 | 1.00k | } else { |
777 | 1.00k | monthPos = 4; |
778 | 1.00k | dayPos = 6; |
779 | 1.00k | } |
780 | | |
781 | 2.57k | auto checkDigits = [&buf, &printWarning](size_t start, size_t count, int32_t& dest) { |
782 | 8.49k | for (size_t i = start; i < start + count; ++i) { |
783 | 6.76k | if (!std::isdigit(buf[i])) { |
784 | 840 | printWarning(); |
785 | 840 | return 1; |
786 | 840 | } |
787 | 6.76k | } |
788 | 1.73k | dest = std::stoul(buf.substr(start, count)); |
789 | 1.73k | return 0; |
790 | 2.57k | }; |
791 | | |
792 | 1.13k | if (checkDigits(0, 4, date_.year) || checkDigits(monthPos, 2, date_.month) || checkDigits(dayPos, 2, date_.day)) { |
793 | 840 | printWarning(); |
794 | 840 | return 1; |
795 | 840 | } |
796 | | |
797 | 296 | if (date_.month > 12 || date_.day > 31) { |
798 | 107 | date_.month = 0; |
799 | 107 | date_.day = 0; |
800 | 107 | printWarning(); |
801 | 107 | return 1; |
802 | 107 | } |
803 | 189 | return 0; |
804 | 296 | } |
805 | 690 | printWarning(); |
806 | 690 | return 1; |
807 | 1.82k | } |
808 | | |
809 | 0 | void DateValue::setDate(const Date& src) { |
810 | 0 | date_ = src; |
811 | 0 | } |
812 | | |
813 | 76 | size_t DateValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
814 | | // \note Here the date is copied in the Basic format YYYYMMDD, as the IPTC key Iptc.Application2.DateCreated |
815 | | // wants it. Check https://exiv2.org/iptc.html |
816 | | |
817 | 76 | auto out = reinterpret_cast<char*>(buf); |
818 | 76 | auto it = stringFormatTo(out, "{:04}{:02}{:02}", date_.year, date_.month, date_.day); |
819 | | |
820 | 76 | return it - out; |
821 | 76 | } |
822 | | |
823 | 0 | const DateValue::Date& DateValue::getDate() const { |
824 | 0 | return date_; |
825 | 0 | } |
826 | | |
827 | 0 | size_t DateValue::count() const { |
828 | 0 | return size(); |
829 | 0 | } |
830 | | |
831 | 152 | size_t DateValue::size() const { |
832 | 152 | return 8; |
833 | 152 | } |
834 | | |
835 | 952 | DateValue* DateValue::clone_() const { |
836 | 952 | return new DateValue(*this); |
837 | 952 | } |
838 | | |
839 | 0 | std::ostream& DateValue::write(std::ostream& os) const { |
840 | | // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD |
841 | 0 | return os << stringFormat("{:04}-{:02}-{:02}", date_.year, date_.month, date_.day); |
842 | 0 | } |
843 | | |
844 | 0 | int64_t DateValue::toInt64(size_t /*n*/) const { |
845 | | // Range of tm struct is limited to about 1970 to 2038 |
846 | | // This will return -1 if outside that range |
847 | 0 | std::tm tms = {}; |
848 | 0 | tms.tm_mday = date_.day; |
849 | 0 | tms.tm_mon = date_.month - 1; |
850 | 0 | tms.tm_year = date_.year - 1900; |
851 | 0 | auto l = static_cast<int64_t>(std::mktime(&tms)); |
852 | 0 | ok_ = (l != -1); |
853 | 0 | return l; |
854 | 0 | } |
855 | | |
856 | 0 | uint32_t DateValue::toUint32(size_t /*n*/) const { |
857 | 0 | const int64_t t = toInt64(); |
858 | 0 | if (t < 0 || t > std::numeric_limits<uint32_t>::max()) { |
859 | 0 | ok_ = false; |
860 | 0 | return 0; |
861 | 0 | } |
862 | 0 | return static_cast<uint32_t>(t); |
863 | 0 | } |
864 | | |
865 | 0 | float DateValue::toFloat(size_t n) const { |
866 | 0 | return static_cast<float>(toInt64(n)); |
867 | 0 | } |
868 | | |
869 | 0 | Rational DateValue::toRational(size_t n) const { |
870 | 0 | const int64_t t = toInt64(n); |
871 | 0 | if (t < std::numeric_limits<int32_t>::min() || t > std::numeric_limits<int32_t>::max()) { |
872 | 0 | ok_ = false; |
873 | 0 | return {0, 1}; |
874 | 0 | } |
875 | 0 | return {static_cast<int32_t>(t), 1}; |
876 | 0 | } |
877 | | |
878 | 6.59k | TimeValue::TimeValue() : Value(time) { |
879 | 6.59k | time_ = {}; |
880 | 6.59k | } |
881 | | |
882 | 0 | TimeValue::TimeValue(int32_t hour, int32_t minute, int32_t second, int32_t tzHour, int32_t tzMinute) : Value(date) { |
883 | 0 | time_ = {hour, minute, second, tzHour, tzMinute}; |
884 | 0 | } |
885 | | |
886 | 6.59k | int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
887 | 6.59k | const std::string str(reinterpret_cast<const char*>(buf), len); |
888 | 6.59k | return read(str); |
889 | 6.59k | } |
890 | | |
891 | 6.59k | int TimeValue::read(const std::string& buf) { |
892 | | // ISO 8601 time formats: |
893 | | // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf |
894 | | // Not supported formats: |
895 | | // 4.2.2.4 Representations with decimal fraction: 232050,5 |
896 | 6.59k | auto printWarning = [] { |
897 | 4.91k | #ifndef SUPPRESS_WARNINGS |
898 | 4.91k | EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n"; |
899 | 4.91k | #endif |
900 | 4.91k | return 1; |
901 | 4.91k | }; |
902 | | |
903 | 6.59k | if (buf.size() < 2) |
904 | 1.44k | return printWarning(); |
905 | | |
906 | 5.14k | for (auto c : buf) |
907 | 64.2k | if (c != ':' && c != '+' && c != '-' && c != 'Z' && !std::isdigit(c)) |
908 | 1.55k | return printWarning(); |
909 | | |
910 | 3.59k | size_t mpos; |
911 | 3.59k | size_t spos; |
912 | 3.59k | if (buf.find(':') != std::string::npos) { |
913 | 1.36k | mpos = 3; |
914 | 1.36k | spos = 6; |
915 | 2.23k | } else { |
916 | 2.23k | mpos = 2; |
917 | 2.23k | spos = 4; |
918 | 2.23k | } |
919 | | |
920 | 3.59k | auto hi = std::stoi(buf.substr(0, 2)); |
921 | 3.59k | if (hi < 0 || hi > 23) |
922 | 206 | return printWarning(); |
923 | 3.38k | time_.hour = hi; |
924 | 3.38k | if (buf.size() > 3) { |
925 | 2.96k | auto mi = std::stoi(buf.substr(mpos, 2)); |
926 | 2.96k | if (mi < 0 || mi > 59) |
927 | 313 | return printWarning(); |
928 | 2.65k | time_.minute = std::stoi(buf.substr(mpos, 2)); |
929 | 2.65k | } else { |
930 | 420 | time_.minute = 0; |
931 | 420 | } |
932 | 3.07k | if (buf.size() > 5) { |
933 | 2.37k | auto si = std::stoi(buf.substr(spos, 2)); |
934 | 2.37k | if (si < 0 || si > 60) |
935 | 141 | return printWarning(); |
936 | 2.23k | time_.second = std::stoi(buf.substr(spos, 2)); |
937 | 2.23k | } else { |
938 | 697 | time_.second = 0; |
939 | 697 | } |
940 | | |
941 | 2.93k | auto fpos = buf.find('+'); |
942 | 2.93k | if (fpos == std::string::npos) |
943 | 2.05k | fpos = buf.find('-'); |
944 | | |
945 | 2.93k | if (fpos != std::string::npos) { |
946 | 2.21k | auto format = buf.substr(fpos, buf.size()); |
947 | 2.21k | auto posColon = format.find(':'); |
948 | 2.21k | if (posColon == std::string::npos) { |
949 | | // Extended format |
950 | 1.09k | auto tzhi = std::stoi(format.substr(0, 3)); |
951 | 1.09k | if (tzhi < -23 || tzhi > 23) |
952 | 101 | return printWarning(); |
953 | 995 | time_.tzHour = tzhi; |
954 | 995 | if (format.size() > 3) { |
955 | 549 | int minute = std::stoi(format.substr(3)); |
956 | 549 | if (minute < 0 || minute > 59) |
957 | 244 | return printWarning(); |
958 | 305 | time_.tzMinute = time_.tzHour < 0 ? -minute : minute; |
959 | 305 | } |
960 | 1.12k | } else { |
961 | | // Basic format |
962 | 1.12k | auto tzhi = std::stoi(format.substr(0, posColon)); |
963 | 1.12k | if (tzhi < -23 || tzhi > 23) |
964 | 678 | return printWarning(); |
965 | 444 | time_.tzHour = tzhi; |
966 | 444 | int minute = std::stoi(format.substr(posColon + 1)); |
967 | 444 | if (minute < 0 || minute > 59) |
968 | 236 | return printWarning(); |
969 | 208 | time_.tzMinute = time_.tzHour < 0 ? -minute : minute; |
970 | 208 | } |
971 | 2.21k | } |
972 | 1.67k | return 0; |
973 | 2.93k | } |
974 | | |
975 | | /// \todo not used internally. At least we should test it |
976 | 0 | void TimeValue::setTime(const Time& src) { |
977 | 0 | time_ = src; |
978 | 0 | } |
979 | | |
980 | 460 | size_t TimeValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
981 | | // NOTE: Here the time is copied in the Basic format HHMMSS:HHMM, as the IPTC key |
982 | | // Iptc.Application2.TimeCreated wants it. Check https://exiv2.org/iptc.html |
983 | 460 | char plusMinus = '+'; |
984 | 460 | if (time_.tzHour < 0 || time_.tzMinute < 0) |
985 | 123 | plusMinus = '-'; |
986 | | |
987 | 460 | auto out = reinterpret_cast<char*>(buf); |
988 | 460 | auto it = stringFormatTo(out, "{:02}{:02}{:02}{}{:02}{:02}", time_.hour, time_.minute, time_.second, plusMinus, |
989 | 460 | std::abs(time_.tzHour), std::abs(time_.tzMinute)); |
990 | | |
991 | 460 | auto wrote = static_cast<size_t>(it - out); |
992 | 460 | Internal::enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat); |
993 | 460 | return wrote; |
994 | 460 | } |
995 | | |
996 | 0 | const TimeValue::Time& TimeValue::getTime() const { |
997 | 0 | return time_; |
998 | 0 | } |
999 | | |
1000 | 0 | size_t TimeValue::count() const { |
1001 | 0 | return size(); |
1002 | 0 | } |
1003 | | |
1004 | 920 | size_t TimeValue::size() const { |
1005 | 920 | return 11; |
1006 | 920 | } |
1007 | | |
1008 | 3.08k | TimeValue* TimeValue::clone_() const { |
1009 | 3.08k | return new TimeValue(*this); |
1010 | 3.08k | } |
1011 | | |
1012 | 0 | std::ostream& TimeValue::write(std::ostream& os) const { |
1013 | | // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm |
1014 | 0 | char plusMinus = '+'; |
1015 | 0 | if (time_.tzHour < 0 || time_.tzMinute < 0) |
1016 | 0 | plusMinus = '-'; |
1017 | |
|
1018 | 0 | return os << stringFormat("{:02}:{:02}:{:02}{}{:02}:{:02}", time_.hour, time_.minute, time_.second, plusMinus, |
1019 | 0 | std::abs(time_.tzHour), std::abs(time_.tzMinute)); |
1020 | 0 | } |
1021 | | |
1022 | 0 | int64_t TimeValue::toInt64(size_t /*n*/) const { |
1023 | | // Returns number of seconds in the day in UTC. |
1024 | 0 | auto result = static_cast<int64_t>(time_.hour - time_.tzHour) * 60 * 60; |
1025 | 0 | result += static_cast<int64_t>(time_.minute - time_.tzMinute) * 60; |
1026 | 0 | result += time_.second; |
1027 | 0 | if (result < 0) { |
1028 | 0 | result += 86400; |
1029 | 0 | } |
1030 | 0 | ok_ = true; |
1031 | 0 | return result; |
1032 | 0 | } |
1033 | | |
1034 | 0 | uint32_t TimeValue::toUint32(size_t /*n*/) const { |
1035 | 0 | return static_cast<uint32_t>(std::clamp<int64_t>(toInt64(), 0, std::numeric_limits<uint32_t>::max())); |
1036 | 0 | } |
1037 | | |
1038 | 0 | float TimeValue::toFloat(size_t n) const { |
1039 | 0 | return static_cast<float>(toInt64(n)); |
1040 | 0 | } |
1041 | | |
1042 | 0 | Rational TimeValue::toRational(size_t n) const { |
1043 | 0 | return {static_cast<int32_t>(toInt64(n)), 1}; |
1044 | 0 | } |
1045 | | |
1046 | | } // namespace Exiv2 |