1 // Written in the D programming language.
2 
3 /**
4 This module declares basic data types for usage in dlangui library.
5 
6 Contains reference counting support, point and rect structures, character glyph structure, misc utility functions.
7 
8 Synopsis:
9 
10 ----
11 import dlangui.core.types;
12 
13 // points
14 Point p(5, 10);
15 
16 // rectangles
17 Rect r(5, 13, 120, 200);
18 writeln(r);
19 
20 // reference counted objects, useful for RAII / resource management.
21 class Foo : RefCountedObject {
22     int[] resource;
23     ~this() {
24         writeln("freeing Foo resources");
25     }
26 }
27 {
28     Ref!Foo ref1;
29     {
30         Ref!Foo fooRef = new RefCountedObject();
31         ref1 = fooRef;
32     }
33     // RAII: will destroy object when no more references
34 }
35 
36 ----
37 
38 Copyright: Vadim Lopatin, 2014
39 License:   Boost License 1.0
40 Authors:   Vadim Lopatin, coolreader.org@gmail.com
41 */
42 module dlangui.core.types;
43 
44 public import dlangui.core.config;
45 
46 import std.algorithm;
47 
48 /// 2D point
49 struct Point {
50     int x;
51     int y;
52 
53 @safe @nogc nothrow:
54     Point opBinary(string op)(Point v) const if (op == "+") {
55         return Point(x + v.x, y + v.y);
56     }
57     Point opBinary(string op)(int n) const if (op == "*") {
58         return Point(x * n, y * n);
59     }
60     Point opBinary(string op)(Point v) const if (op == "-") {
61         return Point(x - v.x, y - v.y);
62     }
63     Point opUnary(string op)() const if (op == "-") {
64         return Point(-x, -y);
65     }
66     int opCmp(ref const Point b) const {
67         if (x == b.x) return y - b.y;
68         return x - b.x;
69     }
70     int opCmp(const Point b) const {
71         if (x == b.x) return y - b.y;
72         return x - b.x;
73     }
74 }
75 
76 // Point unittest
77 @safe @nogc unittest
78 {
79     immutable testA = Point(100, 100);
80     immutable testB = Point(50, 50);
81     assert(testA + testA == Point(200, 200));
82     assert(testA * 2 == Point(200, 200));
83     assert(testA - testA == Point(0, 0));
84     assert(-testA == Point(-100, -100));
85     assert(testA > Point(50, 50));
86     assert(testA > testB);
87 }
88 
89 /** 2D rectangle
90     Note: Rect(0,0,20,10) size is 20x10 -- right and bottom sides are non-inclusive -- if you draw such rect, rightmost drawn pixel will be x=19 and bottom pixel y=9
91 */
92 struct Rect {
93     /// x coordinate of top left corner
94     int left;
95     /// y coordinate of top left corner
96     int top;
97     /// x coordinate of bottom right corner (non-inclusive)
98     int right;
99     /// y coordinate of bottom right corner (non-inclusive)
100     int bottom;
101 
102 @safe @nogc nothrow:
103     /// returns average of left, right
104     @property int middlex() const { return (left + right) / 2; }
105     /// returns average of top, bottom
106     @property int middley() const { return (top + bottom) / 2; }
107     /// returns middle point
108     @property Point middle() const { return Point(middlex, middley); }
109 
110     /// returns top left point of rectangle
111     @property Point topLeft() const { return Point(left, top); }
112     /// returns bottom right point of rectangle
113     @property Point bottomRight() const { return Point(right, bottom); }
114     /// returns top right point of rectangel
115     @property Point topRight() const { return Point(right, top); }
116     /// returns bottom left point of rectangle
117     @property Point bottomLeft() const { return Point(left, bottom); }
118 
119     /// returns size (width, height) in Point
120     @property Point size() const { return Point(right - left, bottom - top); }
121 
122     /// add offset to horizontal and vertical coordinates
123     void offset(int dx, int dy) {
124         left += dx;
125         right += dx;
126         top += dy;
127         bottom += dy;
128     }
129     /// expand rectangle dimensions
130     void expand(int dx, int dy) {
131         left -= dx;
132         right += dx;
133         top -= dy;
134         bottom += dy;
135     }
136     /// shrink rectangle dimensions
137     void shrink(int dx, int dy) {
138         left += dx;
139         right -= dx;
140         top += dy;
141         bottom -= dy;
142     }
143     /// for all fields, sets this.field to rc.field if rc.field > this.field
144     void setMax(Rect rc) {
145         if (left < rc.left)
146             left = rc.left;
147         if (right < rc.right)
148             right = rc.right;
149         if (top < rc.top)
150             top = rc.top;
151         if (bottom < rc.bottom)
152             bottom = rc.bottom;
153     }
154     /// returns width of rectangle (right - left)
155     @property int width() const { return right - left; }
156     /// returns height of rectangle (bottom - top)
157     @property int height() const { return bottom - top; }
158     /// constructs rectangle using left, top, right, bottom coordinates
159     this(int x0, int y0, int x1, int y1) {
160         left = x0;
161         top = y0;
162         right = x1;
163         bottom = y1;
164     }
165     /// constructs rectangle using two points - (left, top), (right, bottom) coordinates
166     this(Point pt0, Point pt1) {
167         this(pt0.x, pt0.y, pt1.x, pt1.y);
168     }
169     /// returns true if rectangle is empty (right <= left || bottom <= top)
170     @property bool empty() const {
171         return right <= left || bottom <= top;
172     }
173     /// translate rectangle coordinates by (x,y) - add deltax to x coordinates, and deltay to y coordinates
174     alias moveBy = offset;
175     /// moves this rect to fit rc bounds, retaining the same size
176     void moveToFit(ref Rect rc) {
177         if (right > rc.right)
178             moveBy(rc.right - right, 0);
179         if (bottom > rc.bottom)
180             moveBy(0, rc.bottom - bottom);
181         if (left < rc.left)
182             moveBy(rc.left - left, 0);
183         if (top < rc.top)
184             moveBy(0, rc.top - top);
185 
186     }
187     /// updates this rect to intersection with rc, returns true if result is non empty
188     bool intersect(Rect rc) {
189         if (left < rc.left)
190             left = rc.left;
191         if (top < rc.top)
192             top = rc.top;
193         if (right > rc.right)
194             right = rc.right;
195         if (bottom > rc.bottom)
196             bottom = rc.bottom;
197         return right > left && bottom > top;
198     }
199     /// returns true if this rect has nonempty intersection with rc
200     bool intersects(Rect rc) const {
201         if (rc.left >= right || rc.top >= bottom || rc.right <= left || rc.bottom <= top)
202             return false;
203         return true;
204     }
205     /// returns true if point is inside of this rectangle
206     bool isPointInside(Point pt) const {
207         return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom;
208     }
209     /// returns true if point is inside of this rectangle
210     bool isPointInside(int x, int y) const {
211         return x >= left && x < right && y >= top && y < bottom;
212     }
213     /// this rectangle is completely inside rc
214     bool isInsideOf(Rect rc) const {
215         return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom;
216     }
217 
218     bool opEquals(Rect rc) const {
219         return left == rc.left && right == rc.right && top == rc.top && bottom == rc.bottom;
220     }
221 }
222 
223 // Rect unittests
224 @safe @nogc unittest
225 {
226     immutable testA = Rect(0, 0, 100, 100);
227     assert(testA.width == 100);
228     assert(testA.height == 100);
229     assert(testA.middlex == 50);
230     assert(testA.middley == 50);
231     assert(testA.middle == Point(50, 50));
232     assert(testA == Rect(0, 0, 100, 100));
233     assert(testA == Rect(Point(0, 0), Point(100, 100)));
234     assert(testA.topLeft == Point(0, 0));
235     assert(testA.topRight == Point(100, 0));
236     assert(testA.bottomLeft == Point(0, 100));
237     assert(testA.bottomRight == Point(100, 100));
238     assert(testA.size == Point(100, 100));
239     assert(!testA.empty);
240 }
241 
242 /// constant acting as "rectangle not set" value
243 immutable Rect RECT_VALUE_IS_NOT_SET = Rect(int.min, int.min, int.min, int.min);
244 
245 /// widget state bit flags
246 enum State : uint {
247     /// state not specified / normal
248     Normal = 4 | 256, // Normal is Enabled
249     /// pressed (e.g. clicked by mouse)
250     Pressed = 1,
251     /// widget has focus
252     Focused = 2,
253     /// widget can process mouse and key events
254     Enabled = 4,
255     /// mouse pointer is over this widget
256     Hovered = 8, // mouse pointer is over control, buttons not pressed
257     /// widget is selected
258     Selected = 16,
259     /// widget can be checked
260     Checkable = 32,
261     /// widget is checked
262     Checked = 64,
263     /// widget is activated
264     Activated = 128,
265     /// window is focused
266     WindowFocused = 256,
267     /// widget is default control for form (should be focused when window gains focus first time)
268     Default = 512, // widget is default for form (e.g. default button will be focused on show)
269     /// widget has been focused by keyboard navigation
270     KeyboardFocused = 1024,
271     /// return state of parent instead of widget's state when requested
272     Parent = 0x10000, // use parent's state
273 }
274 
275 
276 // Layout size constants
277 /// layout option, to occupy all available place
278 enum int FILL_PARENT = 0x4000_0000;
279 /// layout option, for size based on content
280 enum int WRAP_CONTENT = 0x2000_0000;
281 /// use as widget.layout() param to avoid applying of parent size
282 enum int SIZE_UNSPECIFIED = 0x6000_0000;
283 
284 /// use in styles to specify size in points (1/72 inch)
285 enum int SIZE_IN_POINTS_FLAG = 0x1000_0000;
286 /// (RESERVED) use in styles to specify size in percents * 100 (e.g. 0 == 0%, 10000 == 100%, 100 = 1%)
287 enum int SIZE_IN_PERCENTS_FLAG = 0x0800_0000;
288 
289 
290 /// convert custom size to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set)
291 int toPixels(int sz) {
292     if (sz > 0 && (sz & SIZE_IN_POINTS_FLAG) != 0) {
293         return pointsToPixels(sz ^ SIZE_IN_POINTS_FLAG);
294     }
295     return sz;
296 }
297 
298 /// convert custom size Point to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set)
299 Point toPixels(const Point sz) {
300     return Point(toPixels(sz.x), toPixels(sz.y));
301 }
302 
303 /// convert custom size Rect to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set)
304 Rect toPixels(const Rect sz) {
305     return Rect(toPixels(sz.left), toPixels(sz.top), toPixels(sz.right), toPixels(sz.bottom));
306 }
307 
308 /// make size value with SIZE_IN_POINTS_FLAG set
309 int makePointSize(int pt) {
310     return pt | SIZE_IN_POINTS_FLAG;
311 }
312 
313 /// make size value with SIZE_IN_PERCENTS_FLAG set
314 int makePercentSize(int percent) {
315     return (percent * 100) | SIZE_IN_PERCENTS_FLAG;
316 }
317 
318 /// make size value with SIZE_IN_PERCENTS_FLAG set
319 int makePercentSize(double percent) {
320     return cast(int)(percent * 100) | SIZE_IN_PERCENTS_FLAG;
321 }
322 
323 /// returns true for WRAP_CONTENT, WRAP_CONTENT, SIZE_UNSPECIFIED
324 bool isSpecialSize(int sz) {
325     // don't forget to update if more special constants added
326     return (sz & (WRAP_CONTENT | FILL_PARENT | SIZE_UNSPECIFIED)) != 0;
327 }
328 
329 /// returns true if size has SIZE_IN_PERCENTS_FLAG bit set
330 bool isPercentSize(int size) {
331     return (size & SIZE_IN_PERCENTS_FLAG) != 0;
332 }
333 
334 /// if size has SIZE_IN_PERCENTS_FLAG bit set, returns percent of baseSize, otherwise returns size unchanged
335 int fromPercentSize(int size, int baseSize) {
336     if (isPercentSize(size))
337         return cast(int)(cast(long)(size & ~SIZE_IN_PERCENTS_FLAG) * baseSize / 10000);
338     return size;
339 }
340 
341 /// screen dots per inch
342 private __gshared int PRIVATE_SCREEN_DPI = 96;
343 /// value to override detected system DPI, 0 to disable overriding
344 private __gshared int PRIVATE_SCREEN_DPI_OVERRIDE = 0;
345 
346 /// get current screen DPI used for scaling while drawing
347 @property int SCREEN_DPI() {
348     return PRIVATE_SCREEN_DPI_OVERRIDE ? PRIVATE_SCREEN_DPI_OVERRIDE : PRIVATE_SCREEN_DPI;
349 }
350 
351 /// get screen DPI detection override value, if non 0 - this value is used instead of DPI detected by platform, if 0, value detected by platform will be used
352 @property int overrideScreenDPI() {
353     return PRIVATE_SCREEN_DPI_OVERRIDE;
354 }
355 
356 /// call to disable automatic screen DPI detection, use provided one instead (pass 0 to disable override and use value detected by platform)
357 @property void overrideScreenDPI(int dpi = 96) {
358     static if (WIDGET_STYLE_CONSOLE) {
359     } else {
360         if ((dpi >= 72 && dpi <= 500) || dpi == 0)
361             PRIVATE_SCREEN_DPI_OVERRIDE = dpi;
362     }
363 }
364 
365 /// set screen DPI detected by platform
366 @property void SCREEN_DPI(int dpi) {
367     static if (WIDGET_STYLE_CONSOLE) {
368         PRIVATE_SCREEN_DPI = dpi;
369     } else {
370         if (dpi >= 72 && dpi <= 500) {
371             if (PRIVATE_SCREEN_DPI != dpi) {
372                 // changed DPI
373                 PRIVATE_SCREEN_DPI = dpi;
374             }
375         }
376     }
377 }
378 
379 /// returns DPI detected by platform w/o override
380 @property int systemScreenDPI() {
381     return PRIVATE_SCREEN_DPI;
382 }
383 
384 /// one point is 1/72 of inch
385 enum POINTS_PER_INCH = 72;
386 
387 /// convert length in points (1/72in units) to pixels according to SCREEN_DPI
388 int pointsToPixels(int pt) {
389     return pt * SCREEN_DPI / POINTS_PER_INCH;
390 }
391 
392 /// rectangle coordinates in points (1/72in units) to pixels according to SCREEN_DPI
393 Rect pointsToPixels(Rect rc) {
394     return Rect(rc.left.pointsToPixels, rc.top.pointsToPixels, rc.right.pointsToPixels, rc.bottom.pointsToPixels);
395 }
396 
397 /// convert points (1/72in units) to pixels according to SCREEN_DPI
398 int pixelsToPoints(int px) {
399     return px * POINTS_PER_INCH / SCREEN_DPI;
400 }
401 
402 /// Subpixel rendering mode for fonts (aka ClearType)
403 enum SubpixelRenderingMode : ubyte {
404     /// no sub
405     None,
406     /// subpixel rendering on, subpixel order on device: B,G,R
407     BGR,
408     /// subpixel rendering on, subpixel order on device: R,G,B
409     RGB,
410 }
411 
412 /**
413     Character glyph.
414 
415     Holder for glyph metrics as well as image.
416 */
417 align(1)
418 struct Glyph
419 {
420     static if (ENABLE_OPENGL) {
421         /// 0: unique id of glyph (for drawing in hardware accelerated scenes)
422         uint    id;
423     }
424 
425     /// 0: width of glyph black box
426     ushort   blackBoxX;
427 
428     @property ushort correctedBlackBoxX() { return subpixelMode ? (blackBoxX + 2) / 3 : blackBoxX; }
429 
430 
431     /// 2: height of glyph black box
432     ubyte   blackBoxY;
433     /// 3: X origin for glyph
434     byte    originX;
435     /// 4: Y origin for glyph
436     byte    originY;
437 
438     /// 5: full width of glyph
439     ubyte   widthPixels;
440     /// 6: full width of glyph scaled * 64
441     ushort   widthScaled;
442     /// 8: subpixel rendering mode - if !=SubpixelRenderingMode.None, glyph data contains 3 bytes per pixel instead of 1
443     SubpixelRenderingMode subpixelMode;
444     /// 9: usage flag, to handle cleanup of unused glyphs
445     ubyte   lastUsage;
446 
447     ///< 10: glyph data, arbitrary size (blackBoxX * blackBoxY)
448     ubyte[] glyph;
449 }
450 
451 /**
452     Base class for reference counted objects, maintains reference counter inplace.
453 
454     If some class is not inherited from RefCountedObject, additional object will be required to hold counters.
455 */
456 class RefCountedObject {
457     /// count of references to this object from Ref
458     protected int _refCount;
459     /// returns current value of reference counter
460     @property int refCount() const { return _refCount; }
461     /// increments reference counter
462     void addRef() {
463         _refCount++;
464     }
465     /// decrement reference counter, destroy object if no more references left
466     void releaseRef() {
467         if (--_refCount == 0)
468             destroy(this);
469     }
470     ~this() {}
471 }
472 
473 /**
474     Reference counting support.
475 
476     Implemented for case when T is RefCountedObject.
477     Similar to shared_ptr in C++.
478     Allows to share object, destroying it when no more references left.
479 
480     Useful for automatic destroy of objects.
481 */
482 struct Ref(T) { // if (T is RefCountedObject)
483     private T _data;
484     alias get this;
485     /// returns true if object is not assigned
486     @property bool isNull() const { return _data is null; }
487     /// returns counter of references
488     @property int refCount() const { return _data !is null ? _data.refCount : 0; }
489     /// init from T
490     this(T data) {
491         _data = data;
492         if (_data !is null)
493             _data.addRef();
494     }
495     /// after blit
496     this(this) {
497         if (_data !is null)
498             _data.addRef();
499     }
500     /// assign from another refcount by reference
501     ref Ref opAssign(ref Ref data) {
502         if (data._data == _data)
503             return this;
504         if (_data !is null)
505             _data.releaseRef();
506         _data = data._data;
507         if (_data !is null)
508             _data.addRef();
509         return this;
510     }
511     /// assign from another refcount by value
512     ref Ref opAssign(Ref data) {
513         if (data._data == _data)
514             return this;
515         if (_data !is null)
516             _data.releaseRef();
517         _data = data._data;
518         if (_data !is null)
519             _data.addRef();
520         return this;
521     }
522     /// assign object
523     ref Ref opAssign(T data) {
524         if (data == _data)
525             return this;
526         if (_data !is null)
527             _data.releaseRef();
528         _data = data;
529         if (_data !is null)
530             _data.addRef();
531         return this;
532     }
533     /// clears reference
534     void clear() {
535         if (_data !is null) {
536             _data.releaseRef();
537             _data = null;
538         }
539     }
540     /// returns object reference (null if not assigned)
541     @property T get() {
542         return _data;
543     }
544     /// returns const reference from const object
545     @property const(T) get() const {
546         return _data;
547     }
548     /// decreases counter, and destroys object if no more references left
549     ~this() {
550         if (_data !is null)
551             _data.releaseRef();
552     }
553 }
554 
555 
556 /**
557     This struct allows to not execute some code if some variables was not changed since the last check.
558     Used for optimizations.
559 
560     Reference types, arrays and pointers are compared by reference.
561  */
562 struct CalcSaver(Params...) {
563     import std.typecons : Tuple;
564     Tuple!Params values;
565 
566     bool check(Params args) {
567         bool changed;
568         foreach (i, arg; args) {
569             if (values[i] !is arg) {
570                 values[i] = arg;
571                 changed = true;
572             }
573         }
574         return changed;
575     }
576 }
577 
578 ///
579 unittest {
580 
581     class A { }
582 
583     CalcSaver!(uint, double[], A) saver;
584 
585     uint x = 5;
586     double[] arr = [1, 2, 3];
587     A a = new A();
588 
589     assert(saver.check(x, arr, a));
590     // values are not changing
591     assert(!saver.check(x, arr, a));
592     assert(!saver.check(x, arr, a));
593     assert(!saver.check(x, arr, a));
594     assert(!saver.check(x, arr, a));
595 
596     x = 8;
597     arr ~= 25;
598     a = new A();
599     // values are changed
600     assert(saver.check(x, arr, a));
601     assert(!saver.check(x, arr, a));
602 }
603 
604 
605 //================================================================================
606 // some utility functions
607 
608 /** conversion from wchar z-string */
609 wstring fromWStringz(const(wchar[]) s) {
610     if (s is null)
611         return null;
612     int i = 0;
613     while(s[i])
614         i++;
615     return cast(wstring)(s[0..i].dup);
616 }
617 
618 /** conversion from wchar z-string */
619 wstring fromWStringz(const(wchar) * s) {
620     if (s is null)
621         return null;
622     int i = 0;
623     while(s[i])
624         i++;
625     return cast(wstring)(s[0..i].dup);
626 }
627 
628 /** Deprecated: use std.uni.toUpper instead.
629     Uppercase unicode character.
630 */
631 deprecated dchar dcharToUpper(dchar ch) {
632     static import std.uni;
633     return std.uni.toUpper(ch);
634 }
635 
636 /// decodes hex digit (0..9, a..f, A..F), returns uint.max if invalid
637 uint parseHexDigit(T)(T ch) pure nothrow {
638     if (ch >= '0' && ch <= '9')
639         return ch - '0';
640     else if (ch >= 'a' && ch <= 'f')
641         return ch - 'a' + 10;
642     else if (ch >= 'A' && ch <= 'F')
643         return ch - 'A' + 10;
644     return uint.max;
645 }
646 
647 /// replacement of deprecated std.utf.toUTF8
648 string toUTF8(dstring str) {
649     import std.utf : encode, codeLength, byUTF;
650     char[] buf;
651     buf.length = codeLength!char(str);
652     int pos = 0;
653     foreach(ch; str.byUTF!char) {
654         buf.ptr[pos++] = ch;
655     }
656     return cast(string)buf;
657 }
658 
659 /// normalize end of line style - convert to '\n'
660 dstring normalizeEndOfLineCharacters(dstring s) {
661     bool crFound = false;
662     foreach(ch; s) {
663         if (ch == '\r') {
664             crFound = true;
665             break;
666         }
667     }
668     if (!crFound)
669         return s;
670     dchar[] res;
671     res.reserve(s.length);
672     dchar prevCh = 0;
673     foreach(ch; s) {
674         if (ch == '\r') {
675             res ~= '\n';
676         } else if (ch == '\n') {
677             if (prevCh != '\r')
678                 res ~= '\n';
679         } else {
680             res ~= ch;
681         }
682         prevCh = ch;
683     }
684     return cast(dstring)res;
685 }
686 
687 /// C malloc allocated array wrapper
688 struct MallocBuf(T) {
689     import core.stdc.stdlib : realloc, free;
690     private T * _allocated;
691     private uint _allocatedSize;
692     private uint _length;
693     /// get pointer
694     @property T * ptr() { return _allocated; }
695     /// get length
696     @property uint length() { return _length; }
697     /// set new length
698     @property void length(uint len) {
699         if (len > _allocatedSize) {
700             reserve(_allocatedSize ? len * 2 : len);
701         }
702         _length = len;
703     }
704     /// const array[index];
705     T opIndex(uint index) const {
706         assert(index < _length);
707         return _allocated[index];
708     }
709     /// ref array[index];
710     ref T opIndex(uint index) {
711         assert(index < _length);
712         return _allocated[index];
713     }
714     /// array[index] = value;
715     void opIndexAssign(uint index, T value) {
716         assert(index < _length);
717         _allocated[index] = value;
718     }
719     /// array[index] = value;
720     void opIndexAssign(uint index, T[] values) {
721         assert(index + values.length < _length);
722         _allocated[index .. index + values.length] = values[];
723     }
724     /// array[a..b]
725     T[] opSlice(uint a, uint b) {
726         assert(a <= b && b <= _length);
727         return _allocated[a .. b];
728     }
729     /// array[]
730     T[] opSlice() {
731         return _allocated ? _allocated[0 .. _length] : null;
732     }
733     /// array[$]
734     uint opDollar() { return _length; }
735     ~this() {
736         clear();
737     }
738     /// free allocated memory, set length to 0
739     void clear() {
740         if (_allocated)
741             free(_allocated);
742         _allocatedSize = 0;
743         _length = 0;
744     }
745     /// make sure buffer capacity is at least (size) items
746     void reserve(uint size) {
747         if (_allocatedSize < size) {
748             _allocated = cast(T*)realloc(_allocated, T.sizeof * size);
749             _allocatedSize = size;
750         }
751     }
752     /// fill buffer with specified value
753     void fill(T value) {
754         if (_length) {
755             _allocated[0 .. _length] = value;
756         }
757     }
758 }