1 /* 2 * Copyright 2001-2007, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * DarkWyrm <bpmagic@columbus.rr.com> 7 * Jérôme Duval, jerome.duval@free.fr 8 * Michael Lotz <mmlr@mlotz.ch> 9 * Stephan Aßmus <superstippi@gmx.de> 10 */ 11 12 13 #include "ServerFont.h" 14 15 #include "Angle.h" 16 #include "GlyphLayoutEngine.h" 17 #include "FontManager.h" 18 #include "truncate_string.h" 19 #include "utf8_functions.h" 20 21 #include FT_FREETYPE_H 22 #include FT_GLYPH_H 23 #include FT_OUTLINE_H 24 25 #include <Shape.h> 26 #include <String.h> 27 #include <UTF8.h> 28 29 #include <agg_bounding_rect.h> 30 31 #include <stdio.h> 32 #include <string.h> 33 34 // functions needed to convert a freetype vector graphics to a BShape 35 inline BPoint 36 VectorToPoint(const FT_Vector *vector) 37 { 38 BPoint result; 39 result.x = float(vector->x) / 64; 40 result.y = -float(vector->y) / 64; 41 return result; 42 } 43 44 45 int 46 MoveToFunc(const FT_Vector *to, void *user) 47 { 48 ((BShape *)user)->MoveTo(VectorToPoint(to)); 49 return 0; 50 } 51 52 53 int 54 LineToFunc(const FT_Vector *to, void *user) 55 { 56 ((BShape *)user)->LineTo(VectorToPoint(to)); 57 return 0; 58 } 59 60 61 int 62 ConicToFunc(const FT_Vector *control, const FT_Vector *to, void *user) 63 { 64 BPoint controls[3]; 65 66 controls[0] = VectorToPoint(control); 67 controls[1] = VectorToPoint(to); 68 controls[2] = controls[1]; 69 70 ((BShape *)user)->BezierTo(controls); 71 return 0; 72 } 73 74 75 int 76 CubicToFunc(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user) 77 { 78 BPoint controls[3]; 79 80 controls[0] = VectorToPoint(control1); 81 controls[1] = VectorToPoint(control2); 82 controls[2] = VectorToPoint(to); 83 84 ((BShape *)user)->BezierTo(controls); 85 return 0; 86 } 87 88 89 inline bool 90 is_white_space(uint32 charCode) 91 { 92 switch (charCode) { 93 case 0x0009: /* tab */ 94 case 0x000b: /* vertical tab */ 95 case 0x000c: /* form feed */ 96 case 0x0020: /* space */ 97 case 0x00a0: /* non breaking space */ 98 case 0x000a: /* line feed */ 99 case 0x000d: /* carriage return */ 100 case 0x2028: /* line separator */ 101 case 0x2029: /* paragraph separator */ 102 return true; 103 } 104 105 return false; 106 } 107 108 109 // #pragma mark - 110 111 112 /*! 113 \brief Constructor 114 \param style Style object to which the ServerFont belongs 115 \param size Character size in points 116 \param rotation Rotation in degrees 117 \param shear Shear (slant) in degrees. 45 <= shear <= 135 118 \param flags Style flags as defined in <Font.h> 119 \param spacing String spacing flag as defined in <Font.h> 120 */ 121 ServerFont::ServerFont(FontStyle& style, float size, 122 float rotation, float shear, float falseBoldWidth, 123 uint16 flags, uint8 spacing) 124 : fStyle(&style), 125 fSize(size), 126 fRotation(rotation), 127 fShear(shear), 128 fFalseBoldWidth(falseBoldWidth), 129 fBounds(0, 0, 0, 0), 130 fFlags(flags), 131 fSpacing(spacing), 132 fDirection(style.Direction()), 133 fFace(style.Face()), 134 fEncoding(B_UNICODE_UTF8) 135 { 136 fStyle->Acquire(); 137 } 138 139 140 ServerFont::ServerFont() 141 : 142 fStyle(NULL) 143 { 144 *this = *gFontManager->DefaultPlainFont(); 145 } 146 147 148 /*! 149 \brief Copy Constructor 150 \param font ServerFont to copy 151 */ 152 ServerFont::ServerFont(const ServerFont &font) 153 : 154 fStyle(NULL) 155 { 156 *this = font; 157 } 158 159 160 /*! 161 \brief Removes itself as a dependency of its owning style. 162 */ 163 ServerFont::~ServerFont() 164 { 165 fStyle->Release(); 166 } 167 168 169 /*! 170 \brief Returns a copy of the specified font 171 \param The font to copy from. 172 \return A copy of the specified font 173 */ 174 ServerFont& 175 ServerFont::operator=(const ServerFont& font) 176 { 177 if (font.fStyle) { 178 fSize = font.fSize; 179 fRotation = font.fRotation; 180 fShear = font.fShear; 181 fFalseBoldWidth = font.fFalseBoldWidth; 182 fFlags = font.fFlags; 183 fSpacing = font.fSpacing; 184 fEncoding = font.fEncoding; 185 fBounds = font.fBounds; 186 187 SetStyle(font.fStyle); 188 } 189 190 return *this; 191 } 192 193 194 /*! 195 \brief Returns the number of strikes in the font 196 \return The number of strikes in the font 197 */ 198 int32 199 ServerFont::CountTuned() 200 { 201 return fStyle->TunedCount(); 202 } 203 204 205 /*! 206 \brief Returns the file format of the font. 207 \return Mostly B_TRUETYPE_WINDOWS :) 208 */ 209 font_file_format 210 ServerFont::FileFormat() 211 { 212 return fStyle->FileFormat(); 213 } 214 215 216 const char* 217 ServerFont::Style() const 218 { 219 return fStyle->Name(); 220 } 221 222 223 const char* 224 ServerFont::Family() const 225 { 226 return fStyle->Family()->Name(); 227 } 228 229 230 void 231 ServerFont::SetStyle(FontStyle* style) 232 { 233 if (style && style != fStyle) { 234 // detach from old style 235 if (fStyle) 236 fStyle->Release(); 237 238 // attach to new style 239 fStyle = style; 240 241 fStyle->Acquire(); 242 243 fFace = fStyle->Face(); 244 fDirection = fStyle->Direction(); 245 } 246 } 247 248 249 /*! 250 \brief Sets the ServerFont instance to whatever font is specified 251 This method will lock the font manager. 252 253 \param familyID ID number of the family to set 254 \param styleID ID number of the style to set 255 \return B_OK if successful, B_ERROR if not 256 */ 257 status_t 258 ServerFont::SetFamilyAndStyle(uint16 familyID, uint16 styleID) 259 { 260 FontStyle* style = NULL; 261 262 if (gFontManager->Lock()) { 263 style = gFontManager->GetStyle(familyID, styleID); 264 if (style != NULL) 265 style->Acquire(); 266 267 gFontManager->Unlock(); 268 } 269 270 if (!style) 271 return B_ERROR; 272 273 SetStyle(style); 274 style->Release(); 275 276 return B_OK; 277 } 278 279 280 /*! 281 \brief Sets the ServerFont instance to whatever font is specified 282 \param fontID the combination of family and style ID numbers 283 \return B_OK if successful, B_ERROR if not 284 */ 285 status_t 286 ServerFont::SetFamilyAndStyle(uint32 fontID) 287 { 288 uint16 style = fontID & 0xFFFF; 289 uint16 family = (fontID & 0xFFFF0000) >> 16; 290 291 return SetFamilyAndStyle(family, style); 292 } 293 294 295 void 296 ServerFont::SetFace(uint32 face) 297 { 298 // TODO: change font style as requested! 299 fFace = face; 300 } 301 302 303 /*! 304 \brief Gets the ID values for the ServerFont instance in one shot 305 \return the combination of family and style ID numbers 306 */ 307 uint32 308 ServerFont::GetFamilyAndStyle() const 309 { 310 return (FamilyID() << 16) | StyleID(); 311 } 312 313 314 FT_Face 315 ServerFont::GetTransformedFace(bool rotate, bool shear) const 316 { 317 fStyle->Lock(); 318 FT_Face face = fStyle->FreeTypeFace(); 319 if (!face) { 320 fStyle->Unlock(); 321 return NULL; 322 } 323 324 FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72); 325 326 if ((rotate && fRotation != 0) || (shear && fShear != 90)) { 327 FT_Matrix rmatrix, smatrix; 328 329 Angle rotationAngle(fRotation); 330 rmatrix.xx = (FT_Fixed)( rotationAngle.Cosine() * 0x10000); 331 rmatrix.xy = (FT_Fixed)(-rotationAngle.Sine() * 0x10000); 332 rmatrix.yx = (FT_Fixed)( rotationAngle.Sine() * 0x10000); 333 rmatrix.yy = (FT_Fixed)( rotationAngle.Cosine() * 0x10000); 334 335 Angle shearAngle(fShear); 336 smatrix.xx = (FT_Fixed)(0x10000); 337 smatrix.xy = (FT_Fixed)(-shearAngle.Cosine() * 0x10000); 338 smatrix.yx = (FT_Fixed)(0); 339 smatrix.yy = (FT_Fixed)(0x10000); 340 341 // Multiply togheter and apply transform 342 FT_Matrix_Multiply(&rmatrix, &smatrix); 343 FT_Set_Transform(face, &smatrix, NULL); 344 } 345 346 // fStyle will be unlocked in PutTransformedFace() 347 return face; 348 } 349 350 351 void 352 ServerFont::PutTransformedFace(FT_Face face) const 353 { 354 // Reset transformation 355 FT_Set_Transform(face, NULL, NULL); 356 fStyle->Unlock(); 357 } 358 359 360 status_t 361 ServerFont::GetGlyphShapes(const char charArray[], int32 numChars, 362 BShape *shapeArray[]) const 363 { 364 if (!charArray || numChars <= 0 || !shapeArray) 365 return B_BAD_DATA; 366 367 FT_Face face = GetTransformedFace(true, true); 368 if (!face) 369 return B_ERROR; 370 371 FT_Outline_Funcs funcs; 372 funcs.move_to = MoveToFunc; 373 funcs.line_to = LineToFunc; 374 funcs.conic_to = ConicToFunc; 375 funcs.cubic_to = CubicToFunc; 376 funcs.shift = 0; 377 funcs.delta = 0; 378 379 const char *string = charArray; 380 for (int i = 0; i < numChars; i++) { 381 shapeArray[i] = new BShape(); 382 FT_Load_Char(face, UTF8ToCharCode(&string), FT_LOAD_NO_BITMAP); 383 FT_Outline outline = face->glyph->outline; 384 FT_Outline_Decompose(&outline, &funcs, shapeArray[i]); 385 shapeArray[i]->Close(); 386 } 387 388 PutTransformedFace(face); 389 return B_OK; 390 } 391 392 393 class HasGlyphsConsumer { 394 public: 395 HasGlyphsConsumer(bool* hasArray) 396 : fHasArray(hasArray) 397 { 398 } 399 void Start() {} 400 void Finish(double x, double y) {} 401 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) 402 { 403 fHasArray[index] = false; 404 } 405 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 406 FontCacheEntry* entry, double x, double y) 407 { 408 fHasArray[index] = glyph->glyph_index >= 0; 409 return true; 410 } 411 412 private: 413 bool* fHasArray; 414 }; 415 416 417 status_t 418 ServerFont::GetHasGlyphs(const char* string, int32 numBytes, 419 bool* hasArray) const 420 { 421 if (!string || numBytes <= 0 || !hasArray) 422 return B_BAD_DATA; 423 424 bool kerning = true; // TODO make this a property? 425 426 HasGlyphsConsumer consumer(hasArray); 427 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 428 NULL, kerning, fSpacing)) 429 return B_OK; 430 431 return B_ERROR; 432 } 433 434 435 class EdgesConsumer { 436 public: 437 EdgesConsumer(edge_info* edges, float size) 438 : fEdges(edges) 439 , fSize(size) 440 { 441 } 442 void Start() {} 443 void Finish(double x, double y) {} 444 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) 445 { 446 fEdges[index].left = 0.0; 447 fEdges[index].right = 0.0; 448 } 449 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 450 FontCacheEntry* entry, double x, double y) 451 { 452 fEdges[index].left = glyph->inset_left / fSize; 453 fEdges[index].right = glyph->inset_right / fSize; 454 return true; 455 } 456 457 private: 458 edge_info* fEdges; 459 float fSize; 460 }; 461 462 463 status_t 464 ServerFont::GetEdges(const char* string, int32 numBytes, 465 edge_info* edges) const 466 { 467 if (!string || numBytes <= 0 || !edges) 468 return B_BAD_DATA; 469 470 bool kerning = true; // TODO make this a property? 471 472 EdgesConsumer consumer(edges, fSize); 473 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 474 NULL, kerning, fSpacing)) 475 return B_OK; 476 477 return B_ERROR; 478 479 // FT_Face face = GetTransformedFace(false, false); 480 // if (!face) 481 // return B_ERROR; 482 // 483 // const char *string = charArray; 484 // for (int i = 0; i < numChars; i++) { 485 // FT_Load_Char(face, UTF8ToCharCode(&string), FT_LOAD_NO_BITMAP); 486 // edgeArray[i].left = float(face->glyph->metrics.horiBearingX) 487 // / 64 / fSize; 488 // edgeArray[i].right = float(face->glyph->metrics.horiBearingX 489 // + face->glyph->metrics.width - face->glyph->metrics.horiAdvance) 490 // / 64 / fSize; 491 // } 492 // 493 // PutTransformedFace(face); 494 // return B_OK; 495 } 496 497 498 class BPointEscapementConsumer { 499 public: 500 BPointEscapementConsumer(BPoint* escapements, BPoint* offsets, float size) 501 : fEscapements(escapements) 502 , fOffsets(offsets) 503 , fSize(size) 504 { 505 } 506 void Start() {} 507 void Finish(double x, double y) {} 508 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) 509 { 510 _Set(index, 0, 0); 511 } 512 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 513 FontCacheEntry* entry, double x, double y) 514 { 515 _Set(index, glyph->advance_x, glyph->advance_y); 516 return true; 517 } 518 private: 519 inline void _Set(int32 index, double x, double y) 520 { 521 fEscapements[index].x = x / fSize; 522 fEscapements[index].y = y / fSize; 523 if (fOffsets) { 524 // ToDo: According to the BeBook: "The offsetArray is applied by 525 // the dynamic spacing in order to improve the relative position 526 // of the character's width with relation to another character, 527 // without altering the width." So this will probably depend on 528 // the spacing mode. 529 fOffsets[index].x = 0; 530 fOffsets[index].y = 0; 531 } 532 } 533 534 BPoint* fEscapements; 535 BPoint* fOffsets; 536 float fSize; 537 }; 538 539 540 status_t 541 ServerFont::GetEscapements(const char* string, int32 numBytes, 542 escapement_delta delta, BPoint escapementArray[], 543 BPoint offsetArray[]) const 544 { 545 if (!string || numBytes <= 0 || !escapementArray) 546 return B_BAD_DATA; 547 548 bool kerning = true; // TODO make this a property? 549 550 BPointEscapementConsumer consumer(escapementArray, offsetArray, fSize); 551 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 552 &delta, kerning, fSpacing)) 553 return B_OK; 554 555 return B_ERROR; 556 } 557 558 559 class WidthEscapementConsumer { 560 public: 561 WidthEscapementConsumer(float* widths, float size) 562 : fWidths(widths) 563 , fSize(size) 564 { 565 } 566 void Start() {} 567 void Finish(double x, double y) {} 568 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) 569 { 570 fWidths[index] = 0.0; 571 } 572 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 573 FontCacheEntry* entry, double x, double y) 574 { 575 fWidths[index] = glyph->advance_x / fSize; 576 return true; 577 } 578 579 private: 580 float* fWidths; 581 float fSize; 582 }; 583 584 585 586 status_t 587 ServerFont::GetEscapements(const char* string, int32 numBytes, 588 escapement_delta delta, float widthArray[]) const 589 { 590 if (!string || numBytes <= 0 || !widthArray) 591 return B_BAD_DATA; 592 593 bool kerning = true; // TODO make this a property? 594 595 WidthEscapementConsumer consumer(widthArray, fSize); 596 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 597 &delta, kerning, fSpacing)) 598 return B_OK; 599 return B_ERROR; 600 } 601 602 603 class BoundingBoxConsumer { 604 public: 605 BoundingBoxConsumer(Transformable& transform, BRect* rectArray, 606 bool asString) 607 : rectArray(rectArray) 608 , stringBoundingBox(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN) 609 , fAsString(asString) 610 , fCurves(fPathAdaptor) 611 , fContour(fCurves) 612 , fTransformedOutline(fCurves, transform) 613 , fTransformedContourOutline(fContour, transform) 614 , fTransform(transform) 615 { 616 } 617 618 void Start() {} 619 void Finish(double x, double y) {} 620 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) {} 621 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 622 FontCacheEntry* entry, double x, double y) 623 { 624 if (glyph->data_type != glyph_data_outline) { 625 const agg::rect_i& r = glyph->bounds; 626 if (fAsString) { 627 if (rectArray) { 628 rectArray[index].left = r.x1 + x; 629 rectArray[index].top = r.y1 + y; 630 rectArray[index].right = r.x2 + x + 1; 631 rectArray[index].bottom = r.y2 + y + 1; 632 } else { 633 stringBoundingBox = stringBoundingBox 634 | BRect(r.x1 + x, r.y1 + y, 635 r.x2 + x + 1, r.y2 + y + 1); 636 } 637 } else { 638 rectArray[index].left = r.x1; 639 rectArray[index].top = r.y1; 640 rectArray[index].right = r.x2 + 1; 641 rectArray[index].bottom = r.y2 + 1; 642 } 643 } else { 644 if (fAsString) { 645 entry->InitAdaptors(glyph, x, y, 646 fMonoAdaptor, fGray8Adaptor, fPathAdaptor); 647 } else { 648 entry->InitAdaptors(glyph, 0, 0, 649 fMonoAdaptor, fGray8Adaptor, fPathAdaptor); 650 } 651 double left = 0.0; 652 double top = 0.0; 653 double right = -1.0; 654 double bottom = -1.0; 655 uint32 pathID[1]; 656 pathID[0] = 0; 657 // TODO: use fContour if falseboldwidth is > 0 658 agg::bounding_rect(fTransformedOutline, pathID, 0, 1, 659 &left, &top, &right, &bottom); 660 661 if (rectArray) { 662 rectArray[index] = BRect(left, top, right, bottom); 663 } else { 664 stringBoundingBox = stringBoundingBox 665 | BRect(left, top, right, bottom); 666 } 667 } 668 return true; 669 } 670 671 BRect* rectArray; 672 BRect stringBoundingBox; 673 674 private: 675 bool fAsString; 676 FontCacheEntry::GlyphPathAdapter fPathAdaptor; 677 FontCacheEntry::GlyphGray8Adapter fGray8Adaptor; 678 FontCacheEntry::GlyphMonoAdapter fMonoAdaptor; 679 680 FontCacheEntry::CurveConverter fCurves; 681 FontCacheEntry::ContourConverter fContour; 682 683 FontCacheEntry::TransformedOutline fTransformedOutline; 684 FontCacheEntry::TransformedContourOutline fTransformedContourOutline; 685 686 Transformable& fTransform; 687 }; 688 689 690 status_t 691 ServerFont::GetBoundingBoxes(const char* string, int32 numBytes, 692 BRect rectArray[], bool stringEscapement, font_metric_mode mode, 693 escapement_delta delta, bool asString) 694 { 695 // TODO: The font_metric_mode is not used 696 if (!string || numBytes <= 0 || !rectArray) 697 return B_BAD_DATA; 698 699 bool kerning = true; // TODO make this a property? 700 701 Transformable transform(EmbeddedTransformation()); 702 703 BoundingBoxConsumer consumer(transform, rectArray, asString); 704 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 705 stringEscapement ? &delta : NULL, kerning, fSpacing)) 706 return B_OK; 707 return B_ERROR; 708 } 709 710 711 status_t 712 ServerFont::GetBoundingBoxesForStrings(char *charArray[], int32 lengthArray[], 713 int32 numStrings, BRect rectArray[], font_metric_mode mode, 714 escapement_delta deltaArray[]) 715 { 716 // TODO: The font_metric_mode is never used 717 if (!charArray || !lengthArray|| numStrings <= 0 || !rectArray || !deltaArray) 718 return B_BAD_DATA; 719 720 bool kerning = true; // TODO make this a property? 721 722 Transformable transform(EmbeddedTransformation()); 723 724 for (int32 i = 0; i < numStrings; i++) { 725 int32 numBytes = lengthArray[i]; 726 const char* string = charArray[i]; 727 escapement_delta delta = deltaArray[i]; 728 729 BoundingBoxConsumer consumer(transform, NULL, true); 730 if (!GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 731 &delta, kerning, fSpacing)) 732 return B_ERROR; 733 734 rectArray[i] = consumer.stringBoundingBox; 735 } 736 737 return B_OK; 738 } 739 740 741 class StringWidthConsumer { 742 public: 743 StringWidthConsumer() : width(0.0) {} 744 void Start() {} 745 void Finish(double x, double y) { width = x; } 746 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) {} 747 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 748 FontCacheEntry* entry, double x, double y) 749 { return true; } 750 751 float width; 752 }; 753 754 755 float 756 ServerFont::StringWidth(const char *string, int32 numBytes, 757 const escapement_delta* deltaArray) const 758 { 759 if (!string || numBytes <= 0) 760 return 0.0; 761 762 bool kerning = true; // TODO make this a property? 763 764 StringWidthConsumer consumer; 765 if (!GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 766 deltaArray, kerning, fSpacing)) 767 return 0.0; 768 769 return consumer.width; 770 } 771 772 773 /*! 774 \brief Returns a BRect which encloses the entire font 775 \return A BRect which encloses the entire font 776 */ 777 BRect 778 ServerFont::BoundingBox() 779 { 780 // TODO: fBounds is nowhere calculated! 781 return fBounds; 782 } 783 784 785 /*! 786 \brief Obtains the height values for characters in the font in its current state 787 \param fh pointer to a font_height object to receive the values for the font 788 */ 789 void 790 ServerFont::GetHeight(font_height& height) const 791 { 792 fStyle->GetHeight(fSize, height); 793 } 794 795 796 void 797 ServerFont::TruncateString(BString* inOut, uint32 mode, float width) const 798 { 799 if (!inOut) 800 return; 801 802 // the width of the "…" glyph 803 float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS, 1); 804 const char *string = inOut->String(); 805 int32 length = inOut->Length(); 806 807 // temporary array to hold result 808 char *result = new char[length + 3]; 809 810 // count the individual glyphs 811 int32 numChars = UTF8CountChars(string, -1); 812 813 // get the escapement of each glyph in font units 814 float *escapementArray = new float[numChars]; 815 static escapement_delta delta = (escapement_delta){ 0.0, 0.0 }; 816 if (GetEscapements(string, length, delta, escapementArray) == B_OK) { 817 truncate_string(string, mode, width, result, escapementArray, fSize, 818 ellipsisWidth, length, numChars); 819 820 inOut->SetTo(result); 821 } 822 823 delete[] escapementArray; 824 delete[] result; 825 } 826 827 828 Transformable 829 ServerFont::EmbeddedTransformation() const 830 { 831 // TODO: cache this? 832 Transformable transform; 833 834 transform.ShearBy(B_ORIGIN, (90.0 - fShear) * PI / 180.0, 0.0); 835 transform.RotateBy(B_ORIGIN, -fRotation * PI / 180.0); 836 837 return transform; 838 } 839 840