Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
cpdf_psengine.cpp
Go to the documentation of this file.
1
// Copyright 2017 The PDFium Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7
#
include
"core/fpdfapi/page/cpdf_psengine.h"
8
9
#
include
<
math
.
h
>
10
11
#
include
<
algorithm
>
12
#
include
<
limits
>
13
#
include
<
utility
>
14
15
#
include
"core/fpdfapi/parser/cpdf_simple_parser.h"
16
#
include
"core/fxcrt/check.h"
17
#
include
"core/fxcrt/check_op.h"
18
#
include
"core/fxcrt/fx_safe_types.h"
19
#
include
"core/fxcrt/fx_string.h"
20
21
namespace
{
22
23
struct
PDF_PSOpName {
24
// Inline string data reduces size for small strings.
25
const
char
name[9];
26
PDF_PSOP
op;
27
};
28
29
constexpr
PDF_PSOpName kPsOpNames[] = {
30
{
"abs"
,
PSOP_ABS
},
31
{
"add"
,
PSOP_ADD
},
32
{
"and"
,
PSOP_AND
},
33
{
"atan"
,
PSOP_ATAN
},
34
{
"bitshift"
,
PSOP_BITSHIFT
},
35
{
"ceiling"
,
PSOP_CEILING
},
36
{
"copy"
,
PSOP_COPY
},
37
{
"cos"
,
PSOP_COS
},
38
{
"cvi"
,
PSOP_CVI
},
39
{
"cvr"
,
PSOP_CVR
},
40
{
"div"
,
PSOP_DIV
},
41
{
"dup"
,
PSOP_DUP
},
42
{
"eq"
,
PSOP_EQ
},
43
{
"exch"
,
PSOP_EXCH
},
44
{
"exp"
,
PSOP_EXP
},
45
{
"false"
,
PSOP_FALSE
},
46
{
"floor"
,
PSOP_FLOOR
},
47
{
"ge"
,
PSOP_GE
},
48
{
"gt"
,
PSOP_GT
},
49
{
"idiv"
,
PSOP_IDIV
},
50
{
"if"
,
PSOP_IF
},
51
{
"ifelse"
,
PSOP_IFELSE
},
52
{
"index"
,
PSOP_INDEX
},
53
{
"le"
,
PSOP_LE
},
54
{
"ln"
,
PSOP_LN
},
55
{
"log"
,
PSOP_LOG
},
56
{
"lt"
,
PSOP_LT
},
57
{
"mod"
,
PSOP_MOD
},
58
{
"mul"
,
PSOP_MUL
},
59
{
"ne"
,
PSOP_NE
},
60
{
"neg"
,
PSOP_NEG
},
61
{
"not"
,
PSOP_NOT
},
62
{
"or"
,
PSOP_OR
},
63
{
"pop"
,
PSOP_POP
},
64
{
"roll"
,
PSOP_ROLL
},
65
{
"round"
,
PSOP_ROUND
},
66
{
"sin"
,
PSOP_SIN
},
67
{
"sqrt"
,
PSOP_SQRT
},
68
{
"sub"
,
PSOP_SUB
},
69
{
"true"
,
PSOP_TRUE
},
70
{
"truncate"
,
PSOP_TRUNCATE
},
71
{
"xor"
,
PSOP_XOR
},
72
};
73
74
// Round half up is a nearest integer round with half-way numbers always rounded
75
// up. Example: -5.5 rounds to -5.
76
float
RoundHalfUp(
float
f) {
77
if
(isnan(f))
78
return
0;
79
if
(f >
std
::numeric_limits<
float
>::max() - 0.5f)
80
return
std
::numeric_limits<
float
>::max();
81
return
floor(f + 0.5f);
82
}
83
84
}
// namespace
85
86
CPDF_PSOP
::
CPDF_PSOP
()
87
: m_op(
PSOP_PROC
), m_value(0),
m_proc
(
std
::
make_unique
<
CPDF_PSProc
>()) {}
88
89
CPDF_PSOP
::
CPDF_PSOP
(
PDF_PSOP
op) : m_op(op), m_value(0) {
90
DCHECK
(m_op != PSOP_CONST);
91
DCHECK
(m_op != PSOP_PROC);
92
}
93
94
CPDF_PSOP
::
CPDF_PSOP
(
float
value) : m_op(
PSOP_CONST
), m_value(value) {}
95
96
CPDF_PSOP
::~
CPDF_PSOP
() =
default
;
97
98
bool
CPDF_PSOP
::
Parse
(
CPDF_SimpleParser
* parser,
int
depth) {
99
CHECK_EQ
(m_op,
PSOP_PROC
);
100
return
m_proc->Parse(parser, depth);
101
}
102
103
void
CPDF_PSOP
::
Execute
(
CPDF_PSEngine
* pEngine) {
104
CHECK_EQ
(m_op,
PSOP_PROC
);
105
m_proc->Execute(pEngine);
106
}
107
108
float
CPDF_PSOP
::
GetFloatValue
()
const
{
109
CHECK_EQ
(m_op,
PSOP_CONST
);
110
return
m_value;
111
}
112
113
bool
CPDF_PSEngine
::
Execute
() {
114
return
m_MainProc.Execute(
this
);
115
}
116
117
CPDF_PSProc
::
CPDF_PSProc
() =
default
;
118
119
CPDF_PSProc
::~
CPDF_PSProc
() =
default
;
120
121
bool
CPDF_PSProc
::
Parse
(
CPDF_SimpleParser
* parser,
int
depth) {
122
if
(depth > kMaxDepth)
123
return
false
;
124
125
while
(
true
) {
126
ByteStringView
word = parser->GetWord();
127
if
(word.IsEmpty())
128
return
false
;
129
130
if
(word ==
"}"
)
131
return
true
;
132
133
if
(word ==
"{"
) {
134
m_Operators.push_back(std::make_unique<CPDF_PSOP>());
135
if
(!m_Operators.back()->Parse(parser, depth + 1))
136
return
false
;
137
continue
;
138
}
139
140
AddOperator(word);
141
}
142
}
143
144
bool
CPDF_PSProc
::
Execute
(
CPDF_PSEngine
* pEngine) {
145
for
(size_t i = 0; i < m_Operators.size(); ++i) {
146
const
PDF_PSOP
op = m_Operators[i]->GetOp();
147
if
(op ==
PSOP_PROC
)
148
continue
;
149
150
if
(op ==
PSOP_CONST
) {
151
pEngine
->
Push
(m_Operators[i]->GetFloatValue());
152
continue
;
153
}
154
155
if
(op ==
PSOP_IF
) {
156
if
(i == 0 || m_Operators[i - 1]->GetOp() != PSOP_PROC)
157
return
false
;
158
159
if
(pEngine->PopInt())
160
m_Operators[i - 1]->Execute(pEngine);
161
}
else
if
(op ==
PSOP_IFELSE
) {
162
if
(i < 2 || m_Operators[i - 1]->GetOp() != PSOP_PROC ||
163
m_Operators[i - 2]->GetOp() != PSOP_PROC) {
164
return
false
;
165
}
166
size_t offset = pEngine
->
PopInt
(
)
? 2 : 1;
167
m_Operators[i - offset]->Execute(pEngine);
168
}
else
{
169
pEngine
->
DoOperator
(
op
)
;
170
}
171
}
172
return
true
;
173
}
174
175
void
CPDF_PSProc
::
AddOperatorForTesting
(
ByteStringView
word) {
176
AddOperator(word);
177
}
178
179
void
CPDF_PSProc
::AddOperator(
ByteStringView
word) {
180
const
auto
* pFound =
181
std
::lower_bound(
std
::begin(kPsOpNames),
std
::end(kPsOpNames), word,
182
[](
const
PDF_PSOpName& name,
ByteStringView
word) {
183
return
name.name < word;
184
});
185
if
(pFound != std::end(kPsOpNames) && pFound->name == word)
186
m_Operators.push_back(std::make_unique<CPDF_PSOP>(pFound->op));
187
else
188
m_Operators.push_back(std::make_unique<CPDF_PSOP>(StringToFloat(word)));
189
}
190
191
CPDF_PSEngine
::
CPDF_PSEngine
() =
default
;
192
193
CPDF_PSEngine
::~
CPDF_PSEngine
() =
default
;
194
195
void
CPDF_PSEngine
::
Push
(
float
v) {
196
if
(m_StackCount < kPSEngineStackSize)
197
m_Stack[m_StackCount++] = v;
198
}
199
200
float
CPDF_PSEngine
::
Pop
() {
201
return
m_StackCount > 0 ? m_Stack[--m_StackCount] : 0;
202
}
203
204
int
CPDF_PSEngine
::
PopInt
() {
205
return
static_cast
<
int
>(
Pop
(
)
);
206
}
207
208
bool
CPDF_PSEngine
::
Parse
(pdfium::span<
const
uint8_t> input) {
209
CPDF_SimpleParser
parser(input);
210
return
parser
.
GetWord
() ==
"{"
&& m_MainProc.Parse(&parser, 0);
211
}
212
213
bool
CPDF_PSEngine
::
DoOperator
(
PDF_PSOP
op) {
214
int
i1;
215
int
i2;
216
float
d1;
217
float
d2;
218
FX_SAFE_INT32
result;
219
switch
(op) {
220
case
PSOP_ADD
:
221
d1 =
Pop
(
)
;
222
d2 =
Pop
(
)
;
223
Push
(
d1 + d2
)
;
224
break
;
225
case
PSOP_SUB
:
226
d2 =
Pop
(
)
;
227
d1 =
Pop
(
)
;
228
Push
(
d1 - d2
)
;
229
break
;
230
case
PSOP_MUL
:
231
d1 =
Pop
(
)
;
232
d2 =
Pop
(
)
;
233
Push
(
d1 * d2
)
;
234
break
;
235
case
PSOP_DIV
:
236
d2 =
Pop
(
)
;
237
d1 =
Pop
(
)
;
238
Push
(
d2 ? d1 / d2 : 0
)
;
239
break
;
240
case
PSOP_IDIV
:
241
i2 =
PopInt
(
)
;
242
i1 =
PopInt
(
)
;
243
if
(i2) {
244
result = i1;
245
result /= i2;
246
Push
(
result.ValueOrDefault(0)
)
;
247
}
else
{
248
Push
(
0
)
;
249
}
250
break
;
251
case
PSOP_MOD
:
252
i2 =
PopInt
(
)
;
253
i1 =
PopInt
(
)
;
254
if
(i2) {
255
result = i1;
256
result %= i2;
257
Push
(
result.ValueOrDefault(0)
)
;
258
}
else
{
259
Push
(
0
)
;
260
}
261
break
;
262
case
PSOP_NEG
:
263
d1 =
Pop
(
)
;
264
Push
(
-d1
)
;
265
break
;
266
case
PSOP_ABS
:
267
d1 =
Pop
(
)
;
268
Push
(
fabs(d1)
)
;
269
break
;
270
case
PSOP_CEILING
:
271
d1 =
Pop
(
)
;
272
Push
(
ceil(d1)
)
;
273
break
;
274
case
PSOP_FLOOR
:
275
d1 =
Pop
(
)
;
276
Push
(
floor(d1)
)
;
277
break
;
278
case
PSOP_ROUND
:
279
d1 =
Pop
(
)
;
280
Push
(
RoundHalfUp(d1)
)
;
281
break
;
282
case
PSOP_TRUNCATE
:
283
i1 =
PopInt
(
)
;
284
Push
(
i1
)
;
285
break
;
286
case
PSOP_SQRT
:
287
d1 =
Pop
(
)
;
288
Push
(
sqrt(d1)
)
;
289
break
;
290
case
PSOP_SIN
:
291
d1 =
Pop
(
)
;
292
Push
(
sin(d1 *
FXSYS_PI
/ 180.0f)
)
;
293
break
;
294
case
PSOP_COS
:
295
d1 =
Pop
(
)
;
296
Push
(
cos(d1 *
FXSYS_PI
/ 180.0f)
)
;
297
break
;
298
case
PSOP_ATAN
:
299
d2 =
Pop
(
)
;
300
d1 =
Pop
(
)
;
301
d1 = atan2(d1, d2) * 180.0 /
FXSYS_PI
;
302
if
(d1 < 0) {
303
d1 += 360;
304
}
305
Push
(
d1
)
;
306
break
;
307
case
PSOP_EXP
:
308
d2 =
Pop
(
)
;
309
d1 =
Pop
(
)
;
310
Push
(
powf(d1, d2)
)
;
311
break
;
312
case
PSOP_LN
:
313
d1 =
Pop
(
)
;
314
Push
(
log(d1)
)
;
315
break
;
316
case
PSOP_LOG
:
317
d1 =
Pop
(
)
;
318
Push
(
log10(d1)
)
;
319
break
;
320
case
PSOP_CVI
:
321
i1 =
PopInt
(
)
;
322
Push
(
i1
)
;
323
break
;
324
case
PSOP_CVR
:
325
break
;
326
case
PSOP_EQ
:
327
d2 =
Pop
(
)
;
328
d1 =
Pop
(
)
;
329
Push
(
d1 == d2
)
;
330
break
;
331
case
PSOP_NE
:
332
d2 =
Pop
(
)
;
333
d1 =
Pop
(
)
;
334
Push
(
d1 != d2
)
;
335
break
;
336
case
PSOP_GT
:
337
d2 =
Pop
(
)
;
338
d1 =
Pop
(
)
;
339
Push
(
d1 > d2
)
;
340
break
;
341
case
PSOP_GE
:
342
d2 =
Pop
(
)
;
343
d1 =
Pop
(
)
;
344
Push
(
d1 >= d2
)
;
345
break
;
346
case
PSOP_LT
:
347
d2 =
Pop
(
)
;
348
d1 =
Pop
(
)
;
349
Push
(
d1 < d2
)
;
350
break
;
351
case
PSOP_LE
:
352
d2 =
Pop
(
)
;
353
d1 =
Pop
(
)
;
354
Push
(
d1 <= d2
)
;
355
break
;
356
case
PSOP_AND
:
357
i1 =
PopInt
(
)
;
358
i2 =
PopInt
(
)
;
359
Push
(
i1 & i2
)
;
360
break
;
361
case
PSOP_OR
:
362
i1 =
PopInt
(
)
;
363
i2 =
PopInt
(
)
;
364
Push
(
i1 | i2
)
;
365
break
;
366
case
PSOP_XOR
:
367
i1 =
PopInt
(
)
;
368
i2 =
PopInt
(
)
;
369
Push
(
i1 ^ i2
)
;
370
break
;
371
case
PSOP_NOT
:
372
i1 =
PopInt
(
)
;
373
Push
(
!i1
)
;
374
break
;
375
case
PSOP_BITSHIFT
: {
376
int
shift =
PopInt
(
)
;
377
result =
PopInt
(
)
;
378
if
(shift > 0) {
379
result <<= shift;
380
}
else
{
381
// Avoids unsafe negation of INT_MIN.
382
FX_SAFE_INT32
safe_shift = shift;
383
result >>= (-safe_shift).ValueOrDefault(0);
384
}
385
Push
(
result.ValueOrDefault(0)
)
;
386
break
;
387
}
388
case
PSOP_TRUE
:
389
Push
(
1
)
;
390
break
;
391
case
PSOP_FALSE
:
392
Push
(
0
)
;
393
break
;
394
case
PSOP_POP
:
395
Pop
(
)
;
396
break
;
397
case
PSOP_EXCH
:
398
d2 =
Pop
(
)
;
399
d1 =
Pop
(
)
;
400
Push
(
d2
)
;
401
Push
(
d1
)
;
402
break
;
403
case
PSOP_DUP
:
404
d1 =
Pop
(
)
;
405
Push
(
d1
)
;
406
Push
(
d1
)
;
407
break
;
408
case
PSOP_COPY
: {
409
int
n =
PopInt
(
)
;
410
if
(n < 0 || m_StackCount + n > kPSEngineStackSize ||
411
n >
static_cast
<
int
>(m_StackCount))
412
break
;
413
for
(
int
i = 0; i < n; i++)
414
m_Stack[m_StackCount + i] = m_Stack[m_StackCount + i - n];
415
m_StackCount += n;
416
break
;
417
}
418
case
PSOP_INDEX
: {
419
int
n =
PopInt
(
)
;
420
if
(n < 0 || n >=
static_cast
<
int
>(m_StackCount))
421
break
;
422
Push
(m_Stack[m_StackCount - n - 1]);
423
break
;
424
}
425
case
PSOP_ROLL
: {
426
int
j =
PopInt
(
)
;
427
int
n =
PopInt
(
)
;
428
if
(j == 0 || n == 0 || m_StackCount == 0)
429
break
;
430
if
(n < 0 || n >
static_cast
<
int
>(m_StackCount))
431
break
;
432
433
j %= n;
434
if
(j > 0)
435
j -= n;
436
auto
begin_it =
std
::begin(m_Stack) + m_StackCount - n;
437
auto
middle_it = begin_it - j;
438
auto
end_it =
std
::begin(m_Stack) + m_StackCount;
439
std
::rotate(begin_it, middle_it, end_it);
440
break
;
441
}
442
default
:
443
break
;
444
}
445
return
true
;
446
}
DCHECK
#define DCHECK
Definition
check.h:33
CHECK_EQ
#define CHECK_EQ(x, y)
Definition
check_op.h:10
CPDF_PSEngine
Definition
cpdf_psengine.h:110
CPDF_PSEngine::DoOperator
bool DoOperator(PDF_PSOP op)
Definition
cpdf_psengine.cpp:213
CPDF_PSEngine::PopInt
int PopInt()
Definition
cpdf_psengine.cpp:204
CPDF_PSEngine::Parse
bool Parse(pdfium::span< const uint8_t > input)
Definition
cpdf_psengine.cpp:208
CPDF_PSEngine::Push
void Push(float value)
Definition
cpdf_psengine.cpp:195
CPDF_PSEngine::CPDF_PSEngine
CPDF_PSEngine()
CPDF_PSEngine::Execute
bool Execute()
Definition
cpdf_psengine.cpp:113
CPDF_PSEngine::Pop
float Pop()
Definition
cpdf_psengine.cpp:200
CPDF_PSEngine::~CPDF_PSEngine
~CPDF_PSEngine()
CPDF_PSOP
Definition
cpdf_psengine.h:70
CPDF_PSOP::Execute
void Execute(CPDF_PSEngine *pEngine)
Definition
cpdf_psengine.cpp:103
CPDF_PSOP::GetFloatValue
float GetFloatValue() const
Definition
cpdf_psengine.cpp:108
CPDF_PSOP::Parse
bool Parse(CPDF_SimpleParser *parser, int depth)
Definition
cpdf_psengine.cpp:98
CPDF_PSOP::~CPDF_PSOP
~CPDF_PSOP()
CPDF_PSOP::CPDF_PSOP
CPDF_PSOP(float value)
Definition
cpdf_psengine.cpp:94
CPDF_PSOP::CPDF_PSOP
CPDF_PSOP()
Definition
cpdf_psengine.cpp:86
CPDF_PSOP::CPDF_PSOP
CPDF_PSOP(PDF_PSOP op)
Definition
cpdf_psengine.cpp:89
CPDF_PSProc
Definition
cpdf_psengine.h:88
CPDF_PSProc::Execute
bool Execute(CPDF_PSEngine *pEngine)
Definition
cpdf_psengine.cpp:144
CPDF_PSProc::~CPDF_PSProc
~CPDF_PSProc()
CPDF_PSProc::Parse
bool Parse(CPDF_SimpleParser *parser, int depth)
Definition
cpdf_psengine.cpp:121
CPDF_PSProc::CPDF_PSProc
CPDF_PSProc()
CPDF_PSProc::AddOperatorForTesting
void AddOperatorForTesting(ByteStringView word)
Definition
cpdf_psengine.cpp:175
CPDF_SimpleParser
Definition
cpdf_simple_parser.h:17
CPDF_SimpleParser::GetWord
ByteStringView GetWord()
Definition
cpdf_simple_parser.cpp:21
PDF_PSOP
PDF_PSOP
Definition
cpdf_psengine.h:23
PSOP_COS
@ PSOP_COS
Definition
cpdf_psengine.h:38
PSOP_FLOOR
@ PSOP_FLOOR
Definition
cpdf_psengine.h:33
PSOP_LT
@ PSOP_LT
Definition
cpdf_psengine.h:49
PSOP_LOG
@ PSOP_LOG
Definition
cpdf_psengine.h:42
PSOP_DIV
@ PSOP_DIV
Definition
cpdf_psengine.h:27
PSOP_DUP
@ PSOP_DUP
Definition
cpdf_psengine.h:62
PSOP_GE
@ PSOP_GE
Definition
cpdf_psengine.h:48
PSOP_NEG
@ PSOP_NEG
Definition
cpdf_psengine.h:30
PSOP_LN
@ PSOP_LN
Definition
cpdf_psengine.h:41
PSOP_OR
@ PSOP_OR
Definition
cpdf_psengine.h:52
PSOP_EQ
@ PSOP_EQ
Definition
cpdf_psengine.h:45
PSOP_MOD
@ PSOP_MOD
Definition
cpdf_psengine.h:29
PSOP_IDIV
@ PSOP_IDIV
Definition
cpdf_psengine.h:28
PSOP_ATAN
@ PSOP_ATAN
Definition
cpdf_psengine.h:39
PSOP_CVR
@ PSOP_CVR
Definition
cpdf_psengine.h:44
PSOP_SUB
@ PSOP_SUB
Definition
cpdf_psengine.h:25
PSOP_EXP
@ PSOP_EXP
Definition
cpdf_psengine.h:40
PSOP_ADD
@ PSOP_ADD
Definition
cpdf_psengine.h:24
PSOP_ROUND
@ PSOP_ROUND
Definition
cpdf_psengine.h:34
PSOP_CONST
@ PSOP_CONST
Definition
cpdf_psengine.h:67
PSOP_IF
@ PSOP_IF
Definition
cpdf_psengine.h:58
PSOP_NE
@ PSOP_NE
Definition
cpdf_psengine.h:46
PSOP_PROC
@ PSOP_PROC
Definition
cpdf_psengine.h:66
PSOP_FALSE
@ PSOP_FALSE
Definition
cpdf_psengine.h:57
PSOP_TRUE
@ PSOP_TRUE
Definition
cpdf_psengine.h:56
PSOP_MUL
@ PSOP_MUL
Definition
cpdf_psengine.h:26
PSOP_ROLL
@ PSOP_ROLL
Definition
cpdf_psengine.h:65
PSOP_AND
@ PSOP_AND
Definition
cpdf_psengine.h:51
PSOP_GT
@ PSOP_GT
Definition
cpdf_psengine.h:47
PSOP_CEILING
@ PSOP_CEILING
Definition
cpdf_psengine.h:32
PSOP_POP
@ PSOP_POP
Definition
cpdf_psengine.h:60
PSOP_EXCH
@ PSOP_EXCH
Definition
cpdf_psengine.h:61
PSOP_BITSHIFT
@ PSOP_BITSHIFT
Definition
cpdf_psengine.h:55
PSOP_SQRT
@ PSOP_SQRT
Definition
cpdf_psengine.h:36
PSOP_COPY
@ PSOP_COPY
Definition
cpdf_psengine.h:63
PSOP_IFELSE
@ PSOP_IFELSE
Definition
cpdf_psengine.h:59
PSOP_NOT
@ PSOP_NOT
Definition
cpdf_psengine.h:54
PSOP_ABS
@ PSOP_ABS
Definition
cpdf_psengine.h:31
PSOP_CVI
@ PSOP_CVI
Definition
cpdf_psengine.h:43
PSOP_SIN
@ PSOP_SIN
Definition
cpdf_psengine.h:37
PSOP_TRUNCATE
@ PSOP_TRUNCATE
Definition
cpdf_psengine.h:35
PSOP_XOR
@ PSOP_XOR
Definition
cpdf_psengine.h:53
PSOP_LE
@ PSOP_LE
Definition
cpdf_psengine.h:50
PSOP_INDEX
@ PSOP_INDEX
Definition
cpdf_psengine.h:64
FX_SAFE_INT32
pdfium::CheckedNumeric< int32_t > FX_SAFE_INT32
Definition
fx_safe_types.h:15
FXSYS_PI
#define FXSYS_PI
Definition
fx_system.h:44
std
[33]
Definition
src_corelib_tools_qhash.cpp:421
ByteStringView
fxcrt::ByteStringView ByteStringView
Definition
string_view_template.h:325
qtwebengine
src
3rdparty
chromium
third_party
pdfium
core
fpdfapi
page
cpdf_psengine.cpp
Generated on
for Qt by
1.18.0