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