1 /* 2 * PROJECT: ReactOS win32 kernel mode subsystem 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: win32ss/gdi/ntgdi/xformobj.c 5 * PURPOSE: XFORMOBJ API 6 * PROGRAMMER: Timo Kreuzer 7 */ 8 9 /** Includes ******************************************************************/ 10 11 #include <win32k.h> 12 #define NDEBUG 13 #include <debug.h> 14 15 #define DOES_VALUE_OVERFLOW_LONG(x) \ 16 (((__int64)((long)(x))) != (x)) 17 18 /** Inline helper functions ***************************************************/ 19 20 /* 21 * Inline helper to calculate pfo1 * pfo2 + pfo3 * pfo4 22 */ 23 FORCEINLINE 24 VOID 25 MulAdd( 26 PFLOATOBJ pfoDest, 27 PFLOATOBJ pfo1, 28 PFLOATOBJ pfo2, 29 PFLOATOBJ pfo3, 30 PFLOATOBJ pfo4) 31 { 32 FLOATOBJ foTmp; 33 34 *pfoDest = *pfo1; 35 FLOATOBJ_Mul(pfoDest, pfo2); 36 foTmp = *pfo3; 37 FLOATOBJ_Mul(&foTmp, pfo4); 38 FLOATOBJ_Add(pfoDest, &foTmp); 39 } 40 41 /* 42 * Inline helper to calculate pfo1 * l2 + pfo3 * l4 43 */ 44 FORCEINLINE 45 VOID 46 MulAddLong( 47 PFLOATOBJ pfoDest, 48 PFLOATOBJ pfo1, 49 LONG l2, 50 PFLOATOBJ pfo3, 51 LONG l4) 52 { 53 FLOATOBJ foTmp; 54 55 *pfoDest = *pfo1; 56 FLOATOBJ_MulLong(pfoDest, l2); 57 foTmp = *pfo3; 58 FLOATOBJ_MulLong(&foTmp, l4); 59 FLOATOBJ_Add(pfoDest, &foTmp); 60 } 61 62 /* 63 * Inline helper to calculate pfo1 * pfo2 - pfo3 * pfo4 64 */ 65 FORCEINLINE 66 VOID 67 MulSub( 68 PFLOATOBJ pfoDest, 69 PFLOATOBJ pfo1, 70 PFLOATOBJ pfo2, 71 PFLOATOBJ pfo3, 72 PFLOATOBJ pfo4) 73 { 74 FLOATOBJ foTmp; 75 76 *pfoDest = *pfo1; 77 FLOATOBJ_Mul(pfoDest, pfo2); 78 foTmp = *pfo3; 79 FLOATOBJ_Mul(&foTmp, pfo4); 80 FLOATOBJ_Sub(pfoDest, &foTmp); 81 } 82 83 /* 84 * Inline helper to get the complexity hint from flAccel 85 */ 86 FORCEINLINE 87 ULONG 88 HintFromAccel(ULONG flAccel) 89 { 90 switch (flAccel & (XFORM_SCALE|XFORM_UNITY|XFORM_NO_TRANSLATION)) 91 { 92 case (XFORM_SCALE|XFORM_UNITY|XFORM_NO_TRANSLATION): 93 return GX_IDENTITY; 94 case (XFORM_SCALE|XFORM_UNITY): 95 return GX_OFFSET; 96 case XFORM_SCALE: 97 return GX_SCALE; 98 default: 99 return GX_GENERAL; 100 } 101 } 102 103 /** Internal functions ********************************************************/ 104 105 ULONG 106 NTAPI 107 XFORMOBJ_UpdateAccel( 108 IN XFORMOBJ *pxo) 109 { 110 PMATRIX pmx = XFORMOBJ_pmx(pxo); 111 112 /* Copy Dx and Dy to FIX format */ 113 pmx->fxDx = FLOATOBJ_GetFix(&pmx->efDx); 114 pmx->fxDy = FLOATOBJ_GetFix(&pmx->efDy); 115 116 pmx->flAccel = 0; 117 118 if (FLOATOBJ_Equal0(&pmx->efDx) && 119 FLOATOBJ_Equal0(&pmx->efDy)) 120 { 121 pmx->flAccel |= XFORM_NO_TRANSLATION; 122 } 123 124 if (FLOATOBJ_Equal0(&pmx->efM12) && 125 FLOATOBJ_Equal0(&pmx->efM21)) 126 { 127 pmx->flAccel |= XFORM_SCALE; 128 } 129 130 if (FLOATOBJ_Equal1(&pmx->efM11) && 131 FLOATOBJ_Equal1(&pmx->efM22)) 132 { 133 pmx->flAccel |= XFORM_UNITY; 134 } 135 136 if (FLOATOBJ_IsLong(&pmx->efM11) && FLOATOBJ_IsLong(&pmx->efM12) && 137 FLOATOBJ_IsLong(&pmx->efM21) && FLOATOBJ_IsLong(&pmx->efM22)) 138 { 139 pmx->flAccel |= XFORM_INTEGER; 140 } 141 142 return HintFromAccel(pmx->flAccel); 143 } 144 145 146 ULONG 147 NTAPI 148 XFORMOBJ_iSetXform( 149 OUT XFORMOBJ *pxo, 150 IN const XFORML *pxform) 151 { 152 PMATRIX pmx = XFORMOBJ_pmx(pxo); 153 154 /* Check parameters */ 155 if (!pxo || !pxform) return DDI_ERROR; 156 157 /* Check if the xform is valid */ 158 if ((pxform->eM11 == 0) || (pxform->eM22 == 0)) return DDI_ERROR; 159 160 /* Copy members */ 161 FLOATOBJ_SetFloat(&pmx->efM11, pxform->eM11); 162 FLOATOBJ_SetFloat(&pmx->efM12, pxform->eM12); 163 FLOATOBJ_SetFloat(&pmx->efM21, pxform->eM21); 164 FLOATOBJ_SetFloat(&pmx->efM22, pxform->eM22); 165 FLOATOBJ_SetFloat(&pmx->efDx, pxform->eDx); 166 FLOATOBJ_SetFloat(&pmx->efDy, pxform->eDy); 167 168 /* Update accelerators and return complexity */ 169 return XFORMOBJ_UpdateAccel(pxo); 170 } 171 172 173 /* 174 * Multiplies pxo1 with pxo2 and stores the result in pxo. 175 * returns complexity hint 176 * | efM11 efM12 0 | 177 * | efM21 efM22 0 | 178 * | efDx efDy 1 | 179 */ 180 ULONG 181 NTAPI 182 XFORMOBJ_iCombine( 183 IN XFORMOBJ *pxo, 184 IN XFORMOBJ *pxo1, 185 IN XFORMOBJ *pxo2) 186 { 187 MATRIX mx; 188 PMATRIX pmx, pmx1, pmx2; 189 190 /* Get the source matrices */ 191 pmx1 = XFORMOBJ_pmx(pxo1); 192 pmx2 = XFORMOBJ_pmx(pxo2); 193 194 /* Do a 3 x 3 matrix multiplication with mx as destinantion */ 195 MulAdd(&mx.efM11, &pmx1->efM11, &pmx2->efM11, &pmx1->efM12, &pmx2->efM21); 196 MulAdd(&mx.efM12, &pmx1->efM11, &pmx2->efM12, &pmx1->efM12, &pmx2->efM22); 197 MulAdd(&mx.efM21, &pmx1->efM21, &pmx2->efM11, &pmx1->efM22, &pmx2->efM21); 198 MulAdd(&mx.efM22, &pmx1->efM21, &pmx2->efM12, &pmx1->efM22, &pmx2->efM22); 199 MulAdd(&mx.efDx, &pmx1->efDx, &pmx2->efM11, &pmx1->efDy, &pmx2->efM21); 200 FLOATOBJ_Add(&mx.efDx, &pmx2->efDx); 201 MulAdd(&mx.efDy, &pmx1->efDx, &pmx2->efM12, &pmx1->efDy, &pmx2->efM22); 202 FLOATOBJ_Add(&mx.efDy, &pmx2->efDy); 203 204 /* Copy back */ 205 pmx = XFORMOBJ_pmx(pxo); 206 *pmx = mx; 207 208 /* Update accelerators and return complexity */ 209 return XFORMOBJ_UpdateAccel(pxo); 210 } 211 212 213 ULONG 214 NTAPI 215 XFORMOBJ_iCombineXform( 216 IN XFORMOBJ *pxo, 217 IN XFORMOBJ *pxo1, 218 IN XFORML *pxform, 219 IN BOOL bLeftMultiply) 220 { 221 MATRIX mx; 222 XFORMOBJ xo2; 223 224 XFORMOBJ_vInit(&xo2, &mx); 225 XFORMOBJ_iSetXform(&xo2, pxform); 226 227 if (bLeftMultiply) 228 { 229 return XFORMOBJ_iCombine(pxo, &xo2, pxo1); 230 } 231 else 232 { 233 return XFORMOBJ_iCombine(pxo, pxo1, &xo2); 234 } 235 } 236 237 /* 238 * A^-1 = adj(A) / det(AT) 239 * A^-1 = 1/(a*d - b*c) * (a22,-a12,a21,-a11) 240 */ 241 ULONG 242 NTAPI 243 XFORMOBJ_iInverse( 244 OUT XFORMOBJ *pxoDst, 245 IN XFORMOBJ *pxoSrc) 246 { 247 PMATRIX pmxDst, pmxSrc; 248 FLOATOBJ foDet; 249 XFORM xformSrc; 250 251 pmxDst = XFORMOBJ_pmx(pxoDst); 252 pmxSrc = XFORMOBJ_pmx(pxoSrc); 253 254 XFORMOBJ_iGetXform(pxoSrc, (XFORML*)&xformSrc); 255 256 /* det = M11 * M22 - M12 * M21 */ 257 MulSub(&foDet, &pmxSrc->efM11, &pmxSrc->efM22, &pmxSrc->efM12, &pmxSrc->efM21); 258 259 if (FLOATOBJ_Equal0(&foDet)) 260 { 261 /* Determinant is 0! */ 262 return DDI_ERROR; 263 } 264 265 /* Calculate adj(A) / det(A) */ 266 pmxDst->efM11 = pmxSrc->efM22; 267 FLOATOBJ_Div(&pmxDst->efM11, &foDet); 268 pmxDst->efM22 = pmxSrc->efM11; 269 FLOATOBJ_Div(&pmxDst->efM22, &foDet); 270 271 /* The other 2 are negative, negate foDet for that */ 272 FLOATOBJ_Neg(&foDet); 273 pmxDst->efM12 = pmxSrc->efM12; 274 FLOATOBJ_Div(&pmxDst->efM12, &foDet); 275 pmxDst->efM21 = pmxSrc->efM21; 276 FLOATOBJ_Div(&pmxDst->efM21, &foDet); 277 278 /* Calculate the inverted x shift: Dx' = -Dx * M11' - Dy * M21' */ 279 pmxDst->efDx = pmxSrc->efDx; 280 FLOATOBJ_Neg(&pmxDst->efDx); 281 MulSub(&pmxDst->efDx, &pmxDst->efDx, &pmxDst->efM11, &pmxSrc->efDy, &pmxDst->efM21); 282 283 /* Calculate the inverted y shift: Dy' = -Dy * M22' - Dx * M12' */ 284 pmxDst->efDy = pmxSrc->efDy; 285 FLOATOBJ_Neg(&pmxDst->efDy); 286 MulSub(&pmxDst->efDy, &pmxDst->efDy, &pmxDst->efM22, &pmxSrc->efDx, &pmxDst->efM12); 287 288 /* Update accelerators and return complexity */ 289 return XFORMOBJ_UpdateAccel(pxoDst); 290 } 291 292 293 /*! 294 * \brief Transforms fix-point coordinates in an array of POINTL structures using 295 * the transformation matrix from the XFORMOBJ. 296 * 297 * \param pxo - Pointer to the XFORMOBJ 298 * 299 * \param cPoints - Number of coordinates to transform 300 * 301 * \param pptIn - Pointer to an array of POINTL structures containing the 302 * source coordinates. 303 * 304 * \param pptOut - Pointer to an array of POINTL structures, receiving the 305 * transformed coordinates. Can be the same as pptIn. 306 * 307 * \return TRUE if the operation was successful, FALSE if any of the calculations 308 * caused an integer overflow. 309 * 310 * \note If the function returns FALSE, it might still have written to the 311 * output buffer. If pptIn and pptOut are equal, the source coordinates 312 * might have been partly overwritten! 313 */ 314 static 315 BOOL 316 NTAPI 317 XFORMOBJ_bXformFixPoints( 318 _In_ XFORMOBJ *pxo, 319 _In_ ULONG cPoints, 320 _In_reads_(cPoints) PPOINTL pptIn, 321 _Out_writes_(cPoints) PPOINTL pptOut) 322 { 323 PMATRIX pmx; 324 INT i; 325 FLOATOBJ fo1, fo2; 326 FLONG flAccel; 327 LONG lM11, lM12, lM21, lM22, lTemp; 328 register LONGLONG llx, lly; 329 330 pmx = XFORMOBJ_pmx(pxo); 331 flAccel = pmx->flAccel; 332 333 if ((flAccel & (XFORM_SCALE|XFORM_UNITY)) == (XFORM_SCALE|XFORM_UNITY)) 334 { 335 /* Identity transformation */ 336 RtlCopyMemory(pptOut, pptIn, cPoints * sizeof(POINTL)); 337 } 338 else if (flAccel & XFORM_INTEGER) 339 { 340 if (flAccel & XFORM_UNITY) 341 { 342 /* 1-scale integer transform, get the off-diagonal elements */ 343 if (!FLOATOBJ_bConvertToLong(&pmx->efM12, &lM12) || 344 !FLOATOBJ_bConvertToLong(&pmx->efM21, &lM21)) 345 { 346 NT_ASSERT(FALSE); 347 return FALSE; 348 } 349 350 i = cPoints - 1; 351 do 352 { 353 /* Calculate x in 64 bit and check for overflow */ 354 llx = Int32x32To64(pptIn[i].y, lM21) + pptIn[i].x; 355 if (DOES_VALUE_OVERFLOW_LONG(llx)) 356 { 357 return FALSE; 358 } 359 360 /* Calculate y in 64 bit and check for overflow */ 361 lly = Int32x32To64(pptIn[i].x, lM12) + pptIn[i].y; 362 if (DOES_VALUE_OVERFLOW_LONG(lly)) 363 { 364 return FALSE; 365 } 366 367 /* Write back the results */ 368 pptOut[i].x = (LONG)llx; 369 pptOut[i].y = (LONG)lly; 370 } 371 while (--i >= 0); 372 } 373 else if (flAccel & XFORM_SCALE) 374 { 375 /* Diagonal integer transform, get the diagonal elements */ 376 if (!FLOATOBJ_bConvertToLong(&pmx->efM11, &lM11) || 377 !FLOATOBJ_bConvertToLong(&pmx->efM22, &lM22)) 378 { 379 NT_ASSERT(FALSE); 380 return FALSE; 381 } 382 383 i = cPoints - 1; 384 do 385 { 386 /* Calculate x in 64 bit and check for overflow */ 387 llx = Int32x32To64(pptIn[i].x, lM11); 388 if (DOES_VALUE_OVERFLOW_LONG(llx)) 389 { 390 return FALSE; 391 } 392 393 /* Calculate y in 64 bit and check for overflow */ 394 lly = Int32x32To64(pptIn[i].y, lM22); 395 if (DOES_VALUE_OVERFLOW_LONG(lly)) 396 { 397 return FALSE; 398 } 399 400 /* Write back the results */ 401 pptOut[i].x = (LONG)llx; 402 pptOut[i].y = (LONG)lly; 403 } 404 while (--i >= 0); 405 } 406 else 407 { 408 /* Full integer transform */ 409 if (!FLOATOBJ_bConvertToLong(&pmx->efM11, &lM11) || 410 !FLOATOBJ_bConvertToLong(&pmx->efM12, &lM12) || 411 !FLOATOBJ_bConvertToLong(&pmx->efM21, &lM21) || 412 !FLOATOBJ_bConvertToLong(&pmx->efM22, &lM22)) 413 { 414 NT_ASSERT(FALSE); 415 return FALSE; 416 } 417 418 i = cPoints - 1; 419 do 420 { 421 /* Calculate x in 64 bit and check for overflow */ 422 llx = Int32x32To64(pptIn[i].x, lM11); 423 llx += Int32x32To64(pptIn[i].y, lM21); 424 if (DOES_VALUE_OVERFLOW_LONG(llx)) 425 { 426 return FALSE; 427 } 428 429 /* Calculate y in 64 bit and check for overflow */ 430 lly = Int32x32To64(pptIn[i].y, lM22); 431 lly += Int32x32To64(pptIn[i].x, lM12); 432 if (DOES_VALUE_OVERFLOW_LONG(lly)) 433 { 434 return FALSE; 435 } 436 437 /* Write back the results */ 438 pptOut[i].x = (LONG)llx; 439 pptOut[i].y = (LONG)lly; 440 } 441 while (--i >= 0); 442 } 443 } 444 else if (flAccel & XFORM_UNITY) 445 { 446 /* 1-scale transform */ 447 i = cPoints - 1; 448 do 449 { 450 /* Calculate x in 64 bit and check for overflow */ 451 fo1 = pmx->efM21; 452 FLOATOBJ_MulLong(&fo1, pptIn[i].y); 453 if (!FLOATOBJ_bConvertToLong(&fo1, &lTemp)) 454 { 455 return FALSE; 456 } 457 llx = (LONGLONG)pptIn[i].x + lTemp; 458 if (DOES_VALUE_OVERFLOW_LONG(llx)) 459 { 460 return FALSE; 461 } 462 463 /* Calculate y in 64 bit and check for overflow */ 464 fo2 = pmx->efM12; 465 FLOATOBJ_MulLong(&fo2, pptIn[i].x); 466 if (!FLOATOBJ_bConvertToLong(&fo2, &lTemp)) 467 { 468 return FALSE; 469 } 470 lly = (LONGLONG)pptIn[i].y + lTemp; 471 if (DOES_VALUE_OVERFLOW_LONG(lly)) 472 { 473 return FALSE; 474 } 475 476 /* Write back the results */ 477 pptOut[i].x = (LONG)llx; 478 pptOut[i].y = (LONG)lly; 479 } 480 while (--i >= 0); 481 } 482 else if (flAccel & XFORM_SCALE) 483 { 484 /* Diagonal float transform */ 485 i = cPoints - 1; 486 do 487 { 488 fo1 = pmx->efM11; 489 FLOATOBJ_MulLong(&fo1, pptIn[i].x); 490 if (!FLOATOBJ_bConvertToLong(&fo1, &pptOut[i].x)) 491 { 492 return FALSE; 493 } 494 495 fo2 = pmx->efM22; 496 FLOATOBJ_MulLong(&fo2, pptIn[i].y); 497 if (!FLOATOBJ_bConvertToLong(&fo2, &pptOut[i].y)) 498 { 499 return FALSE; 500 } 501 } 502 while (--i >= 0); 503 } 504 else 505 { 506 /* Full float transform */ 507 i = cPoints - 1; 508 do 509 { 510 /* Calculate x as FLOATOBJ */ 511 MulAddLong(&fo1, &pmx->efM11, pptIn[i].x, &pmx->efM21, pptIn[i].y); 512 513 /* Calculate y as FLOATOBJ */ 514 MulAddLong(&fo2, &pmx->efM12, pptIn[i].x, &pmx->efM22, pptIn[i].y); 515 516 if (!FLOATOBJ_bConvertToLong(&fo1, &pptOut[i].x)) 517 { 518 return FALSE; 519 } 520 521 if (!FLOATOBJ_bConvertToLong(&fo2, &pptOut[i].y)) 522 { 523 return FALSE; 524 } 525 } 526 while (--i >= 0); 527 } 528 529 if (!(pmx->flAccel & XFORM_NO_TRANSLATION)) 530 { 531 /* Translate points */ 532 i = cPoints - 1; 533 do 534 { 535 llx = (LONGLONG)pptOut[i].x + pmx->fxDx; 536 if (DOES_VALUE_OVERFLOW_LONG(llx)) 537 { 538 return FALSE; 539 } 540 pptOut[i].x = (LONG)llx; 541 542 lly = (LONGLONG)pptOut[i].y + pmx->fxDy; 543 if (DOES_VALUE_OVERFLOW_LONG(lly)) 544 { 545 return FALSE; 546 } 547 pptOut[i].y = (LONG)lly; 548 } 549 while (--i >= 0); 550 } 551 552 return TRUE; 553 } 554 555 /** Public functions **********************************************************/ 556 557 // www.osr.com/ddk/graphics/gdifncs_0s2v.htm 558 ULONG 559 APIENTRY 560 XFORMOBJ_iGetXform( 561 IN XFORMOBJ *pxo, 562 OUT XFORML *pxform) 563 { 564 PMATRIX pmx = XFORMOBJ_pmx(pxo); 565 566 /* Check parameters */ 567 if (!pxo || !pxform) 568 { 569 return DDI_ERROR; 570 } 571 572 /* Copy members */ 573 pxform->eM11 = FLOATOBJ_GetFloat(&pmx->efM11); 574 pxform->eM12 = FLOATOBJ_GetFloat(&pmx->efM12); 575 pxform->eM21 = FLOATOBJ_GetFloat(&pmx->efM21); 576 pxform->eM22 = FLOATOBJ_GetFloat(&pmx->efM22); 577 pxform->eDx = FLOATOBJ_GetFloat(&pmx->efDx); 578 pxform->eDy = FLOATOBJ_GetFloat(&pmx->efDy); 579 580 /* Return complexity hint */ 581 return HintFromAccel(pmx->flAccel); 582 } 583 584 585 // www.osr.com/ddk/graphics/gdifncs_5ig7.htm 586 ULONG 587 APIENTRY 588 XFORMOBJ_iGetFloatObjXform( 589 IN XFORMOBJ *pxo, 590 OUT FLOATOBJ_XFORM *pxfo) 591 { 592 PMATRIX pmx = XFORMOBJ_pmx(pxo); 593 594 /* Check parameters */ 595 if (!pxo || !pxfo) 596 { 597 return DDI_ERROR; 598 } 599 600 /* Copy members */ 601 pxfo->eM11 = pmx->efM11; 602 pxfo->eM12 = pmx->efM12; 603 pxfo->eM21 = pmx->efM21; 604 pxfo->eM22 = pmx->efM22; 605 pxfo->eDx = pmx->efDx; 606 pxfo->eDy = pmx->efDy; 607 608 /* Return complexity hint */ 609 return HintFromAccel(pmx->flAccel); 610 } 611 612 613 // www.osr.com/ddk/graphics/gdifncs_027b.htm 614 BOOL 615 APIENTRY 616 XFORMOBJ_bApplyXform( 617 IN XFORMOBJ *pxo, 618 IN ULONG iMode, 619 IN ULONG cPoints, 620 IN PVOID pvIn, 621 OUT PVOID pvOut) 622 { 623 MATRIX mx; 624 XFORMOBJ xoInv; 625 PPOINTL pptlIn, pptlOut; 626 INT i; 627 628 /* Check parameters */ 629 if (!pxo || !pvIn || !pvOut || cPoints < 1) 630 { 631 return FALSE; 632 } 633 634 /* Use inverse xform? */ 635 if (iMode == XF_INV_FXTOL || iMode == XF_INV_LTOL) 636 { 637 XFORMOBJ_vInit(&xoInv, &mx); 638 if (XFORMOBJ_iInverse(&xoInv, pxo) == DDI_ERROR) 639 { 640 return FALSE; 641 } 642 pxo = &xoInv; 643 } 644 645 /* Convert POINTL to POINTFIX? */ 646 if (iMode == XF_LTOFX || iMode == XF_LTOL || iMode == XF_INV_LTOL) 647 { 648 pptlIn = pvIn; 649 pptlOut = pvOut; 650 for (i = cPoints - 1; i >= 0; i--) 651 { 652 pptlOut[i].x = LONG2FIX(pptlIn[i].x); 653 pptlOut[i].y = LONG2FIX(pptlIn[i].y); 654 } 655 656 /* The input is in the out buffer now! */ 657 pvIn = pvOut; 658 } 659 660 /* Do the actual fixpoint transformation */ 661 if (!XFORMOBJ_bXformFixPoints(pxo, cPoints, pvIn, pvOut)) 662 { 663 return FALSE; 664 } 665 666 /* Convert POINTFIX to POINTL? */ 667 if (iMode == XF_INV_FXTOL || iMode == XF_INV_LTOL || iMode == XF_LTOL) 668 { 669 pptlOut = pvOut; 670 for (i = cPoints - 1; i >= 0; i--) 671 { 672 pptlOut[i].x = FIX2LONG(pptlOut[i].x); 673 pptlOut[i].y = FIX2LONG(pptlOut[i].y); 674 } 675 } 676 677 return TRUE; 678 } 679 680 /* EOF */ 681