1 /* 2 * Copyright 2001-2008, 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, 501 int32 numChars, float size) 502 : 503 fEscapements(escapements), 504 fOffsets(offsets), 505 fNumChars(numChars), 506 fSize(size) 507 { 508 } 509 510 void Start() {} 511 void Finish(double x, double y) {} 512 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) 513 { 514 _Set(index, 0, 0); 515 } 516 517 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 518 FontCacheEntry* entry, double x, double y) 519 { 520 return _Set(index, glyph->advance_x, glyph->advance_y); 521 } 522 523 private: 524 inline bool _Set(int32 index, double x, double y) 525 { 526 if (index >= fNumChars) 527 return false; 528 529 fEscapements[index].x = x / fSize; 530 fEscapements[index].y = y / fSize; 531 if (fOffsets) { 532 // ToDo: According to the BeBook: "The offsetArray is applied by 533 // the dynamic spacing in order to improve the relative position 534 // of the character's width with relation to another character, 535 // without altering the width." So this will probably depend on 536 // the spacing mode. 537 fOffsets[index].x = 0; 538 fOffsets[index].y = 0; 539 } 540 return true; 541 } 542 543 BPoint* fEscapements; 544 BPoint* fOffsets; 545 int32 fNumChars; 546 float fSize; 547 }; 548 549 550 status_t 551 ServerFont::GetEscapements(const char* string, int32 numBytes, int32 numChars, 552 escapement_delta delta, BPoint escapementArray[], 553 BPoint offsetArray[]) const 554 { 555 if (!string || numBytes <= 0 || !escapementArray) 556 return B_BAD_DATA; 557 558 bool kerning = true; // TODO make this a property? 559 560 BPointEscapementConsumer consumer(escapementArray, offsetArray, numChars, 561 fSize); 562 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 563 &delta, kerning, fSpacing)) 564 return B_OK; 565 566 return B_ERROR; 567 } 568 569 570 class WidthEscapementConsumer { 571 public: 572 WidthEscapementConsumer(float* widths, int32 numChars, float size) 573 : 574 fWidths(widths), 575 fNumChars(numChars), 576 fSize(size) 577 { 578 } 579 580 void Start() {} 581 void Finish(double x, double y) {} 582 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) 583 { 584 fWidths[index] = 0.0; 585 } 586 587 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 588 FontCacheEntry* entry, double x, double y) 589 { 590 if (index >= fNumChars) 591 return false; 592 593 fWidths[index] = glyph->advance_x / fSize; 594 return true; 595 } 596 597 private: 598 float* fWidths; 599 int32 fNumChars; 600 float fSize; 601 }; 602 603 604 605 status_t 606 ServerFont::GetEscapements(const char* string, int32 numBytes, int32 numChars, 607 escapement_delta delta, float widthArray[]) const 608 { 609 if (!string || numBytes <= 0 || !widthArray) 610 return B_BAD_DATA; 611 612 bool kerning = true; // TODO make this a property? 613 614 WidthEscapementConsumer consumer(widthArray, numChars, fSize); 615 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 616 &delta, kerning, fSpacing)) 617 return B_OK; 618 return B_ERROR; 619 } 620 621 622 class BoundingBoxConsumer { 623 public: 624 BoundingBoxConsumer(Transformable& transform, BRect* rectArray, 625 bool asString) 626 : rectArray(rectArray) 627 , stringBoundingBox(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN) 628 , fAsString(asString) 629 , fCurves(fPathAdaptor) 630 , fContour(fCurves) 631 , fTransformedOutline(fCurves, transform) 632 , fTransformedContourOutline(fContour, transform) 633 , fTransform(transform) 634 { 635 } 636 637 void Start() {} 638 void Finish(double x, double y) {} 639 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) {} 640 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 641 FontCacheEntry* entry, double x, double y) 642 { 643 if (glyph->data_type != glyph_data_outline) { 644 const agg::rect_i& r = glyph->bounds; 645 if (fAsString) { 646 if (rectArray) { 647 rectArray[index].left = r.x1 + x; 648 rectArray[index].top = r.y1 + y; 649 rectArray[index].right = r.x2 + x + 1; 650 rectArray[index].bottom = r.y2 + y + 1; 651 } else { 652 stringBoundingBox = stringBoundingBox 653 | BRect(r.x1 + x, r.y1 + y, 654 r.x2 + x + 1, r.y2 + y + 1); 655 } 656 } else { 657 rectArray[index].left = r.x1; 658 rectArray[index].top = r.y1; 659 rectArray[index].right = r.x2 + 1; 660 rectArray[index].bottom = r.y2 + 1; 661 } 662 } else { 663 if (fAsString) { 664 entry->InitAdaptors(glyph, x, y, 665 fMonoAdaptor, fGray8Adaptor, fPathAdaptor); 666 } else { 667 entry->InitAdaptors(glyph, 0, 0, 668 fMonoAdaptor, fGray8Adaptor, fPathAdaptor); 669 } 670 double left = 0.0; 671 double top = 0.0; 672 double right = -1.0; 673 double bottom = -1.0; 674 uint32 pathID[1]; 675 pathID[0] = 0; 676 // TODO: use fContour if falseboldwidth is > 0 677 agg::bounding_rect(fTransformedOutline, pathID, 0, 1, 678 &left, &top, &right, &bottom); 679 680 if (rectArray) { 681 rectArray[index] = BRect(left, top, right, bottom); 682 } else { 683 stringBoundingBox = stringBoundingBox 684 | BRect(left, top, right, bottom); 685 } 686 } 687 return true; 688 } 689 690 BRect* rectArray; 691 BRect stringBoundingBox; 692 693 private: 694 bool fAsString; 695 FontCacheEntry::GlyphPathAdapter fPathAdaptor; 696 FontCacheEntry::GlyphGray8Adapter fGray8Adaptor; 697 FontCacheEntry::GlyphMonoAdapter fMonoAdaptor; 698 699 FontCacheEntry::CurveConverter fCurves; 700 FontCacheEntry::ContourConverter fContour; 701 702 FontCacheEntry::TransformedOutline fTransformedOutline; 703 FontCacheEntry::TransformedContourOutline fTransformedContourOutline; 704 705 Transformable& fTransform; 706 }; 707 708 709 status_t 710 ServerFont::GetBoundingBoxes(const char* string, int32 numBytes, 711 BRect rectArray[], bool stringEscapement, font_metric_mode mode, 712 escapement_delta delta, bool asString) 713 { 714 // TODO: The font_metric_mode is not used 715 if (!string || numBytes <= 0 || !rectArray) 716 return B_BAD_DATA; 717 718 bool kerning = true; // TODO make this a property? 719 720 Transformable transform(EmbeddedTransformation()); 721 722 BoundingBoxConsumer consumer(transform, rectArray, asString); 723 if (GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 724 stringEscapement ? &delta : NULL, kerning, fSpacing)) 725 return B_OK; 726 return B_ERROR; 727 } 728 729 730 status_t 731 ServerFont::GetBoundingBoxesForStrings(char *charArray[], int32 lengthArray[], 732 int32 numStrings, BRect rectArray[], font_metric_mode mode, 733 escapement_delta deltaArray[]) 734 { 735 // TODO: The font_metric_mode is never used 736 if (!charArray || !lengthArray|| numStrings <= 0 || !rectArray || !deltaArray) 737 return B_BAD_DATA; 738 739 bool kerning = true; // TODO make this a property? 740 741 Transformable transform(EmbeddedTransformation()); 742 743 for (int32 i = 0; i < numStrings; i++) { 744 int32 numBytes = lengthArray[i]; 745 const char* string = charArray[i]; 746 escapement_delta delta = deltaArray[i]; 747 748 BoundingBoxConsumer consumer(transform, NULL, true); 749 if (!GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 750 &delta, kerning, fSpacing)) 751 return B_ERROR; 752 753 rectArray[i] = consumer.stringBoundingBox; 754 } 755 756 return B_OK; 757 } 758 759 760 class StringWidthConsumer { 761 public: 762 StringWidthConsumer() : width(0.0) {} 763 void Start() {} 764 void Finish(double x, double y) { width = x; } 765 void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) {} 766 bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, 767 FontCacheEntry* entry, double x, double y) 768 { return true; } 769 770 float width; 771 }; 772 773 774 float 775 ServerFont::StringWidth(const char *string, int32 numBytes, 776 const escapement_delta* deltaArray) const 777 { 778 if (!string || numBytes <= 0) 779 return 0.0; 780 781 bool kerning = true; // TODO make this a property? 782 783 StringWidthConsumer consumer; 784 if (!GlyphLayoutEngine::LayoutGlyphs(consumer, *this, string, numBytes, 785 deltaArray, kerning, fSpacing)) 786 return 0.0; 787 788 return consumer.width; 789 } 790 791 792 /*! 793 \brief Returns a BRect which encloses the entire font 794 \return A BRect which encloses the entire font 795 */ 796 BRect 797 ServerFont::BoundingBox() 798 { 799 // TODO: fBounds is nowhere calculated! 800 return fBounds; 801 } 802 803 804 /*! 805 \brief Obtains the height values for characters in the font in its current state 806 \param fh pointer to a font_height object to receive the values for the font 807 */ 808 void 809 ServerFont::GetHeight(font_height& height) const 810 { 811 fStyle->GetHeight(fSize, height); 812 } 813 814 815 void 816 ServerFont::TruncateString(BString* inOut, uint32 mode, float width) const 817 { 818 if (!inOut) 819 return; 820 821 // the width of the "…" glyph 822 float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS, 1); 823 const char *string = inOut->String(); 824 int32 length = inOut->Length(); 825 826 // temporary array to hold result 827 char *result = new char[length + 3]; 828 829 // count the individual glyphs 830 int32 numChars = UTF8CountChars(string, -1); 831 832 // get the escapement of each glyph in font units 833 float *escapementArray = new float[numChars]; 834 static escapement_delta delta = (escapement_delta){ 0.0, 0.0 }; 835 if (GetEscapements(string, length, numChars, delta, escapementArray) 836 == B_OK) { 837 truncate_string(string, mode, width, result, escapementArray, fSize, 838 ellipsisWidth, length, numChars); 839 840 inOut->SetTo(result); 841 } 842 843 delete[] escapementArray; 844 delete[] result; 845 } 846 847 848 Transformable 849 ServerFont::EmbeddedTransformation() const 850 { 851 // TODO: cache this? 852 Transformable transform; 853 854 transform.ShearBy(B_ORIGIN, (90.0 - fShear) * PI / 180.0, 0.0); 855 transform.RotateBy(B_ORIGIN, -fRotation * PI / 180.0); 856 857 return transform; 858 } 859 860