Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "value.hpp" |
5 | | #include "config.h" |
6 | | #include "convert.hpp" |
7 | | #include "enforce.hpp" |
8 | | #include "error.hpp" |
9 | | #include "image_int.hpp" |
10 | | #include "types.hpp" |
11 | | |
12 | | // + standard includes |
13 | | #include <iterator> |
14 | | #include <sstream> |
15 | | |
16 | | // ***************************************************************************** |
17 | | // class member definitions |
18 | | namespace Exiv2 { |
19 | 108M | Value::Value(TypeId typeId) : type_(typeId) { |
20 | 108M | } |
21 | | |
22 | 40.4M | Value::UniquePtr Value::create(TypeId typeId) { |
23 | 40.4M | switch (typeId) { |
24 | 0 | case invalidTypeId: |
25 | 8.92k | case signedByte: |
26 | 3.30M | case unsignedByte: |
27 | 3.30M | return std::make_unique<DataValue>(typeId); |
28 | 121k | case asciiString: |
29 | 121k | return std::make_unique<AsciiValue>(); |
30 | 29.7M | case unsignedShort: |
31 | 29.7M | return std::make_unique<ValueType<uint16_t>>(); |
32 | 238k | case unsignedLong: |
33 | 266k | case tiffIfd: |
34 | 266k | return std::make_unique<ValueType<uint32_t>>(typeId); |
35 | 57.7k | case unsignedRational: |
36 | 57.7k | return std::make_unique<ValueType<URational>>(); |
37 | 131k | case undefined: |
38 | 131k | return std::make_unique<DataValue>(); |
39 | 3.61M | case signedShort: |
40 | 3.61M | return std::make_unique<ValueType<int16_t>>(); |
41 | 80.8k | case signedLong: |
42 | 80.8k | return std::make_unique<ValueType<int32_t>>(); |
43 | 42.4k | case signedRational: |
44 | 42.4k | return std::make_unique<ValueType<Rational>>(); |
45 | 10.4k | case tiffFloat: |
46 | 10.4k | return std::make_unique<ValueType<float>>(); |
47 | 10.3k | case tiffDouble: |
48 | 10.3k | return std::make_unique<ValueType<double>>(); |
49 | 62.8k | case string: |
50 | 62.8k | return std::make_unique<StringValue>(); |
51 | 1.68k | case date: |
52 | 1.68k | return std::make_unique<DateValue>(); |
53 | 2.07k | case time: |
54 | 2.07k | return std::make_unique<TimeValue>(); |
55 | 3.38k | case comment: |
56 | 3.38k | return std::make_unique<CommentValue>(); |
57 | 28.4k | case xmpText: |
58 | 28.4k | return std::make_unique<XmpTextValue>(); |
59 | 0 | case xmpBag: |
60 | 3.54k | case xmpSeq: |
61 | 3.54k | case xmpAlt: |
62 | 3.54k | return std::make_unique<XmpArrayValue>(typeId); |
63 | 0 | case langAlt: |
64 | 0 | return std::make_unique<LangAltValue>(); |
65 | 3.02M | default: |
66 | 3.02M | return std::make_unique<DataValue>(typeId); |
67 | 40.4M | } |
68 | 40.4M | } // Value::create |
69 | | |
70 | 1.47k | int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) { |
71 | 1.47k | return -1; |
72 | 1.47k | } |
73 | | |
74 | 142k | std::string Value::toString() const { |
75 | 142k | std::ostringstream os; |
76 | 142k | write(os); |
77 | 142k | ok_ = !os.fail(); |
78 | 142k | return os.str(); |
79 | 142k | } |
80 | | |
81 | 18.7k | std::string Value::toString(size_t /*n*/) const { |
82 | 18.7k | return toString(); |
83 | 18.7k | } |
84 | | |
85 | 61.2k | size_t Value::sizeDataArea() const { |
86 | 61.2k | return 0; |
87 | 61.2k | } |
88 | | |
89 | 5.32k | DataBuf Value::dataArea() const { |
90 | 5.32k | return {nullptr, 0}; |
91 | 5.32k | } |
92 | | |
93 | 6.46M | DataValue::DataValue(TypeId typeId) : Value(typeId) { |
94 | 6.46M | } |
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 | 11.9M | size_t DataValue::count() const { |
101 | 11.9M | return size(); |
102 | 11.9M | } |
103 | | |
104 | 6.46M | int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
105 | | // byteOrder not needed |
106 | 6.46M | value_.assign(buf, buf + len); |
107 | 6.46M | return 0; |
108 | 6.46M | } |
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 | 3.12M | size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
123 | | // byteOrder not needed |
124 | 3.12M | return std::copy(value_.begin(), value_.end(), buf) - buf; |
125 | 3.12M | } |
126 | | |
127 | 22.9M | size_t DataValue::size() const { |
128 | 22.9M | return value_.size(); |
129 | 22.9M | } |
130 | | |
131 | 19.2M | DataValue* DataValue::clone_() const { |
132 | 19.2M | return new DataValue(*this); |
133 | 19.2M | } |
134 | | |
135 | 135k | std::ostream& DataValue::write(std::ostream& os) const { |
136 | 135k | if (!value_.empty()) { |
137 | 135k | std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<int>(os, " ")); |
138 | 135k | os << static_cast<int>(value_.back()); |
139 | 135k | } |
140 | 135k | return os; |
141 | 135k | } |
142 | | |
143 | 4.13k | std::string DataValue::toString(size_t n) const { |
144 | 4.13k | ok_ = true; |
145 | 4.13k | return std::to_string(value_.at(n)); |
146 | 4.13k | } |
147 | | |
148 | 224k | int64_t DataValue::toInt64(size_t n) const { |
149 | 224k | ok_ = true; |
150 | 224k | return value_.at(n); |
151 | 224k | } |
152 | | |
153 | 59.9k | uint32_t DataValue::toUint32(size_t n) const { |
154 | 59.9k | ok_ = true; |
155 | 59.9k | return value_.at(n); |
156 | 59.9k | } |
157 | | |
158 | 2.02k | float DataValue::toFloat(size_t n) const { |
159 | 2.02k | ok_ = true; |
160 | 2.02k | return value_.at(n); |
161 | 2.02k | } |
162 | | |
163 | 2.77k | Rational DataValue::toRational(size_t n) const { |
164 | 2.77k | ok_ = true; |
165 | 2.77k | return {value_.at(n), 1}; |
166 | 2.77k | } |
167 | | |
168 | 0 | StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) { |
169 | 0 | read(buf); |
170 | 0 | } |
171 | | |
172 | 175 | int StringValueBase::read(const std::string& buf) { |
173 | 175 | value_ = buf; |
174 | 175 | return 0; |
175 | 175 | } |
176 | | |
177 | 170k | int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
178 | | // byteOrder not needed |
179 | 170k | if (buf) |
180 | 170k | value_ = std::string(reinterpret_cast<const char*>(buf), len); |
181 | 170k | return 0; |
182 | 170k | } |
183 | | |
184 | 91.5k | size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
185 | 91.5k | if (value_.empty()) |
186 | 35.7k | return 0; |
187 | | // byteOrder not needed |
188 | 55.7k | return value_.copy(reinterpret_cast<char*>(buf), value_.size()); |
189 | 91.5k | } |
190 | | |
191 | 259k | size_t StringValueBase::count() const { |
192 | 259k | return size(); |
193 | 259k | } |
194 | | |
195 | 495k | size_t StringValueBase::size() const { |
196 | 495k | return value_.size(); |
197 | 495k | } |
198 | | |
199 | 1.28k | std::ostream& StringValueBase::write(std::ostream& os) const { |
200 | 1.28k | return os << value_; |
201 | 1.28k | } |
202 | | |
203 | 27.1k | int64_t StringValueBase::toInt64(size_t n) const { |
204 | 27.1k | ok_ = true; |
205 | 27.1k | return value_.at(n); |
206 | 27.1k | } |
207 | | |
208 | 3.81k | uint32_t StringValueBase::toUint32(size_t n) const { |
209 | 3.81k | ok_ = true; |
210 | 3.81k | return value_.at(n); |
211 | 3.81k | } |
212 | | |
213 | 472 | float StringValueBase::toFloat(size_t n) const { |
214 | 472 | ok_ = true; |
215 | 472 | return value_.at(n); |
216 | 472 | } |
217 | | |
218 | 528 | Rational StringValueBase::toRational(size_t n) const { |
219 | 528 | ok_ = true; |
220 | 528 | return {value_.at(n), 1}; |
221 | 528 | } |
222 | | |
223 | 62.8k | StringValue::StringValue() : StringValueBase(string) { |
224 | 62.8k | } |
225 | | |
226 | 0 | StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) { |
227 | 0 | } |
228 | | |
229 | 812k | StringValue* StringValue::clone_() const { |
230 | 812k | return new StringValue(*this); |
231 | 812k | } |
232 | | |
233 | 121k | AsciiValue::AsciiValue() : StringValueBase(asciiString) { |
234 | 121k | } |
235 | | |
236 | 0 | AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) { |
237 | 0 | } |
238 | | |
239 | 35.5k | int AsciiValue::read(const std::string& buf) { |
240 | 35.5k | value_ = buf; |
241 | | // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484 |
242 | 35.5k | if (value_.empty() || value_.back() != '\0') { |
243 | 35.5k | value_ += '\0'; |
244 | 35.5k | } |
245 | 35.5k | return 0; |
246 | 35.5k | } |
247 | | |
248 | 267k | AsciiValue* AsciiValue::clone_() const { |
249 | 267k | return new AsciiValue(*this); |
250 | 267k | } |
251 | | |
252 | 174k | std::ostream& AsciiValue::write(std::ostream& os) const { |
253 | | // Write only up to the first '\0' (if any) |
254 | 174k | std::string::size_type pos = value_.find_first_of('\0'); |
255 | 174k | if (pos == std::string::npos) |
256 | 6.94k | pos = value_.size(); |
257 | 174k | return os << value_.substr(0, pos); |
258 | 174k | } |
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 | 1.24k | const char* CommentValue::CharsetInfo::name(CharsetId charsetId) { |
271 | 1.24k | return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_; |
272 | 1.24k | } |
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 | 6.19k | CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) { |
286 | 6.19k | int i = 0; |
287 | 24.6k | for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) { |
288 | 18.4k | } |
289 | 6.19k | return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; |
290 | 6.19k | } |
291 | | |
292 | 3.38k | CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) { |
293 | 3.38k | } |
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 | 3.38k | int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) { |
330 | 3.38k | byteOrder_ = byteOrder; |
331 | 3.38k | return StringValueBase::read(buf, len, byteOrder); |
332 | 3.38k | } |
333 | | |
334 | 533 | size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const { |
335 | 533 | std::string c = value_; |
336 | 533 | if (charsetId() == unicode) { |
337 | 57 | c = value_.substr(8); |
338 | 57 | [[maybe_unused]] const size_t sz = c.size(); |
339 | 57 | if (byteOrder_ == littleEndian && byteOrder == bigEndian) { |
340 | 0 | convertStringCharset(c, "UCS-2LE", "UCS-2BE"); |
341 | 57 | } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) { |
342 | 0 | convertStringCharset(c, "UCS-2BE", "UCS-2LE"); |
343 | 0 | } |
344 | 57 | c = value_.substr(0, 8) + c; |
345 | 57 | } |
346 | 533 | if (c.empty()) |
347 | 336 | return 0; |
348 | 197 | return c.copy(reinterpret_cast<char*>(buf), c.size()); |
349 | 533 | } |
350 | | |
351 | 1.67k | std::ostream& CommentValue::write(std::ostream& os) const { |
352 | 1.67k | CharsetId csId = charsetId(); |
353 | 1.67k | std::string text = comment(); |
354 | 1.67k | if (csId != undefined) { |
355 | 1.24k | os << "charset=" << CharsetInfo::name(csId) << " "; |
356 | 1.24k | } |
357 | 1.67k | return os << text; |
358 | 1.67k | } |
359 | | |
360 | 1.67k | std::string CommentValue::comment(const char* encoding) const { |
361 | 1.67k | std::string c; |
362 | 1.67k | if (value_.length() < 8) { |
363 | 82 | return c; |
364 | 82 | } |
365 | 1.59k | c = value_.substr(8); |
366 | 1.59k | if (charsetId() == unicode) { |
367 | 787 | const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding; |
368 | 787 | if (!convertStringCharset(c, from, "UTF-8")) |
369 | 31 | throw Error(ErrorCode::kerInvalidIconvEncoding, from, "UTF-8"); |
370 | 787 | } |
371 | | |
372 | | // # 1266 Remove trailing nulls |
373 | 1.56k | if (charsetId() == undefined || charsetId() == ascii) { |
374 | 360 | auto n = c.find('\0'); |
375 | 360 | if (n != std::string::npos) |
376 | 264 | c.resize(n); |
377 | 360 | } |
378 | 1.56k | return c; |
379 | 1.59k | } |
380 | | |
381 | 6.61k | CommentValue::CharsetId CommentValue::charsetId() const { |
382 | 6.61k | CharsetId charsetId = undefined; |
383 | 6.61k | if (value_.length() >= 8) { |
384 | 6.19k | const std::string code = value_.substr(0, 8); |
385 | 6.19k | charsetId = CharsetInfo::charsetIdByCode(code); |
386 | 6.19k | } |
387 | 6.61k | return charsetId; |
388 | 6.61k | } |
389 | | |
390 | 787 | const char* CommentValue::detectCharset(std::string& c) const { |
391 | | // Interpret a BOM if there is one |
392 | 787 | if (c.front() == '\xef' && c[1] == '\xbb' && c[2] == '\xbf') { |
393 | 54 | c = c.substr(3); |
394 | 54 | return "UTF-8"; |
395 | 54 | } |
396 | 733 | if (c.front() == '\xff' && c[1] == '\xfe') { |
397 | 110 | c = c.substr(2); |
398 | 110 | return "UCS-2LE"; |
399 | 110 | } |
400 | 623 | if (c.front() == '\xfe' && c[1] == '\xff') { |
401 | 93 | c = c.substr(2); |
402 | 93 | return "UCS-2BE"; |
403 | 93 | } |
404 | | |
405 | | // Todo: Add logic to guess if the comment is encoded in UTF-8 |
406 | | |
407 | 530 | return byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; |
408 | 623 | } |
409 | | |
410 | 7.54k | CommentValue* CommentValue::clone_() const { |
411 | 7.54k | return new CommentValue(*this); |
412 | 7.54k | } |
413 | | |
414 | 5.83k | void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) { |
415 | 5.83k | xmpArrayType_ = xmpArrayType; |
416 | 5.83k | } |
417 | | |
418 | 930 | void XmpValue::setXmpStruct(XmpStruct xmpStruct) { |
419 | 930 | xmpStruct_ = xmpStruct; |
420 | 930 | } |
421 | | |
422 | 49.0k | XmpValue::XmpArrayType XmpValue::xmpArrayType() const { |
423 | 49.0k | return xmpArrayType_; |
424 | 49.0k | } |
425 | | |
426 | 5.72k | XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) { |
427 | 5.72k | XmpArrayType xa = xaNone; |
428 | 5.72k | switch (typeId) { |
429 | 50 | case xmpAlt: |
430 | 50 | xa = xaAlt; |
431 | 50 | break; |
432 | 223 | case xmpBag: |
433 | 223 | xa = xaBag; |
434 | 223 | break; |
435 | 4.85k | case xmpSeq: |
436 | 4.85k | xa = xaSeq; |
437 | 4.85k | break; |
438 | 600 | default: |
439 | 600 | break; |
440 | 5.72k | } |
441 | 5.72k | return xa; |
442 | 5.72k | } |
443 | | |
444 | 49.1k | XmpValue::XmpStruct XmpValue::xmpStruct() const { |
445 | 49.1k | return xmpStruct_; |
446 | 49.1k | } |
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 | 48.0k | XmpTextValue::XmpTextValue() : XmpValue(xmpText) { |
469 | 48.0k | } |
470 | | |
471 | 0 | XmpTextValue::XmpTextValue(const std::string& buf) : XmpValue(xmpText) { |
472 | 0 | read(buf); |
473 | 0 | } |
474 | | |
475 | 806k | int XmpTextValue::read(const std::string& buf) { |
476 | | // support a type=Alt,Bag,Seq,Struct indicator |
477 | 806k | std::string b = buf; |
478 | 806k | std::string type; |
479 | 806k | if (buf.starts_with("type=")) { |
480 | 441 | std::string::size_type pos = buf.find_first_of(' '); |
481 | 441 | type = buf.substr(5, pos - 5); |
482 | | // Strip quotes (so you can also specify the type without quotes) |
483 | 441 | if (!type.empty() && type.front() == '"') |
484 | 206 | type = type.substr(1); |
485 | 441 | if (!type.empty() && type.back() == '"') |
486 | 121 | type.pop_back(); |
487 | 441 | b.clear(); |
488 | 441 | if (pos != std::string::npos) |
489 | 171 | b = buf.substr(pos + 1); |
490 | 441 | } |
491 | 806k | if (!type.empty()) { |
492 | 289 | if (type == "Alt") { |
493 | 56 | setXmpArrayType(XmpValue::xaAlt); |
494 | 233 | } else if (type == "Bag") { |
495 | 19 | setXmpArrayType(XmpValue::xaBag); |
496 | 214 | } else if (type == "Seq") { |
497 | 32 | setXmpArrayType(XmpValue::xaSeq); |
498 | 182 | } else if (type == "Struct") { |
499 | 36 | setXmpStruct(); |
500 | 146 | } else { |
501 | 146 | throw Error(ErrorCode::kerInvalidXmpText, type); |
502 | 146 | } |
503 | 289 | } |
504 | 806k | value_ = std::move(b); |
505 | 806k | return 0; |
506 | 806k | } |
507 | | |
508 | 0 | XmpTextValue::UniquePtr XmpTextValue::clone() const { |
509 | 0 | return UniquePtr(clone_()); |
510 | 0 | } |
511 | | |
512 | 35.2k | size_t XmpTextValue::size() const { |
513 | 35.2k | return value_.size(); |
514 | 35.2k | } |
515 | | |
516 | 35.2k | size_t XmpTextValue::count() const { |
517 | 35.2k | return size(); |
518 | 35.2k | } |
519 | | |
520 | 41.6k | std::ostream& XmpTextValue::write(std::ostream& os) const { |
521 | 41.6k | bool del = false; |
522 | 41.6k | if (xmpArrayType() != XmpValue::xaNone) { |
523 | 240 | switch (xmpArrayType()) { |
524 | 22 | case XmpValue::xaAlt: |
525 | 22 | os << "type=\"Alt\""; |
526 | 22 | break; |
527 | 2 | case XmpValue::xaBag: |
528 | 2 | os << "type=\"Bag\""; |
529 | 2 | break; |
530 | 216 | case XmpValue::xaSeq: |
531 | 216 | os << "type=\"Seq\""; |
532 | 216 | break; |
533 | 0 | case XmpValue::xaNone: |
534 | 0 | break; // just to suppress the warning |
535 | 240 | } |
536 | 240 | del = true; |
537 | 41.4k | } else if (xmpStruct() != XmpValue::xsNone) { |
538 | 616 | switch (xmpStruct()) { |
539 | 616 | case XmpValue::xsStruct: |
540 | 616 | os << "type=\"Struct\""; |
541 | 616 | break; |
542 | 0 | case XmpValue::xsNone: |
543 | 0 | break; // just to suppress the warning |
544 | 616 | } |
545 | 616 | del = true; |
546 | 616 | } |
547 | 41.6k | if (del && !value_.empty()) |
548 | 2 | os << " "; |
549 | 41.6k | return os << value_; |
550 | 41.6k | } |
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 | 90.1k | XmpTextValue* XmpTextValue::clone_() const { |
569 | 90.1k | return new XmpTextValue(*this); |
570 | 90.1k | } |
571 | | |
572 | 4.83k | XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) { |
573 | 4.83k | setXmpArrayType(xmpArrayType(typeId)); |
574 | 4.83k | } |
575 | | |
576 | 22.4k | int XmpArrayValue::read(const std::string& buf) { |
577 | 22.4k | if (!buf.empty()) |
578 | 15.1k | value_.push_back(buf); |
579 | 22.4k | return 0; |
580 | 22.4k | } |
581 | | |
582 | 0 | XmpArrayValue::UniquePtr XmpArrayValue::clone() const { |
583 | 0 | return UniquePtr(clone_()); |
584 | 0 | } |
585 | | |
586 | 4.12k | size_t XmpArrayValue::count() const { |
587 | 4.12k | return value_.size(); |
588 | 4.12k | } |
589 | | |
590 | 2.09k | std::ostream& XmpArrayValue::write(std::ostream& os) const { |
591 | 2.09k | if (!value_.empty()) { |
592 | 1.70k | std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<std::string>(os, ", ")); |
593 | 1.70k | os << value_.back(); |
594 | 1.70k | } |
595 | 2.09k | return os; |
596 | 2.09k | } |
597 | | |
598 | 2.89k | std::string XmpArrayValue::toString(size_t n) const { |
599 | 2.89k | ok_ = true; |
600 | 2.89k | return value_.at(n); |
601 | 2.89k | } |
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 | 13.2k | XmpArrayValue* XmpArrayValue::clone_() const { |
620 | 13.2k | return new XmpArrayValue(*this); |
621 | 13.2k | } |
622 | | |
623 | 290 | LangAltValue::LangAltValue() : XmpValue(langAlt) { |
624 | 290 | } |
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 | 308 | size_t LangAltValue::count() const { |
678 | 308 | return value_.size(); |
679 | 308 | } |
680 | | |
681 | 308 | std::ostream& LangAltValue::write(std::ostream& os) const { |
682 | 308 | bool first = true; |
683 | | |
684 | | // Write the default entry first |
685 | 308 | if (auto i = value_.find("x-default"); i != value_.end()) { |
686 | 92 | os << "lang=\"" << i->first << "\" " << i->second; |
687 | 92 | first = false; |
688 | 92 | } |
689 | | |
690 | | // Write the others |
691 | 308 | for (const auto& [lang, s] : value_) { |
692 | 284 | if (lang != "x-default") { |
693 | 192 | if (!first) |
694 | 0 | os << ", "; |
695 | 192 | os << "lang=\"" << lang << "\" " << s; |
696 | 192 | first = false; |
697 | 192 | } |
698 | 284 | } |
699 | 308 | return os; |
700 | 308 | } |
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 | 1.22k | LangAltValue* LangAltValue::clone_() const { |
736 | 1.22k | return new LangAltValue(*this); |
737 | 1.22k | } |
738 | | |
739 | 1.68k | DateValue::DateValue() : Value(date) { |
740 | 1.68k | date_ = {}; |
741 | 1.68k | } |
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 | 1.51k | int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
748 | 1.51k | const std::string str(reinterpret_cast<const char*>(buf), len); |
749 | 1.51k | return read(str); |
750 | 1.51k | } |
751 | | |
752 | 1.68k | 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 | 1.68k | size_t monthPos = 0; |
756 | 1.68k | size_t dayPos = 0; |
757 | | |
758 | 2.23k | auto printWarning = [] { |
759 | 2.23k | #ifndef SUPPRESS_WARNINGS |
760 | 2.23k | EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n"; |
761 | 2.23k | #endif |
762 | 2.23k | }; |
763 | | |
764 | 1.68k | if (buf.size() < 8) { |
765 | 293 | printWarning(); |
766 | 293 | return 1; |
767 | 293 | } |
768 | | |
769 | 1.39k | if ((buf.size() >= 10 && buf[4] == '-' && buf[7] == '-') || (buf.size() == 8)) { |
770 | 953 | if (buf.size() >= 10) { |
771 | 75 | monthPos = 5; |
772 | 75 | dayPos = 8; |
773 | 878 | } else { |
774 | 878 | monthPos = 4; |
775 | 878 | dayPos = 6; |
776 | 878 | } |
777 | | |
778 | 1.77k | auto checkDigits = [&buf, &printWarning](size_t start, size_t count, int32_t& dest) { |
779 | 4.80k | for (size_t i = start; i < start + count; ++i) { |
780 | 3.75k | if (!std::isdigit(buf[i])) { |
781 | 722 | printWarning(); |
782 | 722 | return 1; |
783 | 722 | } |
784 | 3.75k | } |
785 | 1.05k | dest = std::stoul(buf.substr(start, count)); |
786 | 1.05k | return 0; |
787 | 1.77k | }; |
788 | | |
789 | 953 | if (checkDigits(0, 4, date_.year) || checkDigits(monthPos, 2, date_.month) || checkDigits(dayPos, 2, date_.day)) { |
790 | 722 | printWarning(); |
791 | 722 | return 1; |
792 | 722 | } |
793 | | |
794 | 231 | if (date_.month > 12 || date_.day > 31) { |
795 | 61 | date_.month = 0; |
796 | 61 | date_.day = 0; |
797 | 61 | printWarning(); |
798 | 61 | return 1; |
799 | 61 | } |
800 | 170 | return 0; |
801 | 231 | } |
802 | 440 | printWarning(); |
803 | 440 | return 1; |
804 | 1.39k | } |
805 | | |
806 | 0 | void DateValue::setDate(const Date& src) { |
807 | 0 | date_ = src; |
808 | 0 | } |
809 | | |
810 | 47 | 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 | 47 | auto out = reinterpret_cast<char*>(buf); |
815 | 47 | auto it = stringFormatTo(out, "{:04}{:02}{:02}", date_.year, date_.month, date_.day); |
816 | | |
817 | 47 | return it - out; |
818 | 47 | } |
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 | 94 | size_t DateValue::size() const { |
829 | 94 | return 8; |
830 | 94 | } |
831 | | |
832 | 663 | DateValue* DateValue::clone_() const { |
833 | 663 | return new DateValue(*this); |
834 | 663 | } |
835 | | |
836 | 474 | std::ostream& DateValue::write(std::ostream& os) const { |
837 | | // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD |
838 | 474 | return os << stringFormat("{:04}-{:02}-{:02}", date_.year, date_.month, date_.day); |
839 | 474 | } |
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 | 2.07k | TimeValue::TimeValue() : Value(time) { |
876 | 2.07k | time_ = {}; |
877 | 2.07k | } |
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 | 2.07k | int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
884 | 2.07k | const std::string str(reinterpret_cast<const char*>(buf), len); |
885 | 2.07k | return read(str); |
886 | 2.07k | } |
887 | | |
888 | 2.07k | 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 | 2.07k | auto printWarning = [] { |
894 | 1.65k | #ifndef SUPPRESS_WARNINGS |
895 | 1.65k | EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n"; |
896 | 1.65k | #endif |
897 | 1.65k | return 1; |
898 | 1.65k | }; |
899 | | |
900 | 2.07k | if (buf.size() < 2) |
901 | 155 | return printWarning(); |
902 | | |
903 | 1.91k | for (auto c : buf) |
904 | 23.8k | if (c != ':' && c != '+' && c != '-' && c != 'Z' && !std::isdigit(c)) |
905 | 737 | return printWarning(); |
906 | | |
907 | 1.17k | size_t mpos; |
908 | 1.17k | size_t spos; |
909 | 1.17k | if (buf.find(':') != std::string::npos) { |
910 | 652 | mpos = 3; |
911 | 652 | spos = 6; |
912 | 652 | } else { |
913 | 526 | mpos = 2; |
914 | 526 | spos = 4; |
915 | 526 | } |
916 | | |
917 | 1.17k | auto hi = std::stoi(buf.substr(0, 2)); |
918 | 1.17k | if (hi < 0 || hi > 23) |
919 | 168 | return printWarning(); |
920 | 1.01k | time_.hour = hi; |
921 | 1.01k | if (buf.size() > 3) { |
922 | 949 | auto mi = std::stoi(buf.substr(mpos, 2)); |
923 | 949 | if (mi < 0 || mi > 59) |
924 | 180 | return printWarning(); |
925 | 769 | time_.minute = std::stoi(buf.substr(mpos, 2)); |
926 | 769 | } else { |
927 | 61 | time_.minute = 0; |
928 | 61 | } |
929 | 830 | if (buf.size() > 5) { |
930 | 725 | auto si = std::stoi(buf.substr(spos, 2)); |
931 | 725 | if (si < 0 || si > 60) |
932 | 50 | return printWarning(); |
933 | 675 | time_.second = std::stoi(buf.substr(spos, 2)); |
934 | 675 | } else { |
935 | 105 | time_.second = 0; |
936 | 105 | } |
937 | | |
938 | 780 | auto fpos = buf.find('+'); |
939 | 780 | if (fpos == std::string::npos) |
940 | 614 | fpos = buf.find('-'); |
941 | | |
942 | 780 | if (fpos != std::string::npos) { |
943 | 709 | auto format = buf.substr(fpos, buf.size()); |
944 | 709 | auto posColon = format.find(':'); |
945 | 709 | if (posColon == std::string::npos) { |
946 | | // Extended format |
947 | 395 | auto tzhi = std::stoi(format.substr(0, 3)); |
948 | 395 | if (tzhi < -23 || tzhi > 23) |
949 | 81 | return printWarning(); |
950 | 314 | time_.tzHour = tzhi; |
951 | 314 | if (format.size() > 3) { |
952 | 251 | int minute = std::stoi(format.substr(3)); |
953 | 251 | if (minute < 0 || minute > 59) |
954 | 107 | return printWarning(); |
955 | 144 | time_.tzMinute = time_.tzHour < 0 ? -minute : minute; |
956 | 144 | } |
957 | 314 | } else { |
958 | | // Basic format |
959 | 314 | auto tzhi = std::stoi(format.substr(0, posColon)); |
960 | 314 | if (tzhi < -23 || tzhi > 23) |
961 | 53 | return printWarning(); |
962 | 261 | time_.tzHour = tzhi; |
963 | 261 | int minute = std::stoi(format.substr(posColon + 1)); |
964 | 261 | if (minute < 0 || minute > 59) |
965 | 122 | return printWarning(); |
966 | 139 | time_.tzMinute = time_.tzHour < 0 ? -minute : minute; |
967 | 139 | } |
968 | 709 | } |
969 | 417 | return 0; |
970 | 780 | } |
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 | 194 | 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 | 194 | char plusMinus = '+'; |
981 | 194 | if (time_.tzHour < 0 || time_.tzMinute < 0) |
982 | 9 | plusMinus = '-'; |
983 | | |
984 | 194 | auto out = reinterpret_cast<char*>(buf); |
985 | 194 | auto it = stringFormatTo(out, "{:02}{:02}{:02}{}{:02}{:02}", time_.hour, time_.minute, time_.second, plusMinus, |
986 | 194 | std::abs(time_.tzHour), std::abs(time_.tzMinute)); |
987 | | |
988 | 194 | auto wrote = static_cast<size_t>(it - out); |
989 | 194 | Internal::enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat); |
990 | 194 | return wrote; |
991 | 194 | } |
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 | 388 | size_t TimeValue::size() const { |
1002 | 388 | return 11; |
1003 | 388 | } |
1004 | | |
1005 | 1.01k | TimeValue* TimeValue::clone_() const { |
1006 | 1.01k | return new TimeValue(*this); |
1007 | 1.01k | } |
1008 | | |
1009 | 112 | std::ostream& TimeValue::write(std::ostream& os) const { |
1010 | | // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm |
1011 | 112 | char plusMinus = '+'; |
1012 | 112 | if (time_.tzHour < 0 || time_.tzMinute < 0) |
1013 | 34 | plusMinus = '-'; |
1014 | | |
1015 | 112 | return os << stringFormat("{:02}:{:02}:{:02}{}{:02}:{:02}", time_.hour, time_.minute, time_.second, plusMinus, |
1016 | 112 | std::abs(time_.tzHour), std::abs(time_.tzMinute)); |
1017 | 112 | } |
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 |