xref: /haiku/src/servers/app/font/FontEngine.cpp (revision 54624bda43f13312a491fe1c91d22834be3374f5)
1 /*
2  * Copyright 2007, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Maxim Shemanarev <mcseemagg@yahoo.com>
7  *		Stephan Aßmus <superstippi@gmx.de>
8  *		Anthony Lee <don.anthony.lee@gmail.com>
9  *		Andrej Spielmann, <andrej.spielmann@seh.ox.ac.uk>
10  */
11 
12 //----------------------------------------------------------------------------
13 // Anti-Grain Geometry - Version 2.4
14 // Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
15 //
16 // Permission to copy, use, modify, sell and distribute this software
17 // is granted provided this copyright notice appears in all copies.
18 // This software is provided "as is" without express or implied
19 // warranty, and with no claim as to its suitability for any purpose.
20 //
21 //----------------------------------------------------------------------------
22 // Contact: mcseem@antigrain.com
23 //			mcseemagg@yahoo.com
24 //			http://www.antigrain.com
25 //----------------------------------------------------------------------------
26 
27 
28 #include "FontEngine.h"
29 
30 #include FT_GLYPH_H
31 #include FT_OUTLINE_H
32 #include FT_LCD_FILTER_H
33 
34 #include <stdio.h>
35 
36 #include <agg_bitset_iterator.h>
37 #include <agg_renderer_scanline.h>
38 
39 #include "GlobalSubpixelSettings.h"
40 
41 
42 static const bool kFlipY = true;
43 
44 
45 static inline double
46 int26p6_to_dbl(int p)
47 {
48 	return double(p) / 64.0;
49 }
50 
51 
52 static inline int
53 dbl_to_int26p6(double p)
54 {
55 	return int(p * 64.0 + 0.5);
56 }
57 
58 
59 template<class PathStorage>
60 bool
61 decompose_ft_outline(const FT_Outline& outline, bool flip_y, PathStorage& path)
62 {
63 	typedef typename PathStorage::value_type value_type;
64 
65 	FT_Vector v_last;
66 	FT_Vector v_control;
67 	FT_Vector v_start;
68 	double x1, y1, x2, y2, x3, y3;
69 
70 	FT_Vector* point;
71 	FT_Vector* limit;
72 	char* tags;
73 
74 	int   n;		 // index of contour in outline
75 	int   first;	 // index of first point in contour
76 	char  tag;	   // current point's state
77 
78 	first = 0;
79 
80 	for (n = 0; n < outline.n_contours; n++) {
81 		int  last;  // index of last point in contour
82 
83 		last  = outline.contours[n];
84 		limit = outline.points + last;
85 
86 		v_start = outline.points[first];
87 		v_last  = outline.points[last];
88 
89 		v_control = v_start;
90 
91 		point = outline.points + first;
92 		tags  = outline.tags  + first;
93 		tag   = FT_CURVE_TAG(tags[0]);
94 
95 		// A contour cannot start with a cubic control point!
96 		if (tag == FT_CURVE_TAG_CUBIC)
97 			return false;
98 
99 		// check first point to determine origin
100 		if ( tag == FT_CURVE_TAG_CONIC) {
101 			// first point is conic control.  Yes, this happens.
102 			if (FT_CURVE_TAG(outline.tags[last]) == FT_CURVE_TAG_ON) {
103 				// start at last point if it is on the curve
104 				v_start = v_last;
105 				limit--;
106 			} else {
107 				// if both first and last points are conic,
108 				// start at their middle and record its position
109 				// for closure
110 				v_start.x = (v_start.x + v_last.x) / 2;
111 				v_start.y = (v_start.y + v_last.y) / 2;
112 
113 				v_last = v_start;
114 			}
115 			point--;
116 			tags--;
117 		}
118 
119 		x1 = int26p6_to_dbl(v_start.x);
120 		y1 = int26p6_to_dbl(v_start.y);
121 		if (flip_y) y1 = -y1;
122 		path.move_to(value_type(dbl_to_int26p6(x1)),
123 					 value_type(dbl_to_int26p6(y1)));
124 
125 		while(point < limit) {
126 			point++;
127 			tags++;
128 
129 			tag = FT_CURVE_TAG(tags[0]);
130 			switch(tag) {
131 				case FT_CURVE_TAG_ON: { // emit a single line_to
132 					x1 = int26p6_to_dbl(point->x);
133 					y1 = int26p6_to_dbl(point->y);
134 					if (flip_y) y1 = -y1;
135 					path.line_to(value_type(dbl_to_int26p6(x1)),
136 								 value_type(dbl_to_int26p6(y1)));
137 					//path.line_to(conv(point->x), flip_y ? -conv(point->y) : conv(point->y));
138 					continue;
139 				}
140 
141 				case FT_CURVE_TAG_CONIC: { // consume conic arcs
142 					v_control.x = point->x;
143 					v_control.y = point->y;
144 
145 				Do_Conic:
146 					if (point < limit) {
147 						FT_Vector vec;
148 						FT_Vector v_middle;
149 
150 						point++;
151 						tags++;
152 						tag = FT_CURVE_TAG(tags[0]);
153 
154 						vec.x = point->x;
155 						vec.y = point->y;
156 
157 						if (tag == FT_CURVE_TAG_ON) {
158 							x1 = int26p6_to_dbl(v_control.x);
159 							y1 = int26p6_to_dbl(v_control.y);
160 							x2 = int26p6_to_dbl(vec.x);
161 							y2 = int26p6_to_dbl(vec.y);
162 							if (flip_y) { y1 = -y1; y2 = -y2; }
163 							path.curve3(value_type(dbl_to_int26p6(x1)),
164 										value_type(dbl_to_int26p6(y1)),
165 										value_type(dbl_to_int26p6(x2)),
166 										value_type(dbl_to_int26p6(y2)));
167 							continue;
168 						}
169 
170 						if (tag != FT_CURVE_TAG_CONIC)
171 							return false;
172 
173 						v_middle.x = (v_control.x + vec.x) / 2;
174 						v_middle.y = (v_control.y + vec.y) / 2;
175 
176 						x1 = int26p6_to_dbl(v_control.x);
177 						y1 = int26p6_to_dbl(v_control.y);
178 						x2 = int26p6_to_dbl(v_middle.x);
179 						y2 = int26p6_to_dbl(v_middle.y);
180 						if (flip_y) { y1 = -y1; y2 = -y2; }
181 						path.curve3(value_type(dbl_to_int26p6(x1)),
182 									value_type(dbl_to_int26p6(y1)),
183 									value_type(dbl_to_int26p6(x2)),
184 									value_type(dbl_to_int26p6(y2)));
185 
186 						//path.curve3(conv(v_control.x),
187 						//			flip_y ? -conv(v_control.y) : conv(v_control.y),
188 						//			conv(v_middle.x),
189 						//			flip_y ? -conv(v_middle.y) : conv(v_middle.y));
190 
191 						v_control = vec;
192 						goto Do_Conic;
193 					}
194 
195 					x1 = int26p6_to_dbl(v_control.x);
196 					y1 = int26p6_to_dbl(v_control.y);
197 					x2 = int26p6_to_dbl(v_start.x);
198 					y2 = int26p6_to_dbl(v_start.y);
199 					if (flip_y) { y1 = -y1; y2 = -y2; }
200 					path.curve3(value_type(dbl_to_int26p6(x1)),
201 								value_type(dbl_to_int26p6(y1)),
202 								value_type(dbl_to_int26p6(x2)),
203 								value_type(dbl_to_int26p6(y2)));
204 
205 					//path.curve3(conv(v_control.x),
206 					//			flip_y ? -conv(v_control.y) : conv(v_control.y),
207 					//			conv(v_start.x),
208 					//			flip_y ? -conv(v_start.y) : conv(v_start.y));
209 					goto Close;
210 				}
211 
212 				default: { // FT_CURVE_TAG_CUBIC
213 					FT_Vector vec1, vec2;
214 
215 					if (point + 1 > limit || FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC)
216 						return false;
217 
218 					vec1.x = point[0].x;
219 					vec1.y = point[0].y;
220 					vec2.x = point[1].x;
221 					vec2.y = point[1].y;
222 
223 					point += 2;
224 					tags  += 2;
225 
226 					if (point <= limit) {
227 						FT_Vector vec;
228 
229 						vec.x = point->x;
230 						vec.y = point->y;
231 
232 						x1 = int26p6_to_dbl(vec1.x);
233 						y1 = int26p6_to_dbl(vec1.y);
234 						x2 = int26p6_to_dbl(vec2.x);
235 						y2 = int26p6_to_dbl(vec2.y);
236 						x3 = int26p6_to_dbl(vec.x);
237 						y3 = int26p6_to_dbl(vec.y);
238 						if (flip_y) { y1 = -y1; y2 = -y2; y3 = -y3; }
239 						path.curve4(value_type(dbl_to_int26p6(x1)),
240 									value_type(dbl_to_int26p6(y1)),
241 									value_type(dbl_to_int26p6(x2)),
242 									value_type(dbl_to_int26p6(y2)),
243 									value_type(dbl_to_int26p6(x3)),
244 									value_type(dbl_to_int26p6(y3)));
245 
246 						//path.curve4(conv(vec1.x),
247 						//			flip_y ? -conv(vec1.y) : conv(vec1.y),
248 						//			conv(vec2.x),
249 						//			flip_y ? -conv(vec2.y) : conv(vec2.y),
250 						//			conv(vec.x),
251 						//			flip_y ? -conv(vec.y) : conv(vec.y));
252 						continue;
253 					}
254 
255 					x1 = int26p6_to_dbl(vec1.x);
256 					y1 = int26p6_to_dbl(vec1.y);
257 					x2 = int26p6_to_dbl(vec2.x);
258 					y2 = int26p6_to_dbl(vec2.y);
259 					x3 = int26p6_to_dbl(v_start.x);
260 					y3 = int26p6_to_dbl(v_start.y);
261 					if (flip_y) { y1 = -y1; y2 = -y2; y3 = -y3; }
262 					path.curve4(value_type(dbl_to_int26p6(x1)),
263 								value_type(dbl_to_int26p6(y1)),
264 								value_type(dbl_to_int26p6(x2)),
265 								value_type(dbl_to_int26p6(y2)),
266 								value_type(dbl_to_int26p6(x3)),
267 								value_type(dbl_to_int26p6(y3)));
268 
269 					//path.curve4(conv(vec1.x),
270 					//			flip_y ? -conv(vec1.y) : conv(vec1.y),
271 					//			conv(vec2.x),
272 					//			flip_y ? -conv(vec2.y) : conv(vec2.y),
273 					//			conv(v_start.x),
274 					//			flip_y ? -conv(v_start.y) : conv(v_start.y));
275 					goto Close;
276 				}
277 			}
278 		}
279 
280 		path.close_polygon();
281 
282    Close:
283 		first = last + 1;
284 	}
285 
286 	return true;
287 }
288 
289 
290 template<class Scanline, class ScanlineStorage>
291 void
292 decompose_ft_bitmap_mono(const FT_Bitmap& bitmap, int x, int y,
293 	bool flip_y, Scanline& sl, ScanlineStorage& storage)
294 {
295 	const uint8* buf = (const uint8*)bitmap.buffer;
296 	int pitch = bitmap.pitch;
297 	sl.reset(x, x + bitmap.width);
298 	storage.prepare();
299 	if (flip_y) {
300 		buf += bitmap.pitch * (bitmap.rows - 1);
301 		y += bitmap.rows;
302 		pitch = -pitch;
303 	}
304 	for (unsigned int i = 0; i < bitmap.rows; i++) {
305 		sl.reset_spans();
306 		agg::bitset_iterator bits(buf, 0);
307 		for (unsigned int j = 0; j < bitmap.width; j++) {
308 			if (bits.bit())
309 				sl.add_cell(x + j, agg::cover_full);
310 			++bits;
311 		}
312 		buf += pitch;
313 		if (sl.num_spans()) {
314 			sl.finalize(y - i - 1);
315 			storage.render(sl);
316 		}
317 	}
318 }
319 
320 
321 template<class Scanline, class ScanlineStorage>
322 void
323 decompose_ft_bitmap_gray8(const FT_Bitmap& bitmap, int x, int y,
324 	bool flip_y, Scanline& sl, ScanlineStorage& storage)
325 {
326 	const uint8* buf = (const uint8*)bitmap.buffer;
327 	int pitch = bitmap.pitch;
328 	sl.reset(x, x + bitmap.width);
329 	storage.prepare();
330 	if (flip_y) {
331 		buf += bitmap.pitch * (bitmap.rows - 1);
332 		y += bitmap.rows;
333 		pitch = -pitch;
334 	}
335 	for (unsigned int i = 0; i < bitmap.rows; i++) {
336 		sl.reset_spans();
337 
338 		if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
339 			// font has built-in mono bitmap
340 			agg::bitset_iterator bits(buf, 0);
341 			for (unsigned int j = 0; j < bitmap.width; j++) {
342 				if (bits.bit())
343 					sl.add_cell(x + j, agg::cover_full);
344 				++bits;
345 			}
346 		} else {
347 			const uint8* p = buf;
348 			for (unsigned int j = 0; j < bitmap.width; j++) {
349 				if (*p)
350 					sl.add_cell(x + j, *p);
351 				++p;
352 			}
353 		}
354 
355 		buf += pitch;
356 		if (sl.num_spans()) {
357 			sl.finalize(y - i - 1);
358 			storage.render(sl);
359 		}
360 	}
361 }
362 
363 
364 template<class Scanline, class ScanlineStorage>
365 void
366 decompose_ft_bitmap_subpix(const FT_Bitmap& bitmap, int x, int y,
367 	bool flip_y, Scanline& sl, ScanlineStorage& storage)
368 {
369 	const uint8* buf = (const uint8*)bitmap.buffer;
370 	int pitch = bitmap.pitch;
371 	sl.reset(x, x + bitmap.width / 3);
372 	storage.prepare();
373 
374 	if (flip_y) {
375 		buf += bitmap.pitch * (bitmap.rows - 1);
376 		y += bitmap.rows;
377 		pitch = -pitch;
378 	}
379 
380 	for (unsigned int i = 0; i < bitmap.rows; i++) {
381 		sl.reset_spans();
382 
383 		if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
384 			// font has built-in mono bitmap
385 			agg::bitset_iterator bits(buf, 0);
386 			for (unsigned int j = 0; j < bitmap.width; j++) {
387 				if (bits.bit()) {
388 					sl.add_cell(x + j,
389 						agg::cover_full, agg::cover_full, agg::cover_full);
390 				}
391 				++bits;
392 			}
393 		} else {
394 			const uint8* p = buf;
395 			int w = bitmap.width / 3;
396 
397 			for (int j = 0; j < w; j++) {
398 				if (p[0] || p[1] || p[2])
399 					sl.add_cell(x + j, p[0], p[1], p[2]);
400 				p += 3;
401 			}
402 		}
403 
404 		buf += pitch;
405 		if (sl.num_spans()) {
406 			sl.finalize(y - i - 1);
407 			storage.render(sl);
408 		}
409 	}
410 }
411 
412 
413 // #pragma mark -
414 
415 
416 FontEngine::FontEngine()
417 	:
418 	fLastError(0),
419 	fLibraryInitialized(false),
420 	fLibrary(0),
421 	fFace(NULL),
422 
423 	fGlyphRendering(glyph_ren_native_gray8),
424 	fHinting(true),
425 
426 	fDataSize(0),
427 	fDataType(glyph_data_invalid),
428 	fBounds(1, 1, 0, 0),
429 	fAdvanceX(0.0),
430 	fAdvanceY(0.0),
431 	fInsetLeft(0.0),
432 	fInsetRight(0.0),
433 
434 	fPath(),
435 	fCurves(fPath),
436 	fScanlineAA(),
437 	fScanlineBin(),
438 	fScanlineSubpix(),
439 	fScanlineStorageAA(),
440 	fScanlineStorageBin(),
441 	fScanlineStorageSubpix()
442 {
443 	fCurves.approximation_scale(4.0);
444 
445 	fLastError = FT_Init_FreeType(&fLibrary);
446 	if (fLastError == 0)
447 		fLibraryInitialized = true;
448 }
449 
450 
451 FontEngine::~FontEngine()
452 {
453 	FT_Done_Face(fFace);
454 
455 	if (fLibraryInitialized)
456 		FT_Done_FreeType(fLibrary);
457 }
458 
459 
460 unsigned
461 FontEngine::CountFaces() const
462 {
463 	if (fFace)
464 		return fFace->num_faces;
465 
466 	return 0;
467 }
468 
469 
470 uint32
471 FontEngine::GlyphIndexForGlyphCode(uint32 glyphCode) const
472 {
473 	return FT_Get_Char_Index(fFace, glyphCode);
474 }
475 
476 
477 bool
478 FontEngine::PrepareGlyph(uint32 glyphIndex)
479 {
480 	FT_Int32 loadFlags = fHinting ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING;
481 	loadFlags |= fGlyphRendering == glyph_ren_subpix ?
482 		FT_LOAD_TARGET_LCD : FT_LOAD_TARGET_NORMAL;
483 
484 	// Load unscaled and without hinting to get precise advance values
485 	// for B_CHAR_SPACING
486 	fLastError = FT_Load_Glyph(fFace, glyphIndex, loadFlags
487 		| FT_LOAD_NO_HINTING | FT_LOAD_NO_SCALE);
488 
489 	fPreciseAdvanceX = (double)fFace->glyph->advance.x / fFace->units_per_EM;
490 	fPreciseAdvanceY = (double)fFace->glyph->advance.y / fFace->units_per_EM;
491 
492 	// Need to load again with hinting.
493 	fLastError = FT_Load_Glyph(fFace, glyphIndex, loadFlags);
494 
495 	if (fLastError != 0)
496 		return false;
497 
498 	fAdvanceX = int26p6_to_dbl(fFace->glyph->advance.x);
499 	fAdvanceY = int26p6_to_dbl(fFace->glyph->advance.y);
500 
501 	fInsetLeft = int26p6_to_dbl(fFace->glyph->metrics.horiBearingX);
502 	fInsetRight = int26p6_to_dbl(fFace->glyph->metrics.horiBearingX
503 		+ fFace->glyph->metrics.width - fFace->glyph->metrics.horiAdvance);
504 
505 	switch(fGlyphRendering) {
506 		case glyph_ren_native_mono:
507 			fLastError = FT_Render_Glyph(fFace->glyph, FT_RENDER_MODE_MONO);
508 			if (fLastError == 0) {
509 				decompose_ft_bitmap_mono(fFace->glyph->bitmap,
510 					fFace->glyph->bitmap_left, kFlipY ?
511 					-fFace->glyph->bitmap_top : fFace->glyph->bitmap_top,
512 					kFlipY, fScanlineBin, fScanlineStorageBin);
513 				fBounds.x1 = fScanlineStorageBin.min_x();
514 				fBounds.y1 = fScanlineStorageBin.min_y();
515 				fBounds.x2 = fScanlineStorageBin.max_x();
516 				fBounds.y2 = fScanlineStorageBin.max_y();
517 				fDataSize = fScanlineStorageBin.byte_size();
518 				fDataType = glyph_data_mono;
519 				return true;
520 			}
521 			break;
522 
523 
524 		case glyph_ren_native_gray8:
525 			fLastError = FT_Render_Glyph(fFace->glyph, FT_RENDER_MODE_NORMAL);
526 			if (fLastError == 0) {
527 				decompose_ft_bitmap_gray8(fFace->glyph->bitmap,
528 					fFace->glyph->bitmap_left, kFlipY ?
529 					-fFace->glyph->bitmap_top : fFace->glyph->bitmap_top,
530 					kFlipY, fScanlineAA, fScanlineStorageAA);
531 				fBounds.x1 = fScanlineStorageAA.min_x();
532 				fBounds.y1 = fScanlineStorageAA.min_y();
533 				fBounds.x2 = fScanlineStorageAA.max_x();
534 				fBounds.y2 = fScanlineStorageAA.max_y();
535 				fDataSize = fScanlineStorageAA.byte_size();
536 				fDataType = glyph_data_gray8;
537 				return true;
538 			}
539 			break;
540 
541 
542 		case glyph_ren_subpix:
543 			fLastError = FT_Render_Glyph(fFace->glyph, FT_RENDER_MODE_LCD);
544 			if (fLastError == 0) {
545 				decompose_ft_bitmap_subpix(fFace->glyph->bitmap,
546 					fFace->glyph->bitmap_left, kFlipY ?
547 					-fFace->glyph->bitmap_top : fFace->glyph->bitmap_top,
548 					kFlipY, fScanlineSubpix, fScanlineStorageSubpix);
549 				fBounds.x1 = fScanlineStorageSubpix.min_x();
550 				fBounds.y1 = fScanlineStorageSubpix.min_y();
551 				fBounds.x2 = fScanlineStorageSubpix.max_x();
552 				fBounds.y2 = fScanlineStorageSubpix.max_y();
553 				fDataSize = fScanlineStorageSubpix.byte_size();
554 				fDataType = glyph_data_subpix;
555 				return true;
556 			}
557 			break;
558 
559 
560 		case glyph_ren_outline:
561 			fPath.remove_all();
562 			if (decompose_ft_outline(fFace->glyph->outline, kFlipY, fPath)) {
563 				agg::rect_d bounds = fPath.bounding_rect();
564 				fBounds.x1 = int(floor(bounds.x1));
565 				fBounds.y1 = int(floor(bounds.y1));
566 				fBounds.x2 = int(ceil(bounds.x2));
567 				fBounds.y2 = int(ceil(bounds.y2));
568 				fDataSize = fPath.byte_size();
569 				fDataType = glyph_data_outline;
570 				return true;
571 			}
572 			break;
573 	}
574 	return false;
575 }
576 
577 // #pragma mark -
578 
579 // WriteGlyphTo
580 void
581 FontEngine::WriteGlyphTo(uint8* data) const
582 {
583 	if (data && fDataSize) {
584 		switch(fDataType) {
585 			case glyph_data_mono:
586 				fScanlineStorageBin.serialize(data);
587 				break;
588 
589 			case glyph_data_gray8:
590 				fScanlineStorageAA.serialize(data);
591 				break;
592 
593 			case glyph_data_subpix:
594 				fScanlineStorageSubpix.serialize(data);
595 				break;
596 
597 			case glyph_data_outline:
598 				fPath.serialize(data);
599 				break;
600 
601 			case glyph_data_invalid:
602 			default:
603 				break;
604 		}
605 	}
606 }
607 
608 
609 // GetKerning
610 bool
611 FontEngine::GetKerning(uint32 first, uint32 second, double* x, double* y)
612 {
613 	if (fFace && first && second && FT_HAS_KERNING(fFace)) {
614 		FT_Vector delta;
615 		FT_Get_Kerning(fFace, first, second, FT_KERNING_DEFAULT, &delta);
616 
617 		double dx = int26p6_to_dbl(delta.x);
618 		double dy = int26p6_to_dbl(delta.y);
619 
620 		*x += dx;
621 		*y += dy;
622 
623 		return true;
624 	}
625 	return false;
626 }
627 
628 
629 // #pragma mark -
630 
631 
632 bool
633 FontEngine::Init(const char* fontFilePath, unsigned faceIndex, double size,
634 	FT_Encoding charMap, glyph_rendering ren_type, bool hinting,
635 	const char* fontFileBuffer, const long fontFileBufferSize)
636 {
637 	if (!fLibraryInitialized)
638 		return false;
639 
640 	fHinting = hinting;
641 
642 	fLastError = 0;
643 
644 	FT_Done_Face(fFace);
645 	if (fontFileBuffer && fontFileBufferSize) {
646 		fLastError = FT_New_Memory_Face(fLibrary,
647 			(const FT_Byte*)fontFileBuffer, fontFileBufferSize,
648 			faceIndex, &fFace);
649 	} else {
650 		fLastError = FT_New_Face(fLibrary, fontFilePath, faceIndex, &fFace);
651 	}
652 
653 	if (fLastError != 0)
654 		return false;
655 
656 	switch(ren_type) {
657 		case glyph_ren_native_mono:
658 			fGlyphRendering = glyph_ren_native_mono;
659 			break;
660 
661 		case glyph_ren_native_gray8:
662 			fGlyphRendering = glyph_ren_native_gray8;
663 			break;
664 
665 		case glyph_ren_subpix:
666 			fGlyphRendering = glyph_ren_subpix;
667 			break;
668 
669 		case glyph_ren_outline:
670 			if (FT_IS_SCALABLE(fFace))
671 				fGlyphRendering = glyph_ren_outline;
672 			else
673 				fGlyphRendering = glyph_ren_native_gray8;
674 			break;
675 	}
676 
677 	FT_Set_Pixel_Sizes(fFace,
678 		unsigned(size * 64.0) >> 6,		// pixel_width
679 		unsigned(size * 64.0) >> 6);	// pixel_height
680 
681 	if (charMap != FT_ENCODING_NONE) {
682 		fLastError = FT_Select_Charmap(fFace, charMap);
683 	} else {
684 		if (FT_Select_Charmap(fFace, FT_ENCODING_UNICODE) != 0)
685 			fLastError = FT_Select_Charmap(fFace, FT_ENCODING_NONE);
686 	}
687 
688 	return fLastError == 0;
689 }
690 
691