Basic FreeType renderer implementation.
[gdipp.git] / gdipp_server / ft_renderer.cpp
blob12d885537293fddcb5b106473f56dabfa15f495f
1 #include "stdafx.h"
2 #include "ft_renderer.h"
3 #include "gdipp_lib/lock.h"
4 #include "gdipp_server/freetype.h"
5 #include "gdipp_server/global.h"
6 #include "gdipp_server/helper.h"
8 namespace gdipp
11 ft_renderer::ft_renderer(rpc_session *render_session)
12 : renderer(render_session)
16 FT_F26Dot6 ft_renderer::get_embolden_diff(char font_weight_class, char text_weight_class)
18 // the embolden weight is based on the difference between demanded weight and the regular weight
20 const FT_F26Dot6 embolden_values[] = {-32, -16, 0, 16, 32};
21 const char embolden_class_count = sizeof(embolden_values) / sizeof(FT_F26Dot6);
22 const char regular_embolden_class = (embolden_class_count - 1) / 2;
24 char embolden_class = text_weight_class - font_weight_class + regular_embolden_class;
26 if (embolden_class < 0)
27 embolden_class = 0;
28 else if (embolden_class >= embolden_class_count)
29 embolden_class = embolden_class_count - 1;
31 return embolden_values[embolden_class];
34 void ft_renderer::get_font_width_height(const OUTLINETEXTMETRICW *outline_metrics, FT_Short xAvgCharWidth, FT_UInt &font_width, FT_UInt &font_height)
37 while the height in FreeType scaler has the same meaning as the height value in LOGFONT structure, the width is different
38 what we know is, when the width in LOGFONT is the xAvgCharWidth (from the OS/2 table), the corresponding FreeType scaler width is the height
39 therefore we need conversion when LOGFONT width is not 0
40 simple calculation yields freetype_width = logfont_width * em_square / xAvgCharWidth
41 note that the tmAveCharWidth field in TEXTMETRIC is the actual LOGFONT width, which is never 0
44 assert(outline_metrics != NULL);
46 font_height = outline_metrics->otmTextMetrics.tmHeight - outline_metrics->otmTextMetrics.tmInternalLeading;
48 if (xAvgCharWidth == 0)
49 font_width = font_height * outline_metrics->otmTextMetrics.tmDigitizedAspectX / outline_metrics->otmTextMetrics.tmDigitizedAspectY;
50 else
52 // compare the xAvgCharWidth against the current average char width
53 font_width = outline_metrics->otmTextMetrics.tmAveCharWidth * outline_metrics->otmEMSquare / xAvgCharWidth;
57 FT_ULong ft_renderer::make_load_flags(const render_config_static *render_config, FT_Render_Mode render_mode)
59 FT_ULong load_flags = FT_LOAD_CROP_BITMAP | (render_config->embedded_bitmap ? 0 : FT_LOAD_NO_BITMAP);
61 if (render_config->hinting == 0)
62 load_flags |= FT_LOAD_NO_HINTING;
63 else
65 switch (render_config->hinting)
67 case 1:
68 load_flags |= FT_LOAD_TARGET_LIGHT;
69 break;
70 case 3:
71 load_flags |= FT_LOAD_TARGET_MONO;
72 break;
73 default:
75 if (render_mode == FT_RENDER_MODE_LCD)
76 load_flags |= FT_LOAD_TARGET_LCD;
77 else
78 load_flags |= FT_LOAD_TARGET_NORMAL;
79 break;
83 switch (render_config->auto_hinting)
85 case 0:
86 load_flags |= FT_LOAD_NO_AUTOHINT;
87 break;
88 case 2:
89 load_flags |= FT_LOAD_FORCE_AUTOHINT;
90 break;
91 default:
92 load_flags |= FT_LOAD_DEFAULT;
93 break;
97 return load_flags;
100 void ft_renderer::oblique_outline(const FT_Outline *outline, double slant_adv)
102 // advance of slant on x-axis
103 FT_Matrix oblique_mat = {float_to_16dot16(1), float_to_16dot16(slant_adv), 0, float_to_16dot16(1)};
104 FT_Outline_Transform(outline, &oblique_mat);
107 bool ft_renderer::generate_outline_glyph(FT_Glyph *glyph,
108 WORD glyph_index,
109 const FTC_Scaler scaler,
110 FT_F26Dot6 embolden,
111 FT_ULong load_flags,
112 bool is_italic) const
114 FT_Error ft_error;
116 FT_Glyph cached_glyph;
119 // the FreeType function seems not thread-safe
120 lock l(lock::SERVER_FREETYPE);
121 ft_error = FTC_ImageCache_LookupScaler(ft_glyph_cache, scaler, load_flags, glyph_index, &cached_glyph, NULL);
122 if (ft_error != 0)
123 return NULL;
126 // some fonts are embedded with pre-rendered glyph bitmap
127 // in that case, use original ExtTextOutW
128 if (cached_glyph->format != FT_GLYPH_FORMAT_OUTLINE)
129 return NULL;
131 // if italic style is demanded, and the font has italic glyph, do oblique transformation
132 const OUTLINETEXTMETRICW *outline_metrics = reinterpret_cast<const OUTLINETEXTMETRICW *>(_session->outline_metrics_buf);
133 const bool is_oblique = ((outline_metrics->otmTextMetrics.tmItalic != 0) && !is_italic);
134 const bool need_embolden = (embolden != 0);
135 const bool need_glyph_copy = (is_oblique || need_embolden);
137 if (need_glyph_copy)
139 FT_Glyph_Copy(cached_glyph, glyph);
140 FT_Outline *glyph_outline = &(reinterpret_cast<FT_OutlineGlyph>(*glyph)->outline);
142 // it seems faster if oblique first, and then embolden
143 if (is_oblique)
144 oblique_outline(glyph_outline, 0.3);
146 if (need_embolden)
148 ft_error = FT_Outline_Embolden(glyph_outline, embolden);
149 assert(ft_error == 0);
152 else
153 *glyph = cached_glyph;
155 return need_glyph_copy;
158 const FT_Glyph ft_renderer::generate_bitmap_glyph(WORD glyph_index,
159 const FTC_Scaler scaler,
160 FT_Render_Mode render_mode,
161 FT_F26Dot6 embolden,
162 FT_ULong load_flags,
163 bool is_italic,
164 bool request_outline,
165 uint128_t render_trait) const
167 FT_Error ft_error;
168 FT_Glyph glyph;
170 if (request_outline)
172 generate_outline_glyph(&glyph, glyph_index, scaler, embolden, load_flags, is_italic);
173 return glyph;
176 const glyph_cache::char_id_type char_id = glyph_cache::get_char_id(render_trait, glyph_index, true);
177 glyph = glyph_cache_instance.lookup_glyph(char_id);
178 if (glyph == NULL)
180 // no cached glyph, or outline glyph is requested, generate outline
181 const bool is_local_glyph = generate_outline_glyph(&glyph, glyph_index, scaler, embolden, load_flags, is_italic);
183 // outline -> bitmap conversion
185 // the FreeType function seems not thread-safe
186 lock l(lock::SERVER_FREETYPE);
187 ft_error = FT_Glyph_To_Bitmap(&glyph, render_mode, NULL, is_local_glyph);
188 if (ft_error != 0)
189 return NULL;
192 glyph_cache_instance.store_glyph(char_id, glyph);
195 return glyph;
198 bool ft_renderer::generate_glyph_run(bool is_glyph_index, LPCWSTR lpString, UINT c, glyph_run *new_glyph_run, bool request_outline)
200 const OUTLINETEXTMETRICW *curr_outline_metrics = reinterpret_cast<const OUTLINETEXTMETRICW *>(_session->outline_metrics_buf);
201 const render_config_static *curr_render_config = _session->render_config;
202 uint128_t curr_render_trait = _session->render_trait;
203 const wchar_t *curr_font_face = metric_face_name(curr_outline_metrics);
204 const os2_metrics *curr_os2 = font_mgr_instance.lookup_os2_metrics(_session->font_id);
205 const wchar_t *session_font_family = metric_family_name(curr_outline_metrics);
207 FTC_ScalerRec scaler = {};
208 scaler.face_id = _session->font_id;
209 scaler.pixel = 1;
210 get_font_width_height(curr_outline_metrics, (_session->log_font.lfWidth == 0 ? 0 : curr_os2->get_xAvgCharWidth()), scaler.height, scaler.width);
212 FT_F26Dot6 curr_embolden = 0;
213 if (_session->log_font.lfWeight != FW_DONTCARE)
215 // embolden if some weight is demanded
216 curr_embolden = curr_render_config->embolden + get_embolden_diff(curr_os2->get_weight_class(), static_cast<char>(_session->log_font.lfWeight));
219 FT_ULong curr_load_flags = make_load_flags(curr_render_config, _session->render_mode);
221 if (is_glyph_index)
223 // directly render glyph indices with the current DC font
225 for (UINT i = 0; i < c; ++i)
227 const FT_Glyph new_glyph = generate_bitmap_glyph(lpString[i],
228 &scaler,
229 _session->render_mode,
230 curr_embolden,
231 curr_load_flags,
232 curr_os2->is_italic(),
233 request_outline,
234 curr_render_trait);
235 RECT ctrl_box = {}, black_box = {};
237 if (new_glyph == NULL)
239 if (request_outline)
240 return false;
242 else if (curr_render_config->kerning && i > 0 && !request_outline)
244 ctrl_box.left = get_freetype_kern(&scaler, lpString[i-1], lpString[i]);
245 ctrl_box.right = ctrl_box.left;
248 new_glyph_run->glyphs.push_back(new_glyph);
249 new_glyph_run->ctrl_boxes.push_back(ctrl_box);
250 new_glyph_run->black_boxes.push_back(black_box);
253 else
255 FT_Render_Mode curr_render_mode = _session->render_mode;
257 UINT rendered_count = 0;
258 int font_link_index = 0;
259 std::wstring final_string(lpString, c);
260 std::vector<unsigned short> glyph_indices(c);
262 new_glyph_run->glyphs.resize(c);
263 new_glyph_run->ctrl_boxes.resize(c);
264 new_glyph_run->black_boxes.resize(c);
266 while (true)
268 font_mgr_instance.lookup_glyph_indices(scaler.face_id, final_string.data(), c, &glyph_indices[0]);
270 std::vector<FT_Glyph>::iterator glyph_iter;
271 std::vector<RECT>::iterator ctrl_iter, black_iter;
272 UINT i;
273 for (glyph_iter = new_glyph_run->glyphs.begin(), ctrl_iter = new_glyph_run->ctrl_boxes.begin(), black_iter = new_glyph_run->black_boxes.begin(), i = 0;
274 i < c; i++, glyph_iter++, ctrl_iter++, black_iter++)
276 if (final_string[i] == L'\0')
277 continue;
279 // do not render control characters, even the corresponding glyphs exist in font
280 if (iswcntrl(final_string[i]) && !request_outline)
281 *glyph_iter = NULL;
282 else if (glyph_indices[i] != 0xffff)
284 *glyph_iter = generate_bitmap_glyph(glyph_indices[i],
285 &scaler,
286 curr_render_mode,
287 curr_embolden,
288 curr_load_flags,
289 curr_os2->is_italic(),
290 request_outline,
291 curr_render_trait);
293 if (*glyph_iter == NULL)
295 if (request_outline)
296 return false;
298 else if (curr_render_config->kerning && i > 0 && !request_outline)
300 ctrl_iter->left = get_freetype_kern(&scaler, glyph_indices[i-1], glyph_indices[i]);
301 ctrl_iter->right = ctrl_iter->left;
304 else
305 continue;
307 final_string[i] = L'\0';
308 rendered_count += 1;
311 if (rendered_count >= c)
313 assert(rendered_count == c);
314 break;
317 // font linking
319 const font_link_node *curr_link = font_link_instance.lookup_link(session_font_family, font_link_index);
320 if (curr_link == NULL)
321 return false;
322 font_link_index += 1;
324 LOGFONTW linked_log_font = _session->log_font;
326 this reset is essential to make GetGlyphIndices work correctly
327 for example, lfOutPrecision might be OUT_PS_ONLY_PRECIS for Myriad Pro
328 if create HFONT of Microsoft YaHei with such lfOutPrecision, GetGlyphIndices always fails
330 linked_log_font.lfOutPrecision = OUT_DEFAULT_PRECIS;
331 wcsncpy_s(linked_log_font.lfFaceName, curr_link->font_family.c_str(), LF_FACESIZE);
333 BYTE *curr_outline_metrics_buf;
334 unsigned long curr_outline_metrics_size;
335 scaler.face_id = font_mgr_instance.register_font(&linked_log_font, &curr_outline_metrics_buf, &curr_outline_metrics_size);
336 assert(scaler.face_id != NULL);
338 // reload metrics for the linked font
340 const OUTLINETEXTMETRICW *curr_outline_metrics = reinterpret_cast<const OUTLINETEXTMETRICW *>(curr_outline_metrics_buf);
341 curr_font_face = metric_face_name(curr_outline_metrics);
343 if (curr_link->scaling != 1.0)
345 // apply font linking scaling factor
346 scaler.width = static_cast<FT_UInt>(scaler.width * curr_link->scaling);
347 scaler.height = static_cast<FT_UInt>(scaler.height * curr_link->scaling);
350 curr_os2 = font_mgr_instance.lookup_os2_metrics(scaler.face_id);
351 const char font_weight_class = curr_os2->get_weight_class();
352 const LONG point_size = (linked_log_font.lfHeight > 0 ? linked_log_font.lfHeight : -MulDiv(linked_log_font.lfHeight, 72, curr_outline_metrics->otmTextMetrics.tmDigitizedAspectY));
354 curr_render_config = font_render_config_cache_instance.get_font_render_config(!!font_weight_class,
355 curr_os2->is_italic(),
356 point_size,
357 curr_font_face);
359 delete[] curr_outline_metrics_buf;
361 if (!get_render_mode(curr_render_config->render_mode, _session->bits_per_pixel, _session->log_font.lfQuality, &curr_render_mode))
362 return false;
364 curr_render_trait = generate_render_trait(&linked_log_font, curr_render_mode);
366 curr_embolden = 0;
367 if (linked_log_font.lfWeight != FW_DONTCARE)
368 curr_embolden = curr_render_config->embolden + get_embolden_diff(font_weight_class, static_cast<char>(linked_log_font.lfWeight));
370 curr_load_flags = make_load_flags(curr_render_config, _session->render_mode);
374 return true;
377 bool ft_renderer::render(bool is_glyph_index, LPCWSTR lpString, UINT c, glyph_run *new_glyph_run)
379 bool b_ret;
381 b_ret = generate_glyph_run(is_glyph_index, lpString, c, new_glyph_run, false);
382 if (!b_ret)
383 return false;
385 POINT pen_pos = {};
387 std::vector<FT_Glyph>::iterator glyph_iter;
388 std::vector<RECT>::iterator ctrl_iter, black_iter;
389 for (glyph_iter = new_glyph_run->glyphs.begin(), ctrl_iter = new_glyph_run->ctrl_boxes.begin(), black_iter = new_glyph_run->black_boxes.begin();
390 glyph_iter != new_glyph_run->glyphs.end(); ++glyph_iter, ++ctrl_iter, ++black_iter)
392 FT_Int glyph_left = 0, glyph_width = 0;
393 FT_Vector glyph_advance = {};
395 const FT_BitmapGlyph bmp_glyph = reinterpret_cast<FT_BitmapGlyph>(*glyph_iter);
396 if (bmp_glyph != NULL)
398 glyph_left = bmp_glyph->left;
399 glyph_width = get_glyph_bmp_width(bmp_glyph->bitmap);
400 glyph_advance = bmp_glyph->root.advance;
403 ctrl_iter->left += pen_pos.x;
404 ctrl_iter->top += pen_pos.y;
405 black_iter->left = ctrl_iter->left + glyph_left;
406 black_iter->top = ctrl_iter->top;
408 pen_pos.x += int_from_16dot16(glyph_advance.x);
409 pen_pos.y += int_from_16dot16(glyph_advance.y);
411 ctrl_iter->right += pen_pos.x;
412 ctrl_iter->bottom += pen_pos.y;
413 black_iter->right = black_iter->left + glyph_width;
414 black_iter->bottom = ctrl_iter->bottom;
417 return true;