1 // 2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 3 // Free Software Foundation, Inc 4 // 5 // This program is free software; you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation; either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with this program; if not, write to the Free Software 17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 19 // 20 // Original author: Sandro Santilli <strk@keybit.net> 21 // 22 23 #ifndef GNASH_RANGE2D_H 24 #define GNASH_RANGE2D_H 25 26 #include <ostream> 27 #include <limits> 28 #include <algorithm> 29 #include <cassert> // for inlines 30 #include <cmath> // for floor / ceil 31 #include <cstdint> 32 33 namespace gnash { 34 35 namespace geometry { 36 37 /// Kinds of a range 38 enum RangeKind { 39 /// Valid range, using finite values 40 finiteRange, 41 42 /// A NULL range is a range enclosing NO points. 43 nullRange, 44 45 /// \brief 46 /// A WORLD range2d is a range including 47 /// all points on the plane. 48 // 49 /// Note that scaling, shifting and unioning 50 /// will NOT change a WORLD range. 51 /// 52 worldRange 53 }; 54 55 namespace detail { 56 template <typename U> struct Promote { typedef U type; }; 57 template <> struct Promote<float> { typedef double type; }; 58 template <> struct Promote<int> { typedef std::int64_t type; }; 59 template <> struct Promote<unsigned int> { typedef std::uint64_t type; }; 60 } 61 62 /// 2d Range template class 63 // 64 /// The class stores 4 values of the type specified 65 /// as template argument, representing the set of points 66 /// enclosed by the given min and max values for the 2 dimensions, 67 /// and provides methods for manipulating them. 68 /// The parameter type must be a numeric type. 69 /// 70 /// The two dimensions are called X and Y. 71 /// 72 /// Note that the range is "open", which means that the points 73 /// on its boundary are considered internal to the range. 74 /// 75 /// 76 template <typename T> 77 class Range2d 78 { 79 public: 80 81 /// Ouput operator 82 template <typename U> 83 friend std::ostream& operator<< (std::ostream& os, const Range2d<U>& rect); 84 85 /// Equality operator 86 // 87 /// This is needed to take NULL kind into account 88 /// since we don't explicitly set all members when constructing 89 /// NULL ranges 90 /// 91 template <typename U> 92 friend bool operator== (const Range2d<U>& r1, const Range2d<U>& r2); 93 94 /// Inequality operator 95 // 96 /// This is needed to take NULL kind into account 97 /// since we don't explicitly set all members when constructing 98 /// NULL ranges 99 /// 100 template <typename U> 101 friend bool operator!= (const Range2d<U>& r1, const Range2d<U>& r2); 102 103 /// Return a rectangle being the intersetion of the two rectangles 104 // 105 /// Any NULL operand will make the result also NULL. 106 /// 107 template <typename U> friend Range2d<U> 108 Intersection(const Range2d<U>& r1, const Range2d<U>& r2); 109 110 /// Return a rectangle being the union of the two rectangles 111 template <typename U> friend Range2d<U> 112 Union(const Range2d<U>& r1, const Range2d<U>& r2); 113 114 /// Construct a Range2d of the given kind. 115 // 116 /// The default is building a nullRange. 117 /// If finiteRange is given the range will be set to 118 /// enclose the origin. 119 /// 120 /// See RangeKind 121 /// 122 Range2d(RangeKind kind=nullRange) 123 : 124 _xmin(T()), 125 _xmax(T()), 126 _ymin(T()), 127 _ymax(T()) 128 { 129 switch ( kind ) 130 { 131 case worldRange: 132 setWorld(); 133 break; 134 case nullRange: 135 setNull(); 136 break; 137 default: 138 case finiteRange: 139 break; 140 } 141 } 142 143 /// Construct a finite Range2d with the given values 144 // 145 /// Make sure that the min <= max, or an assertion 146 /// would fail. We could as well swap the values 147 /// in this case, but it is probably better to 148 /// force caller to deal with this, as a similar 149 /// case might as well expose a bug in the code. 150 /// 151 Range2d(T xmin, T ymin, T xmax, T ymax) 152 : 153 _xmin(xmin), 154 _xmax(xmax), 155 _ymin(ymin), 156 _ymax(ymax) 157 { 158 // use the default ctor to make a NULL Range2d 159 assert(_xmin <= _xmax); 160 assert(_ymin <= _ymax); 161 // .. or should we raise an exception .. ? 162 } 163 164 /// Templated copy constructor, for casting between range types 165 template <typename U> 166 Range2d(const Range2d<U>& from) 167 { 168 if ( from.isWorld() ) { 169 setWorld(); 170 } else if ( from.isNull() ) { 171 setNull(); 172 } else { 173 _xmin = roundMin(from.getMinX()); 174 _ymin = roundMin(from.getMinY()); 175 _xmax = roundMax(from.getMaxX()); 176 _ymax = roundMax(from.getMaxY()); 177 } 178 } 179 180 /// Returns true if this is the NULL Range2d 181 bool isNull() const 182 { 183 return _xmax < _xmin; 184 } 185 186 /// Set the Range2d to the NULL value 187 // 188 /// @return a reference to this instance 189 /// 190 Range2d<T>& setNull() 191 { 192 _xmin = std::numeric_limits<T>::max(); 193 _xmax = std::numeric_limits<T>::min(); 194 _ymin = 0; 195 _ymax = 0; 196 return *this; 197 } 198 199 /// Returns true if this is the WORLD Range2d 200 bool isWorld() const 201 { 202 return _xmax == std::numeric_limits<T>::max() 203 && _xmin == std::numeric_limits<T>::min(); 204 } 205 206 /// Returns true if this is a finite Range2d 207 // 208 /// See RangeKind::finiteRange 209 /// 210 bool isFinite() const 211 { 212 return ( ! isNull() && ! isWorld() ); 213 } 214 215 /// Set the Range2d to the WORLD value 216 // 217 /// This is implemented using the minimum and maximum 218 /// values of the parameter type. 219 /// 220 /// See RangeType::worldRange 221 /// 222 /// @return a reference to this instance 223 /// 224 Range2d<T>& setWorld() 225 { 226 _xmin = std::numeric_limits<T>::min(); 227 _xmax = std::numeric_limits<T>::max(); 228 _ymin = 0; 229 _ymax = 0; 230 return *this; 231 } 232 233 /// \brief 234 /// Return true if this rectangle contains the point with 235 /// given coordinates (boundaries are inclusive). 236 // 237 /// Note that WORLD rectangles contain every point 238 /// and NULL rectangles contain no point. 239 /// 240 template <typename U> 241 bool contains(U x, U y) const 242 { 243 if ( isNull() ) return false; 244 if ( isWorld() ) return true; 245 if (x < _xmin || x > _xmax || y < _ymin || y > _ymax) 246 { 247 return false; 248 } 249 return true; 250 } 251 252 /// \brief 253 /// Return true if this rectangle contains the given rectangle. 254 // 255 /// Note that: 256 /// 257 /// - WORLD ranges contain every range except NULL ones 258 /// and are only contained in WORLD ranges 259 /// 260 /// - NULL ranges contain no ranges and are contained in no ranges. 261 /// 262 bool contains(const Range2d<T>& other) const 263 { 264 if ( isNull() || other.isNull() ) return false; 265 if ( isWorld() ) return true; 266 if ( other.isWorld() ) return false; 267 268 return _xmin <= other._xmin && 269 _xmax >= other._xmax && 270 _ymin <= other._ymin && 271 _ymax >= other._ymax; 272 } 273 274 /// \brief 275 /// Return true if this rectangle intersects the point with 276 /// given coordinates (boundaries are inclusive). 277 // 278 /// Note that NULL rectangles don't intersect anything 279 /// and WORLD rectangles intersects everything except a NULL rectangle. 280 /// 281 bool intersects(const Range2d<T>& other) const 282 { 283 if ( isNull() || other.isNull() ) return false; 284 if ( isWorld() || other.isWorld() ) return true; 285 286 if ( _xmin > other._xmax ) return false; 287 if ( _xmax < other._xmin ) return false; 288 if ( _ymin > other._ymax ) return false; 289 if ( _ymax < other._ymin ) return false; 290 return true; 291 } 292 293 /// Expand this Range2d to enclose the given point. 294 // 295 /// @return a reference to this instance 296 /// 297 Range2d<T>& expandTo(T x, T y) 298 { 299 // A WORLD range already enclose every point 300 if ( isWorld() ) return *this; 301 302 if ( isNull() ) 303 { 304 setTo(x,y); 305 } 306 else 307 { 308 _xmin = std::min(_xmin, x); 309 _ymin = std::min(_ymin, y); 310 _xmax = std::max(_xmax, x); 311 _ymax = std::max(_ymax, y); 312 } 313 314 return *this; 315 } 316 317 /// Expand this Range2d to enclose the given circle. 318 // 319 /// @return a reference to this instance 320 /// 321 Range2d<T>& expandToCircle(T x, T y, T radius) 322 { 323 // A WORLD range already enclose every point 324 if ( isWorld() ) return *this; 325 326 expandTo(x-radius, y); 327 expandTo(x+radius, y); 328 329 expandTo(x, y-radius); 330 expandTo(x, y+radius); 331 332 return *this; 333 } 334 335 /// Set ourself to bound the given point 336 // 337 /// @return a reference to this instance 338 /// 339 Range2d<T>& setTo(T x, T y) 340 { 341 _xmin = _xmax = x; 342 _ymin = _ymax = y; 343 return *this; 344 } 345 346 /// Set coordinates to given values 347 // 348 /// Make sure that the min <= max, or an assertion 349 /// would fail. We could as well swap the values 350 /// in this case, but it is probably better to 351 /// force caller to deal with this, as a similar 352 /// case might as well expose a bug in the code. 353 // 354 /// @return a reference to this instance 355 /// 356 Range2d<T>& setTo(T xmin, T ymin, T xmax, T ymax) 357 { 358 _xmin = xmin; 359 _xmax = xmax; 360 _ymin = ymin; 361 _ymax = ymax; 362 363 // use the default ctor to make a NULL Range2d 364 assert(_xmin <= _xmax); 365 assert(_ymin <= _ymax); 366 367 return *this; 368 } 369 370 /// Return width this Range2d 371 // 372 /// Don't call this function on a WORLD rectangle! 373 /// 374 T width() const 375 { 376 assert ( ! isWorld() ); 377 if ( isNull() ) return 0; 378 return _xmax-_xmin; 379 } 380 381 /// Return height this Range2dangle 382 // 383 /// Don't call this function on a WORLD rectangle! 384 /// 385 T height() const 386 { 387 assert ( ! isWorld() ); 388 if ( isNull() ) return 0; 389 return _ymax-_ymin; 390 } 391 392 /// Shift this Range2dangle horizontally 393 // 394 /// A positive offset will shift to the right, 395 /// A negative offset will shift to the left. 396 /// 397 /// WORLD or NULL ranges will be unchanged 398 /// 399 /// @return a reference to this instance 400 /// 401 Range2d<T>& shiftX(T offset) 402 { 403 if ( isNull() || isWorld() ) return *this; 404 _xmin += offset; 405 _xmax += offset; 406 return *this; 407 } 408 409 /// Shift this Range2dangle vertically 410 // 411 /// A positive offset will increment y values. 412 /// A negative offset will decrement y values. 413 /// 414 /// WORLD or NULL ranges will be unchanged 415 /// 416 /// @return a reference to this instance 417 /// 418 Range2d<T>& shiftY(T offset) 419 { 420 if ( isNull() || isWorld() ) return *this; 421 _ymin += offset; 422 _ymax += offset; 423 return *this; 424 } 425 426 /// Scale this Range2d horizontally 427 Range2d<T>& scaleX(float factor) 428 { 429 return scale(factor, 1); 430 } 431 432 /// Scale this Range2d vertically 433 Range2d<T>& scaleY(float factor) 434 { 435 return scale(1, factor); 436 } 437 438 /// Scale this Range2d 439 // 440 /// WORLD or NULL ranges will be unchanged 441 /// 442 /// For finite ranges: 443 /// Any factor of 0 will make the range NULL. 444 /// A factor of 1 will leave the corresponding size unchanged. 445 /// A factor > 1 will make the corresponding size bigger. 446 /// A factor < 1 factor will make the corresponding size smaller. 447 /// 448 /// Computation is done in single floating point precision. 449 /// Specializations for integer types ensure that when rounding 450 /// back the resulting range is not smaller then the floating 451 /// range computed during scaling (in all directions). 452 /// 453 /// Control point is the origin (0,0). 454 /// 455 /// If the range so scaled will hit the numerical limit 456 /// of the range an assertion will fail 457 /// (TODO: throw an exception instead!). 458 /// 459 /// @param xfactor 460 /// The horizontal scale factor. It's a float 461 /// to allow for fractional scale even for integer 462 /// ranges. 463 /// 464 /// @param yfactor 465 /// The vertical scale factor. It's a float 466 /// to allow for fractional scale even for integer 467 /// ranges. 468 /// 469 /// @return a reference to this instance 470 /// 471 Range2d<T>& scale(float xfactor, float yfactor) 472 { 473 assert(xfactor >= 0 && yfactor >= 0); 474 475 if ( ! isFinite() ) return *this; 476 477 if ( xfactor == 0 || yfactor == 0 ) 478 { 479 return setNull(); 480 } 481 482 if ( xfactor != 1 ) 483 { 484 _xmin = scaleMin(_xmin, xfactor); 485 _xmax = scaleMax(_xmax, xfactor); 486 assert(_xmin <= _xmax); // in case of overflow... 487 } 488 489 if ( yfactor != 1 ) 490 { 491 _ymin = scaleMin(_ymin, yfactor); 492 _ymax = scaleMax(_ymax, yfactor); 493 assert(_ymin <= _ymax); // in case of overflow... 494 } 495 496 return *this; 497 } 498 499 /// Scale this Range2d in both directions with the same factor 500 Range2d<T>& scale(float factor) 501 { 502 return scale(factor, factor); 503 } 504 505 /// Grow this range by the given amout in all directions. 506 // 507 /// WORLD or NULL ranges will be unchanged. 508 /// 509 /// If a growing range hits the numerical limit for T 510 /// it will be set to the WORLD range. 511 /// 512 /// @param amount 513 /// The amount of T to grow this range in all directions. 514 /// If negative the range will shrink. 515 /// If negative the range will shrink. 516 /// See shrinkBy(). 517 /// 518 /// @return a reference to this instance 519 /// 520 Range2d<T>& growBy(T amount) 521 { 522 if ( isNull() || isWorld() || amount==0 ) return *this; 523 524 // NOTE: this trigger a compiler warning when T is an 525 // unsigned type (Coverity CID 1154656 - 526 // logically dead code) 527 if ( amount < 0 ) return shrinkBy(-amount); 528 529 T newxmin = _xmin - amount; 530 if (newxmin > _xmin ) return setWorld(); 531 else _xmin = newxmin; 532 533 T newxmax = _xmax + amount; 534 if (newxmax < _xmax ) return setWorld(); 535 else _xmax = newxmax; 536 537 T newymin = _ymin - amount; 538 if (newymin > _ymin ) return setWorld(); 539 else _ymin = newymin; 540 541 T newymax = _ymax + amount; 542 if (newymax < _ymax ) return setWorld(); 543 else _ymax = newymax; 544 545 return *this; 546 547 } 548 549 /// Shirnk this range by the given amout in all directions. 550 // 551 /// WORLD or NULL ranges will be unchanged. 552 /// 553 /// If a shrinking range will collapse in either the horizontal 554 /// or vertical dimension it will be set to the NULL range. 555 /// 556 /// @param amount 557 /// The amount of T to shink this range in all directions. 558 /// If negative the range will grow. 559 /// See growBy(). 560 /// 561 /// @return a reference to this instance 562 /// 563 /// NOTE: This method assumes that the numerical type used 564 /// as parameter does allow both positive and negative 565 /// values. Using this method against an instance of 566 /// an 'unsigned' Range2d will likely raise unexpected 567 /// results. 568 /// 569 /// TODO: change the interface to never make the Range null, 570 /// as we might always use the Range *center* point 571 /// instead of forgetting about it! 572 /// 573 Range2d<T>& shrinkBy(T amount) 574 { 575 if ( isNull() || isWorld() || amount==0 ) return *this; 576 577 // NOTE: this trigger a compiler warning when T is an 578 // unsigned type (Coverity CID 1154655 - 579 // logically dead code) 580 if ( amount < 0 ) return growBy(-amount); 581 582 // Turn this range into the NULL range 583 // if any dimension collapses. 584 // Don't use width() and height() to 585 // avoid superflous checks. 586 587 if ( _xmax - _xmin <= amount ) return setNull(); 588 if ( _ymax - _ymin <= amount ) return setNull(); 589 590 _xmin += amount; 591 _ymin += amount; 592 _xmax -= amount; 593 _ymax -= amount; 594 595 return *this; 596 597 } 598 599 /// Get min X ordinate. 600 // 601 /// Don't call this against a NULL or WORLD Range2 602 /// 603 T getMinX() const 604 { 605 assert ( isFinite() ); 606 return _xmin; 607 } 608 609 /// Get max X ordinate. 610 // 611 /// Don't call this against a NULL or WORLD Range2d 612 /// 613 T getMaxX() const 614 { 615 assert ( isFinite() ); 616 return _xmax; 617 } 618 619 /// Get min Y ordinate. 620 // 621 /// Don't call this against a NULL or WORLD Range2d 622 /// 623 T getMinY() const 624 { 625 assert ( isFinite() ); 626 return _ymin; 627 } 628 629 /// Get max Y ordinate. 630 // 631 /// Don't call this against a NULL or WORLD Range2d 632 /// 633 T getMaxY() const 634 { 635 assert ( isFinite() ); 636 return _ymax; 637 } 638 639 640 /// Get area (width*height) 641 // 642 typename detail::Promote<T>::type 643 getArea() const { 644 assert ( !isWorld() ); 645 if ( isNull() ) return 0; 646 return static_cast<typename detail::Promote<T>::type>(_xmax - _xmin) 647 * (_ymax - _ymin); 648 // this implementation is for float types, see specialization below 649 // for ints... 650 } 651 652 /// Expand this range to include the given Range2d 653 // 654 /// WORLD ranges force result to be the WORLD range. 655 /// A NULL range will have no effect on the result. 656 /// 657 void expandTo(const Range2d<T>& r) 658 { 659 if ( r.isNull() ) 660 { 661 // the given range will add nothing 662 return; 663 } 664 665 if ( isNull() ) 666 { 667 // being null ourself, we'll equal the given range 668 *this = r; 669 return; 670 } 671 672 if ( isWorld() || r.isWorld() ) 673 { 674 // union with world is always world... 675 setWorld(); 676 return; 677 } 678 679 _xmin = std::min(_xmin, r._xmin); 680 _xmax = std::max(_xmax, r._xmax); 681 _ymin = std::min(_ymin, r._ymin); 682 _ymax = std::max(_ymax, r._ymax); 683 684 } 685 686 private: 687 688 T _xmin, _xmax, _ymin, _ymax; 689 690 T scaleMin(T min, float scale) const { 691 return roundMin(static_cast<float>(min) * scale); 692 } 693 694 T scaleMax(T max, float scale) const { 695 return roundMax(static_cast<float>(max) * scale); 696 } 697 698 T roundMin(float v) const { 699 return static_cast<T>(v); 700 } 701 702 T roundMax(float v) const { 703 return static_cast<T>(v); 704 } 705 706 707 }; 708 709 template <typename T> inline std::ostream& 710 operator<< (std::ostream& os, const Range2d<T>& rect) 711 { 712 if ( rect.isNull() ) return os << "Null range"; 713 if ( rect.isWorld() ) return os << "World range"; 714 715 return os << "Finite range (" << rect._xmin << "," << rect._ymin 716 << " " << rect._xmax << "," << rect._ymax << ")"; 717 } 718 719 template <typename T> inline bool 720 operator== (const Range2d<T>& r1, const Range2d<T>& r2) 721 { 722 // These checks are needed becase 723 // we don't initialize *all* memebers 724 // when setting to Null or World 725 726 if ( r1.isNull() ) return r2.isNull(); 727 if ( r2.isNull() ) return r1.isNull(); 728 if ( r1.isWorld() ) return r2.isWorld(); 729 if ( r2.isWorld() ) return r1.isWorld(); 730 731 return r1._xmin == r2._xmin && r1._ymin == r2._ymin && 732 r1._xmax == r2._xmax && r1._ymax == r2._ymax; 733 } 734 735 template <typename T> inline bool 736 operator!= (const Range2d<T>& r1, const Range2d<T>& r2) 737 { 738 return ! ( r1 == r2 ); 739 } 740 741 /// Return true of the two ranges intersect (boundaries included) 742 template <typename T> inline bool 743 Intersect(const Range2d<T>& r1, const Range2d<T>& r2) 744 { 745 return r1.intersects(r2); 746 } 747 748 /// Return a rectangle being the union of the two rectangles 749 template <typename T> inline Range2d<T> 750 Union(const Range2d<T>& r1, const Range2d<T>& r2) 751 { 752 Range2d<T> ret = r1; 753 ret.expandTo(r2); 754 return ret; 755 } 756 757 /// Return a rectangle being the intersetion of the two rectangles 758 // 759 /// Any NULL operand will make the result also NULL. 760 /// 761 template <typename T> inline Range2d<T> 762 Intersection(const Range2d<T>& r1, const Range2d<T>& r2) 763 { 764 if ( r1.isNull() || r2.isNull() ) { 765 // NULL ranges intersect nothing 766 return Range2d<T>(nullRange); 767 } 768 769 if ( r1.isWorld() ) { 770 // WORLD range intersect everything 771 return r2; 772 } 773 774 if ( r2.isWorld() ) { 775 // WORLD range intersect everything 776 return r1; 777 } 778 779 if ( ! r1.intersects(r2) ) { 780 // No intersection results in a NULL range 781 return Range2d<T>(nullRange); 782 } 783 784 return Range2d<T> ( 785 std::max(r1._xmin, r2._xmin), // xmin 786 std::max(r1._ymin, r2._ymin), // ymin 787 std::min(r1._xmax, r2._xmax), // xmax 788 std::min(r1._ymax, r2._ymax) // ymax 789 ); 790 791 } 792 793 /// Specialization of minimum value rounding for int type. 794 // 795 /// Use floor. 796 /// 797 template<> inline int 798 Range2d<int>::roundMin(float min) const 799 { 800 return static_cast<int>(std::floor(min)); 801 } 802 803 /// Specialization of minimum value rounding for unsigned int type. 804 // 805 /// Use floor. 806 /// 807 template<> inline unsigned int 808 Range2d<unsigned int>::roundMin(float min) const 809 { 810 return static_cast<unsigned int>(std::floor(min)); 811 } 812 813 /// Specialization of maximum value rounding for int type. 814 // 815 /// Use ceil. 816 /// 817 template<> inline int 818 Range2d<int>::roundMax(float max) const 819 { 820 return static_cast<int>(std::ceil(max)); 821 } 822 823 /// Specialization of maximum value rounding for unsigned int type. 824 // 825 /// Use ceil. 826 /// 827 template<> inline unsigned int 828 Range2d<unsigned int>::roundMax(float max) const 829 { 830 return static_cast<unsigned int>(std::ceil(max)); 831 } 832 833 /// Specialization of area value for int type. 834 // 835 /// Add one. 836 /// 837 template<> inline 838 detail::Promote<int>::type 839 Range2d<int>::getArea() const 840 { 841 assert ( !isWorld() ); 842 if ( isNull() ) return 0; 843 return static_cast<detail::Promote<int>::type>(_xmax - _xmin + 1) * 844 (_ymax - _ymin + 1); 845 } 846 847 /// Specialization of area value for unsigned int type. 848 // 849 /// Add one. 850 /// 851 template<> inline 852 detail::Promote<unsigned int>::type 853 Range2d<unsigned int>::getArea() const 854 { 855 assert ( isFinite() ); 856 return static_cast<detail::Promote<unsigned int>::type>(_xmax - _xmin + 1) * 857 (_ymax - _ymin + 1); 858 } 859 860 861 } // namespace gnash::geometry 862 } // namespace gnash 863 864 #endif // GNASH_RANGE2D_H 865 866 867 // Local Variables: 868 // mode: C++ 869 // indent-tabs-mode: t 870 // End: 871