xref: /haiku/src/servers/app/ServerFont.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
1 /*
2  * Copyright 2001-2005, 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  */
9 
10 
11 #include <ByteOrder.h>
12 #include <Shape.h>
13 #include <String.h>
14 #include <UTF8.h>
15 
16 #include "Angle.h"
17 #include "FontManager.h"
18 #include "moreUTF8.h"
19 #include "truncate_string.h"
20 
21 #include FT_FREETYPE_H
22 #include FT_GLYPH_H
23 #include FT_OUTLINE_H
24 
25 #include "ServerFont.h"
26 
27 
28 // functions needed to convert a freetype vector graphics to a BShape
29 inline BPoint
30 VectorToPoint(FT_Vector *vector)
31 {
32 	BPoint result;
33 	result.x = float(int32(vector->x)) / 2097152;
34 	result.y = -float(int32(vector->y)) / 2097152;
35 	return result;
36 }
37 
38 int
39 MoveToFunc(FT_Vector *to, void *user)
40 {
41 	((BShape *)user)->MoveTo(VectorToPoint(to));
42 	return 0;
43 }
44 
45 int
46 LineToFunc(FT_Vector *to, void *user)
47 {
48 	((BShape *)user)->LineTo(VectorToPoint(to));
49 	return 0;
50 }
51 
52 int
53 ConicToFunc(FT_Vector *control, FT_Vector *to, void *user)
54 {
55 	BPoint controls[3];
56 
57 	controls[0] = VectorToPoint(control);
58 	controls[1] = VectorToPoint(to);
59 	controls[2] = controls[1];
60 
61 	((BShape *)user)->BezierTo(controls);
62 	return 0;
63 }
64 
65 int
66 CubicToFunc(FT_Vector *control1, FT_Vector *control2, FT_Vector *to, void *user)
67 {
68 	BPoint controls[3];
69 
70 	controls[0] = VectorToPoint(control1);
71 	controls[1] = VectorToPoint(control2);
72 	controls[2] = VectorToPoint(to);
73 
74 	((BShape *)user)->BezierTo(controls);
75 	return 0;
76 }
77 
78 
79 // is_white_space
80 inline bool
81 is_white_space(uint16 glyph)
82 {
83 	// TODO: handle them all!
84 	if (glyph == ' ' || glyph == B_TAB)
85 		return true;
86 	return false;
87 }
88 
89 
90 //	#pragma mark -
91 
92 
93 class FaceGetter {
94 	public:
95 		FaceGetter(FontStyle *style)
96 			:
97 			fStyle(style)
98 		{
99 			style->Lock();
100 		}
101 
102 		~FaceGetter()
103 		{
104 			fStyle->Unlock();
105 		}
106 
107 		FT_Face Face()
108 		{
109 			return fStyle->FreeTypeFace();
110 		}
111 
112 	private:
113 		FontStyle *fStyle;
114 };
115 
116 
117 //	#pragma mark -
118 
119 
120 /*!
121 	\brief Constructor
122 	\param style Style object to which the ServerFont belongs
123 	\param size Character size in points
124 	\param rotation Rotation in degrees
125 	\param shear Shear (slant) in degrees. 45 <= shear <= 135
126 	\param flags Style flags as defined in <Font.h>
127 	\param spacing String spacing flag as defined in <Font.h>
128 */
129 ServerFont::ServerFont(FontStyle& style, float size,
130 					   float rotation, float shear,
131 					   uint16 flags, uint8 spacing)
132 	: fStyle(&style),
133 	  fSize(size),
134 	  fRotation(rotation),
135 	  fShear(shear),
136 	  fBounds(0, 0, 0, 0),
137 	  fFlags(flags),
138 	  fSpacing(spacing),
139 	  fDirection(style.Direction()),
140 	  fFace(style.Face()),
141 	  fEncoding(B_UNICODE_UTF8)
142 {
143 	fStyle->Acquire();
144 }
145 
146 
147 ServerFont::ServerFont()
148 	:
149 	fStyle(NULL)
150 {
151 	*this = *gFontManager->DefaultPlainFont();
152 }
153 
154 
155 /*!
156 	\brief Copy Constructor
157 	\param font ServerFont to copy
158 */
159 ServerFont::ServerFont(const ServerFont &font)
160 	:
161 	fStyle(NULL)
162 {
163 	*this = font;
164 }
165 
166 
167 /*!
168 	\brief Removes itself as a dependency of its owning style.
169 */
170 ServerFont::~ServerFont()
171 {
172 	fStyle->Release();
173 }
174 
175 
176 /*!
177 	\brief Returns a copy of the specified font
178 	\param The font to copy from.
179 	\return A copy of the specified font
180 */
181 ServerFont&
182 ServerFont::operator=(const ServerFont& font)
183 {
184 	fSize		= font.fSize;
185 	fRotation	= font.fRotation;
186 	fShear		= font.fShear;
187 	fFlags		= font.fFlags;
188 	fSpacing	= font.fSpacing;
189 	fDirection	= font.fDirection;
190 	fFace		= font.fFace;
191 	fEncoding	= font.fEncoding;
192 	fBounds		= font.fBounds;
193 
194 	SetStyle(*font.fStyle);
195 	return *this;
196 }
197 
198 
199 /*!
200 	\brief Returns the number of strikes in the font
201 	\return The number of strikes in the font
202 */
203 int32
204 ServerFont::CountTuned()
205 {
206 	return fStyle->TunedCount();
207 }
208 
209 
210 /*!
211 	\brief Returns the file format of the font.
212 	\return Mostly B_TRUETYPE_WINDOWS :)
213 */
214 font_file_format
215 ServerFont::FileFormat()
216 {
217 	return fStyle->FileFormat();
218 }
219 
220 
221 const char*
222 ServerFont::Style() const
223 {
224 	return fStyle->Name();
225 }
226 
227 
228 const char*
229 ServerFont::Family() const
230 {
231 	return fStyle->Family()->Name();
232 }
233 
234 
235 void
236 ServerFont::SetStyle(FontStyle& style)
237 {
238 	if (&style != fStyle) {
239 		// detach from old style
240 		if (fStyle)
241 			fStyle->Release();
242 
243 		// attach to new style
244 		fStyle = &style;
245 		fFace = style.Face();
246 		fDirection = style.Direction();
247 
248 		fStyle->Acquire();
249 	}
250 }
251 
252 
253 /*!
254 	\brief Sets the ServerFont instance to whatever font is specified
255 	\param familyID ID number of the family to set
256 	\param styleID ID number of the style to set
257 	\return B_OK if successful, B_ERROR if not
258 */
259 status_t
260 ServerFont::SetFamilyAndStyle(uint16 familyID, uint16 styleID)
261 {
262 	FontStyle* style = NULL;
263 
264 	if (gFontManager->Lock()) {
265 		style = gFontManager->GetStyle(familyID, styleID);
266 		if (style != NULL)
267 			style->Acquire();
268 
269 		gFontManager->Unlock();
270 	}
271 
272 	if (!style)
273 		return B_ERROR;
274 
275 	SetStyle(*style);
276 	style->Release();
277 
278 	return B_OK;
279 }
280 
281 
282 /*!
283 	\brief Sets the ServerFont instance to whatever font is specified
284 	\param fontID the combination of family and style ID numbers
285 	\return B_OK if successful, B_ERROR if not
286 */
287 status_t
288 ServerFont::SetFamilyAndStyle(uint32 fontID)
289 {
290 	uint16 style = fontID & 0xFFFF;
291 	uint16 family = (fontID & 0xFFFF0000) >> 16;
292 
293 	return SetFamilyAndStyle(family, style);
294 }
295 
296 
297 void
298 ServerFont::SetFace(uint32 face)
299 {
300 	// TODO: change font style as requested!
301 	fFace = face;
302 }
303 
304 
305 /*!
306 	\brief Gets the ID values for the ServerFont instance in one shot
307 	\return the combination of family and style ID numbers
308 */
309 uint32
310 ServerFont::GetFamilyAndStyle(void) const
311 {
312 	return (FamilyID() << 16) | StyleID();
313 }
314 
315 
316 BShape **
317 ServerFont::GetGlyphShapes(const char charArray[], int32 numChars) const
318 {
319 	if (!charArray || numChars <= 0)
320 		return NULL;
321 
322 	FT_Outline_Funcs funcs;
323 	funcs.move_to = MoveToFunc;
324 	funcs.line_to = LineToFunc;
325 	funcs.conic_to = ConicToFunc;
326 	funcs.cubic_to = CubicToFunc;
327 
328 	FaceGetter getter(fStyle);
329 	FT_Face face = getter.Face();
330 	if (!face)
331 		return NULL;
332 
333 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
334 
335 	Angle rotation(fRotation);
336 	Angle shear(fShear);
337 
338 	// First, rotate
339 	FT_Matrix rmatrix;
340 	rmatrix.xx = (FT_Fixed)( rotation.Cosine()*0x10000);
341 	rmatrix.xy = (FT_Fixed)(-rotation.Sine()*0x10000);
342 	rmatrix.yx = (FT_Fixed)( rotation.Sine()*0x10000);
343 	rmatrix.yy = (FT_Fixed)( rotation.Cosine()*0x10000);
344 
345 	// Next, shear
346 	FT_Matrix smatrix;
347 	smatrix.xx = (FT_Fixed)(0x10000);
348 	smatrix.xy = (FT_Fixed)(-shear.Cosine()*0x10000);
349 	smatrix.yx = (FT_Fixed)(0);
350 	smatrix.yy = (FT_Fixed)(0x10000);
351 
352 	// Multiply togheter
353 	FT_Matrix_Multiply(&rmatrix, &smatrix);
354 
355 	//FT_Vector pen;
356 	//FT_Set_Transform(face, &smatrix, &pen);
357 
358 	BShape **shapes = (BShape **)malloc(sizeof(BShape *) * numChars);
359 	for (int i = 0; i < numChars; i++) {
360 		shapes[i] = new BShape();
361 		shapes[i]->Clear();
362 		// TODO : this is wrong (the nth char isn't charArray[i])
363 		FT_Load_Char(face, charArray[i], FT_LOAD_NO_BITMAP);
364 		FT_Outline outline = face->glyph->outline;
365 		FT_Outline_Decompose(&outline, &funcs, shapes[i]);
366 		shapes[i]->Close();
367 	}
368 
369 	return shapes;
370 }
371 
372 
373 void
374 ServerFont::GetHasGlyphs(const char charArray[], int32 numChars, bool hasArray[]) const
375 {
376 	if (!charArray || numChars <= 0 || !hasArray)
377 		return;
378 
379 	FaceGetter getter(fStyle);
380 	FT_Face face = getter.Face();
381 	if (!face)
382 		return;
383 
384 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
385 
386 	// UTF8 handling...this can probably be smarter
387 	// Here is what I do in the AGGTextRenderer to handle UTF8...
388 	// It is probably highly inefficient, so it should be reviewed.
389 	int32 numBytes = UTF8CountBytes(charArray, numChars);
390 	int32 convertedLength = numBytes * 2;
391 	char* convertedBuffer = new char[convertedLength];
392 
393 	int32 state = 0;
394 	status_t ret;
395 	if ((ret = convert_from_utf8(B_UNICODE_CONVERSION,
396 								 charArray, &numBytes,
397 								 convertedBuffer, &convertedLength,
398 								 &state, B_SUBSTITUTE)) >= B_OK
399 		&& (ret = swap_data(B_INT16_TYPE, convertedBuffer, convertedLength,
400 							B_SWAP_BENDIAN_TO_HOST)) >= B_OK) {
401 
402 		uint16* glyphIndex = (uint16*)convertedBuffer;
403 		// just to be sure
404 		numChars = min_c((uint32)numChars, convertedLength / sizeof(uint16));
405 
406 		for (int i = 0; i < numChars; i++) {
407 			hasArray[i] = FT_Get_Char_Index(face, glyphIndex[i]) > 0;
408 		}
409 	}
410 	delete[] convertedBuffer;
411 }
412 
413 
414 void
415 ServerFont::GetEdges(const char charArray[], int32 numChars, edge_info edgeArray[]) const
416 {
417 	if (!charArray || numChars <= 0 || !edgeArray)
418 		return;
419 
420 	FaceGetter getter(fStyle);
421 	FT_Face face = getter.Face();
422 	if (!face)
423 		return;
424 
425 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
426 
427 	// UTF8 handling...this can probably be smarter
428 	// Here is what I do in the AGGTextRenderer to handle UTF8...
429 	// It is probably highly inefficient, so it should be reviewed.
430 	int32 numBytes = UTF8CountBytes(charArray, numChars);
431 	int32 convertedLength = numBytes * 2;
432 	char* convertedBuffer = new char[convertedLength];
433 
434 	int32 state = 0;
435 	status_t ret;
436 	if ((ret = convert_from_utf8(B_UNICODE_CONVERSION,
437 								 charArray, &numBytes,
438 								 convertedBuffer, &convertedLength,
439 								 &state, B_SUBSTITUTE)) >= B_OK
440 		&& (ret = swap_data(B_INT16_TYPE, convertedBuffer, convertedLength,
441 							B_SWAP_BENDIAN_TO_HOST)) >= B_OK) {
442 
443 		uint16* glyphIndex = (uint16*)convertedBuffer;
444 		// just to be sure
445 		numChars = min_c((uint32)numChars, convertedLength / sizeof(uint16));
446 
447 		for (int i = 0; i < numChars; i++) {
448 			FT_Load_Char(face, glyphIndex[i], FT_LOAD_NO_BITMAP);
449 			if (face->glyph) {
450 				edgeArray[i].left = float(face->glyph->metrics.horiBearingX /64) / fSize;
451 				edgeArray[i].right = float((face->glyph->metrics.horiBearingX
452 					+ face->glyph->metrics.width - face->glyph->metrics.horiAdvance)/64) /fSize;
453 			}
454 		}
455 	}
456 	delete[] convertedBuffer;
457 }
458 
459 
460 BPoint*
461 ServerFont::GetEscapements(const char charArray[], int32 numChars,
462 						   BPoint offsetArray[]) const
463 {
464 	if (!charArray || numChars <= 0 || !offsetArray)
465 		return NULL;
466 
467 	FaceGetter getter(fStyle);
468 	FT_Face face = getter.Face();
469 	if (!face)
470 		return NULL;
471 
472 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
473 
474 	Angle rotation(fRotation);
475 	Angle shear(fShear);
476 
477 	// First, rotate
478 	FT_Matrix rmatrix;
479 	rmatrix.xx = (FT_Fixed)( rotation.Cosine()*0x10000);
480 	rmatrix.xy = (FT_Fixed)(-rotation.Sine()*0x10000);
481 	rmatrix.yx = (FT_Fixed)( rotation.Sine()*0x10000);
482 	rmatrix.yy = (FT_Fixed)( rotation.Cosine()*0x10000);
483 
484 	// Next, shear
485 	FT_Matrix smatrix;
486 	smatrix.xx = (FT_Fixed)(0x10000);
487 	smatrix.xy = (FT_Fixed)(-shear.Cosine()*0x10000);
488 	smatrix.yx = (FT_Fixed)(0);
489 	smatrix.yy = (FT_Fixed)(0x10000);
490 
491 	// Multiply togheter
492 	FT_Matrix_Multiply(&rmatrix, &smatrix);
493 
494 	//FT_Vector pen;
495 	//FT_Set_Transform(face, &smatrix, &pen);
496 
497 	// TODO: I'm not sure if this the correct interpretation
498 	// of the BeBook. Have actual tests been done here?
499 
500 	// TODO: handle UTF8... see below!!
501 	BPoint *escapements = (BPoint *)malloc(sizeof(BPoint) * numChars);
502 	for (int i = 0; i < numChars; i++) {
503 		// TODO : this is wrong (the nth char isn't charArray[i])
504 		FT_Load_Char(face, charArray[i], FT_LOAD_NO_BITMAP);
505 //		escapements[i].x = float(face->glyph->metrics.width / 64) / fSize;
506 //		escapements[i].y = 0;
507 		escapements[i].x = float(face->glyph->metrics.horiAdvance / 64) / fSize;
508 		escapements[i].y = float(face->glyph->metrics.vertAdvance / 64) / fSize;
509 		escapements[i] += offsetArray[i];
510 	}
511 
512 	return escapements;
513 }
514 
515 
516 bool
517 ServerFont::GetEscapements(const char charArray[], int32 numChars, int32 numBytes,
518 						   float widthArray[], escapement_delta delta) const
519 {
520 	if (!charArray || numChars <= 0)
521 		return false;
522 
523 	FaceGetter getter(fStyle);
524 	FT_Face face = getter.Face();
525 	if (!face)
526 		return false;
527 
528 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
529 
530 	// UTF8 handling...this can probably be smarter
531 	// Here is what I do in the AGGTextRenderer to handle UTF8...
532 	// It is probably highly inefficient, so it should be reviewed.
533 	int32 convertedLength = numBytes * 2;
534 	char* convertedBuffer = new char[convertedLength];
535 
536 	int32 state = 0;
537 	status_t ret;
538 	if ((ret = convert_from_utf8(B_UNICODE_CONVERSION,
539 								 charArray, &numBytes,
540 								 convertedBuffer, &convertedLength,
541 								 &state, B_SUBSTITUTE)) >= B_OK
542 		&& (ret = swap_data(B_INT16_TYPE, convertedBuffer, convertedLength,
543 							B_SWAP_BENDIAN_TO_HOST)) >= B_OK) {
544 
545 		uint16* glyphIndex = (uint16*)convertedBuffer;
546 		// just to be sure
547 		numChars = min_c((uint32)numChars, convertedLength / sizeof(uint16));
548 
549 		for (int i = 0; i < numChars; i++) {
550 			FT_Load_Char(face, glyphIndex[i], FT_LOAD_NO_BITMAP);
551 			if (face->glyph) {
552 				widthArray[i] = ((float)face->glyph->metrics.horiAdvance / 64.0) / fSize;
553 				widthArray[i] += is_white_space(glyphIndex[i]) ? delta.space : delta.nonspace;
554 			}
555 		}
556 	}
557 	delete[] convertedBuffer;
558 
559 	return ret >= B_OK;
560 }
561 
562 
563 bool
564 ServerFont::GetBoundingBoxesAsString(const char charArray[], int32 numChars,
565 	BRect rectArray[], bool string_escapement, font_metric_mode mode,
566 	escapement_delta delta)
567 {
568 	if (!charArray || numChars <= 0 || !rectArray)
569 		return false;
570 
571 	FaceGetter getter(fStyle);
572 	FT_Face face = getter.Face();
573 	if (!face)
574 		return false;
575 
576 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
577 
578 	// UTF8 handling...this can probably be smarter
579 	// Here is what I do in the AGGTextRenderer to handle UTF8...
580 	// It is probably highly inefficient, so it should be reviewed.
581 	int32 numBytes = UTF8CountBytes(charArray, numChars);
582 	int32 convertedLength = numBytes * 2;
583 	char* convertedBuffer = new char[convertedLength];
584 
585 	int32 state = 0;
586 	status_t ret;
587 	if ((ret = convert_from_utf8(B_UNICODE_CONVERSION,
588 								 charArray, &numBytes,
589 								 convertedBuffer, &convertedLength,
590 								 &state, B_SUBSTITUTE)) >= B_OK
591 		&& (ret = swap_data(B_INT16_TYPE, convertedBuffer, convertedLength,
592 							B_SWAP_BENDIAN_TO_HOST)) >= B_OK) {
593 
594 		uint16* glyphIndex = (uint16*)convertedBuffer;
595 		// just to be sure
596 		numChars = min_c((uint32)numChars, convertedLength / sizeof(uint16));
597 
598 		for (int i = 0; i < numChars; i++) {
599 			if (string_escapement) {
600 				if (i>0)
601 					rectArray[i].OffsetBy(is_white_space(glyphIndex[i-1]) ? delta.space/2.0 : delta.nonspace/2.0, 0.0);
602 				rectArray[i].OffsetBy(is_white_space(glyphIndex[i]) ? delta.space/2.0 : delta.nonspace/2.0, 0.0);
603 			}
604 			FT_Load_Char(face, glyphIndex[i], FT_LOAD_NO_BITMAP);
605 			if (face->glyph) {
606 				if (i<numChars-1)
607 					rectArray[i+1].left = rectArray[i+1].right = rectArray[i].left +
608 						face->glyph->metrics.horiAdvance / 64.0;
609 				rectArray[i].left += float(face->glyph->metrics.horiBearingX) /64.0;
610 				rectArray[i].right += float(face->glyph->metrics.horiBearingX
611 					+ face->glyph->metrics.width)/64.0;
612 				rectArray[i].top = -float(face->glyph->metrics.horiBearingY) /64.0;
613 				rectArray[i].bottom = float(face->glyph->metrics.height
614 					- face->glyph->metrics.horiBearingY) /64.0;
615 			}
616 		}
617 	}
618 	delete[] convertedBuffer;
619 
620 	return true;
621 }
622 
623 
624 bool
625 ServerFont::GetBoundingBoxesForStrings(char *charArray[], int32 lengthArray[],
626 	int32 numStrings, BRect rectArray[], font_metric_mode mode, escapement_delta deltaArray[])
627 {
628 	if (!charArray || !lengthArray|| numStrings <= 0 || !rectArray || !deltaArray)
629 		return false;
630 
631 	FaceGetter getter(fStyle);
632 	FT_Face face = getter.Face();
633 	if (!face)
634 		return false;
635 
636 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
637 
638 	for (int32 i = 0; i < numStrings; i++) {
639 		// TODO: ...
640 	}
641 
642 	return true;
643 }
644 
645 
646 float
647 ServerFont::StringWidth(const char* string, int32 numBytes) const
648 {
649 	if (!string || numBytes <= 0)
650 		return 0.0;
651 
652 	FaceGetter getter(fStyle);
653 	FT_Face face = getter.Face();
654 	if (!face)
655 		return 0.0;
656 
657 	FT_Set_Char_Size(face, 0, int32(fSize * 64), 72, 72);
658 
659 	int32 convertedLength = numBytes * 2;
660 	char* convertedBuffer = new char[convertedLength];
661 	float width = 0.0;
662 
663 	int32 state = 0;
664 	status_t ret;
665 	if ((ret = convert_from_utf8(B_UNICODE_CONVERSION,
666 								 string, &numBytes,
667 								 convertedBuffer, &convertedLength,
668 								 &state, B_SUBSTITUTE)) >= B_OK
669 		&& (ret = swap_data(B_INT16_TYPE, convertedBuffer, convertedLength,
670 							B_SWAP_BENDIAN_TO_HOST)) >= B_OK) {
671 
672 		uint16* glyphIndex = (uint16*)convertedBuffer;
673 		// just to be sure
674 		int numChars = convertedLength / sizeof(uint16);
675 
676 		for (int i = 0; i < numChars; i++) {
677 			FT_Load_Char(face, glyphIndex[i], FT_LOAD_NO_BITMAP);
678 			width += face->glyph->advance.x / 64.0;
679 		}
680 	}
681 	delete[] convertedBuffer;
682 
683 	return width;
684 }
685 
686 
687 /*!
688 	\brief Returns a BRect which encloses the entire font
689 	\return A BRect which encloses the entire font
690 */
691 BRect
692 ServerFont::BoundingBox()
693 {
694 	// TODO: fBounds is nowhere calculated!
695 	return fBounds;
696 }
697 
698 
699 /*!
700 	\brief Obtains the height values for characters in the font in its current state
701 	\param fh pointer to a font_height object to receive the values for the font
702 */
703 void
704 ServerFont::GetHeight(font_height& height) const
705 {
706 	fStyle->GetHeight(fSize, height);
707 }
708 
709 
710 void
711 ServerFont::TruncateString(BString* inOut, uint32 mode, float width) const
712 {
713 	if (inOut) {
714 		// the width of the "…" glyph
715 		float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS, 3);
716 		const char* string = inOut->String();
717 		int32 length = strlen(string);
718 		// temporary array to hold result
719 		char* result = new char[length + 3];
720 		// count the individual glyphs
721 		int32 numChars = UTF8CountChars(string, length);
722 		// get the escapement of each glyph in font units
723 		float* escapementArray = new float[numChars];
724 		static escapement_delta delta = (escapement_delta){ 0.0, 0.0 };
725 		GetEscapements(string, numChars, length, escapementArray, delta);
726 
727 		truncate_string(string, mode, width, result,
728 						escapementArray, fSize, ellipsisWidth, length, numChars);
729 
730 		inOut->SetTo(result);
731 
732 		delete[] escapementArray;
733 		delete[] result;
734 	}
735 }
736 
737