Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include <gcach_ftyp.hxx>
21 : #include <sallayout.hxx>
22 : #include <salgdi.hxx>
23 :
24 : #include <boost/static_assert.hpp>
25 :
26 : #include <i18nlangtag/mslangid.hxx>
27 :
28 : #include <vcl/svapp.hxx>
29 :
30 : #include <sal/alloca.h>
31 : #include <rtl/instance.hxx>
32 :
33 : #include <hb-icu.h>
34 : #include <hb-ot.h>
35 :
36 : #include <unicode/uscript.h>
37 :
38 : #include <com/sun/star/lang/XMultiServiceFactory.hpp>
39 : #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
40 : #include <comphelper/processfactory.hxx>
41 :
42 : // =======================================================================
43 : // layout implementation for ServerFont
44 : // =======================================================================
45 :
46 728181 : ServerFontLayout::ServerFontLayout( ServerFont& rFont )
47 728181 : : mrServerFont( rFont )
48 728181 : { }
49 :
50 91323 : void ServerFontLayout::DrawText( SalGraphics& rSalGraphics ) const
51 : {
52 91323 : rSalGraphics.DrawServerFontLayout( *this );
53 91323 : }
54 :
55 : // -----------------------------------------------------------------------
56 :
57 728181 : bool ServerFontLayout::LayoutText( ImplLayoutArgs& rArgs )
58 : {
59 728181 : ServerFontLayoutEngine* pLE = mrServerFont.GetLayoutEngine();
60 : assert(pLE);
61 728181 : bool bRet = pLE ? pLE->layout(*this, rArgs) : false;
62 728181 : return bRet;
63 : }
64 :
65 : // -----------------------------------------------------------------------
66 729128 : void ServerFontLayout::AdjustLayout( ImplLayoutArgs& rArgs )
67 : {
68 729128 : GenericSalLayout::AdjustLayout( rArgs );
69 :
70 : // apply asian kerning if the glyphs are not already formatted
71 729128 : if( (rArgs.mnFlags & SAL_LAYOUT_KERNING_ASIAN)
72 0 : && !(rArgs.mnFlags & SAL_LAYOUT_VERTICAL) )
73 0 : if( (rArgs.mpDXArray != NULL) || (rArgs.mnLayoutWidth != 0) )
74 0 : ApplyAsianKerning( rArgs.mpStr, rArgs.mnLength );
75 :
76 : // insert kashidas where requested by the formatting array
77 729128 : if( (rArgs.mnFlags & SAL_LAYOUT_KASHIDA_JUSTIFICATON) && rArgs.mpDXArray )
78 : {
79 0 : int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 );
80 0 : if( nKashidaIndex != 0 )
81 : {
82 0 : const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex );
83 0 : KashidaJustify( nKashidaIndex, rGM.GetCharWidth() );
84 : // TODO: kashida-GSUB/GPOS
85 : }
86 : }
87 729128 : }
88 :
89 1800 : void ServerFontLayout::setNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos,
90 : bool bRightToLeft)
91 : {
92 1800 : if (nCharPos < 0)
93 1800 : return;
94 :
95 : using namespace ::com::sun::star;
96 :
97 1800 : if (!mxBreak.is())
98 : {
99 : uno::Reference< lang::XMultiServiceFactory > xFactory =
100 1792 : comphelper::getProcessServiceFactory();
101 5376 : mxBreak = uno::Reference< i18n::XBreakIterator >(xFactory->createInstance(
102 5376 : "com.sun.star.i18n.BreakIterator"), uno::UNO_QUERY);
103 : }
104 :
105 1800 : LanguageTag aLangTag(rArgs.meLanguage);
106 3600 : lang::Locale aLocale(aLangTag.getLocale());
107 :
108 : //if position nCharPos is missing in the font, grab the entire grapheme and
109 : //mark all glyphs as missing so the whole thing is rendered with the same
110 : //font
111 3600 : OUString aRun(rArgs.mpStr);
112 : sal_Int32 nDone;
113 : sal_Int32 nGraphemeStartPos =
114 1800 : mxBreak->previousCharacters(aRun, nCharPos+1, aLocale,
115 1800 : i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
116 : sal_Int32 nGraphemeEndPos =
117 1800 : mxBreak->nextCharacters(aRun, nCharPos, aLocale,
118 1800 : i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
119 :
120 3600 : rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
121 : }
122 :
123 : // =======================================================================
124 :
125 0 : std::ostream &operator <<(std::ostream& s, ServerFont* pFont)
126 : {
127 : #ifndef SAL_LOG_INFO
128 : (void) pFont;
129 : #else
130 : FT_Face aFace = pFont->GetFtFace();
131 : const char* pName = FT_Get_Postscript_Name(aFace);
132 : if (pName)
133 : s << pName;
134 : else
135 : s << pFont->GetFontFileName();
136 : #endif
137 0 : return s;
138 : }
139 :
140 6864 : static hb_blob_t *getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
141 : {
142 : char pTagName[5];
143 6864 : pTagName[0] = (char)(nTableTag >> 24);
144 6864 : pTagName[1] = (char)(nTableTag >> 16);
145 6864 : pTagName[2] = (char)(nTableTag >> 8);
146 6864 : pTagName[3] = (char)(nTableTag);
147 6864 : pTagName[4] = 0;
148 :
149 6864 : ServerFont* pFont = (ServerFont*) pUserData;
150 :
151 : SAL_INFO("vcl.harfbuzz", "getFontTable(" << pFont << ", " << pTagName << ")");
152 :
153 : sal_uLong nLength;
154 6864 : const unsigned char* pBuffer = pFont->GetTable(pTagName, &nLength);
155 :
156 6864 : hb_blob_t* pBlob = NULL;
157 6864 : if (pBuffer != NULL)
158 5067 : pBlob = hb_blob_create((const char*) pBuffer, nLength, HB_MEMORY_MODE_READONLY, (void*) pBuffer, NULL);
159 :
160 6864 : return pBlob;
161 : }
162 :
163 12062817 : static hb_bool_t getFontGlyph(hb_font_t* /*font*/, void* pFontData,
164 : hb_codepoint_t ch, hb_codepoint_t vs,
165 : hb_codepoint_t* nGlyphIndex,
166 : void* /*pUserData*/)
167 : {
168 12062817 : ServerFont* pFont = (ServerFont*) pFontData;
169 12062817 : *nGlyphIndex = pFont->GetRawGlyphIndex(ch, vs);
170 :
171 12062817 : return *nGlyphIndex != 0;
172 : }
173 :
174 12057896 : static hb_position_t getGlyphAdvanceH(hb_font_t* /*font*/, void* pFontData,
175 : hb_codepoint_t nGlyphIndex,
176 : void* /*pUserData*/)
177 : {
178 12057896 : ServerFont* pFont = (ServerFont*) pFontData;
179 12057896 : const GlyphMetric& rGM = pFont->GetGlyphMetric(nGlyphIndex);
180 12057896 : return rGM.GetCharWidth() << 6;
181 : }
182 :
183 0 : static hb_position_t getGlyphAdvanceV(hb_font_t* /*font*/, void* /*pFontData*/,
184 : hb_codepoint_t /*nGlyphIndex*/,
185 : void* /*pUserData*/)
186 : {
187 : // XXX: vertical metrics
188 0 : return 0;
189 : }
190 :
191 36153860 : static hb_bool_t getGlyphOriginH(hb_font_t* /*font*/, void* /*pFontData*/,
192 : hb_codepoint_t /*nGlyphIndex*/,
193 : hb_position_t* /*x*/, hb_position_t* /*y*/,
194 : void* /*pUserData*/)
195 : {
196 : // the horizontal origin is always (0, 0)
197 36153860 : return true;
198 : }
199 :
200 0 : static hb_bool_t getGlyphOriginV(hb_font_t* /*font*/, void* /*pFontData*/,
201 : hb_codepoint_t /*nGlyphIndex*/,
202 : hb_position_t* /*x*/, hb_position_t* /*y*/,
203 : void* /*pUserData*/)
204 : {
205 : // XXX: vertical origin
206 0 : return true;
207 : }
208 :
209 8166 : static hb_position_t getGlyphKerningH(hb_font_t* /*font*/, void* pFontData,
210 : hb_codepoint_t nGlyphIndex1, hb_codepoint_t nGlyphIndex2,
211 : void* /*pUserData*/)
212 : {
213 : // This callback is for old style 'kern' table, GPOS kerning is handled by HarfBuzz directly
214 :
215 : // XXX: there is ServerFont::GetKernPairs() but it does many "smart" things
216 : // that I'm not sure about, so I'm using FreeType directly
217 : // P.S. if we decided not to use ServerFont::GetKernPairs() then it and all
218 : // other implementattions should be removed, don't seem to be used
219 : // anywhere.
220 :
221 8166 : ServerFont* pFont = (ServerFont*) pFontData;
222 8166 : FT_Face aFace = pFont->GetFtFace();
223 :
224 : SAL_INFO("vcl.harfbuzz", "getGlyphKerningH(" << pFont << ", " << nGlyphIndex1 << ", " << nGlyphIndex2 << ")");
225 :
226 : FT_Error error;
227 : FT_Vector kerning;
228 : hb_position_t ret;
229 :
230 8166 : error = FT_Get_Kerning(aFace, nGlyphIndex1, nGlyphIndex2, FT_KERNING_DEFAULT, &kerning);
231 8166 : if (error)
232 0 : ret = 0;
233 : else
234 8166 : ret = kerning.x;
235 :
236 8166 : return ret;
237 : }
238 :
239 0 : static hb_position_t getGlyphKerningV(hb_font_t* /*font*/, void* /*pFontData*/,
240 : hb_codepoint_t /*nGlyphIndex1*/, hb_codepoint_t /*nGlyphIndex2*/,
241 : void* /*pUserData*/)
242 : {
243 : // XXX vertical kerning
244 0 : return 0;
245 : }
246 :
247 0 : static hb_bool_t getGlyphExtents(hb_font_t* /*font*/, void* pFontData,
248 : hb_codepoint_t nGlyphIndex,
249 : hb_glyph_extents_t* pExtents,
250 : void* /*pUserData*/)
251 : {
252 0 : ServerFont* pFont = (ServerFont*) pFontData;
253 0 : FT_Face aFace = pFont->GetFtFace();
254 :
255 : SAL_INFO("vcl.harfbuzz", "getGlyphExtents(" << pFont << ", " << nGlyphIndex << ")");
256 :
257 : FT_Error error;
258 0 : error = FT_Load_Glyph(aFace, nGlyphIndex, FT_LOAD_DEFAULT);
259 0 : if (!error)
260 : {
261 0 : pExtents->x_bearing = aFace->glyph->metrics.horiBearingX;
262 0 : pExtents->y_bearing = aFace->glyph->metrics.horiBearingY;
263 0 : pExtents->width = aFace->glyph->metrics.width;
264 0 : pExtents->height = -aFace->glyph->metrics.height;
265 : }
266 :
267 0 : return !error;
268 : }
269 :
270 0 : static hb_bool_t getGlyphContourPoint(hb_font_t* /*font*/, void* pFontData,
271 : hb_codepoint_t nGlyphIndex, unsigned int nPointIndex,
272 : hb_position_t *x, hb_position_t *y,
273 : void* /*pUserData*/)
274 : {
275 0 : bool ret = false;
276 0 : ServerFont* pFont = (ServerFont*) pFontData;
277 0 : FT_Face aFace = pFont->GetFtFace();
278 :
279 : SAL_INFO("vcl.harfbuzz", "getGlyphContourPoint(" << pFont << ", " << nGlyphIndex << ", " << nPointIndex << ")");
280 :
281 : FT_Error error;
282 0 : error = FT_Load_Glyph(aFace, nGlyphIndex, FT_LOAD_DEFAULT);
283 0 : if (!error)
284 : {
285 0 : if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
286 : {
287 0 : if (nPointIndex < (unsigned int) aFace->glyph->outline.n_points)
288 : {
289 0 : *x = aFace->glyph->outline.points[nPointIndex].x;
290 0 : *y = aFace->glyph->outline.points[nPointIndex].y;
291 0 : ret = true;
292 : }
293 : }
294 : }
295 :
296 0 : return ret;
297 : }
298 :
299 728181 : static hb_font_funcs_t* getFontFuncs(void)
300 : {
301 728181 : static hb_font_funcs_t* funcs = hb_font_funcs_create();
302 :
303 728181 : hb_font_funcs_set_glyph_func (funcs, getFontGlyph, NULL, NULL);
304 728181 : hb_font_funcs_set_glyph_h_advance_func (funcs, getGlyphAdvanceH, NULL, NULL);
305 728181 : hb_font_funcs_set_glyph_v_advance_func (funcs, getGlyphAdvanceV, NULL, NULL);
306 728181 : hb_font_funcs_set_glyph_h_origin_func (funcs, getGlyphOriginH, NULL, NULL);
307 728181 : hb_font_funcs_set_glyph_v_origin_func (funcs, getGlyphOriginV, NULL, NULL);
308 728181 : hb_font_funcs_set_glyph_h_kerning_func (funcs, getGlyphKerningH, NULL, NULL);
309 728181 : hb_font_funcs_set_glyph_v_kerning_func (funcs, getGlyphKerningV, NULL, NULL);
310 728181 : hb_font_funcs_set_glyph_extents_func (funcs, getGlyphExtents, NULL, NULL);
311 728181 : hb_font_funcs_set_glyph_contour_point_func (funcs, getGlyphContourPoint, NULL, NULL);
312 :
313 728181 : return funcs;
314 : }
315 :
316 : class HbLayoutEngine : public ServerFontLayoutEngine
317 : {
318 : private:
319 : UScriptCode meScriptCode;
320 : hb_face_t* mpHbFace;
321 : int fUnitsPerEM;
322 :
323 : public:
324 : HbLayoutEngine(ServerFont&);
325 : virtual ~HbLayoutEngine();
326 :
327 : virtual bool layout(ServerFontLayout&, ImplLayoutArgs&);
328 : };
329 :
330 1716 : HbLayoutEngine::HbLayoutEngine(ServerFont& rServerFont)
331 : : meScriptCode(USCRIPT_INVALID_CODE),
332 : mpHbFace(NULL),
333 1716 : fUnitsPerEM(0)
334 : {
335 1716 : FT_Face aFtFace = rServerFont.GetFtFace();
336 1716 : fUnitsPerEM = rServerFont.GetEmUnits();
337 :
338 1716 : mpHbFace = hb_face_create_for_tables(getFontTable, &rServerFont, NULL);
339 1716 : hb_face_set_index(mpHbFace, aFtFace->face_index);
340 1716 : hb_face_set_upem(mpHbFace, fUnitsPerEM);
341 1716 : }
342 :
343 5148 : HbLayoutEngine::~HbLayoutEngine()
344 : {
345 1716 : hb_face_destroy(mpHbFace);
346 3432 : }
347 :
348 728181 : bool HbLayoutEngine::layout(ServerFontLayout& rLayout, ImplLayoutArgs& rArgs)
349 : {
350 728181 : ServerFont& rFont = rLayout.GetServerFont();
351 728181 : FT_Face aFtFace = rFont.GetFtFace();
352 :
353 : SAL_INFO("vcl.harfbuzz", "layout(" << this << ",rArgs=" << rArgs << ")");
354 :
355 728181 : hb_font_t *pHbFont = hb_font_create(mpHbFace);
356 728181 : hb_font_set_funcs(pHbFont, getFontFuncs(), &rFont, NULL);
357 : hb_font_set_scale(pHbFont,
358 728181 : ((uint64_t) aFtFace->size->metrics.x_scale * (uint64_t) fUnitsPerEM) >> 16,
359 1456362 : ((uint64_t) aFtFace->size->metrics.y_scale * (uint64_t) fUnitsPerEM) >> 16);
360 728181 : hb_font_set_ppem(pHbFont, aFtFace->size->metrics.x_ppem, aFtFace->size->metrics.y_ppem);
361 :
362 : // allocate temporary arrays, note: round to even
363 728181 : int nGlyphCapacity = (3 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos) | 15) + 1;
364 :
365 728181 : rLayout.Reserve(nGlyphCapacity);
366 :
367 728181 : Point aCurrPos(0, 0);
368 : while (true)
369 : {
370 : int nMinRunPos, nEndRunPos;
371 : bool bRightToLeft;
372 1456338 : if (!rArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRightToLeft))
373 728181 : break;
374 :
375 728157 : int nRunLen = nEndRunPos - nMinRunPos;
376 :
377 : // find matching script
378 : // TODO: use ICU's UScriptRun API to properly resolves "common" and
379 : // "inherited" script codes, probably use it in GetNextRun() and return
380 : // the script there
381 728157 : UScriptCode eScriptCode = USCRIPT_INVALID_CODE;
382 12789780 : for (int i = nMinRunPos; i < nEndRunPos; ++i)
383 : {
384 12062431 : UErrorCode rcI18n = U_ZERO_ERROR;
385 12062431 : UScriptCode eNextScriptCode = uscript_getScript(rArgs.mpStr[i], &rcI18n);
386 12062431 : if ((eNextScriptCode > USCRIPT_INHERITED))
387 : {
388 5837682 : eScriptCode = eNextScriptCode;
389 5837682 : if (eNextScriptCode != USCRIPT_LATIN)
390 808 : break;
391 : }
392 : }
393 728157 : if (eScriptCode < 0) // TODO: handle errors better
394 243609 : eScriptCode = USCRIPT_LATIN;
395 :
396 728157 : meScriptCode = eScriptCode;
397 :
398 728157 : LanguageTag aLangTag(rArgs.meLanguage);
399 1456314 : OString sLanguage = OUStringToOString(aLangTag.getLanguage(), RTL_TEXTENCODING_UTF8);
400 :
401 728157 : hb_buffer_t *pHbBuffer = hb_buffer_create();
402 728157 : hb_buffer_set_direction(pHbBuffer, bRightToLeft ? HB_DIRECTION_RTL: HB_DIRECTION_LTR);
403 728157 : hb_buffer_set_script(pHbBuffer, hb_icu_script_to_script(eScriptCode));
404 728157 : hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
405 728157 : hb_buffer_add_utf16(pHbBuffer, rArgs.mpStr, rArgs.mnLength, nMinRunPos, nRunLen);
406 728157 : hb_shape(pHbFont, pHbBuffer, NULL, 0);
407 :
408 728157 : int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
409 728157 : hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, NULL);
410 728157 : hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, NULL);
411 :
412 12786053 : for (int i = 0; i < nRunGlyphCount; ++i) {
413 12057896 : int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
414 12057896 : int32_t nCharPos = pHbGlyphInfos[i].cluster;
415 :
416 : // if needed request glyph fallback by updating LayoutArgs
417 12057896 : if (!nGlyphIndex)
418 : {
419 1800 : rLayout.setNeedFallback(rArgs, nCharPos, bRightToLeft);
420 1800 : if (SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags)
421 845 : continue;
422 : }
423 :
424 : // apply vertical flags and glyph substitution
425 : // XXX: Use HB_DIRECTION_TTB above and apply whatever flags magic
426 : // FixupGlyphIndex() is doing, minus the GSUB part.
427 12057051 : if (nCharPos >= 0)
428 : {
429 12057051 : sal_UCS4 aChar = rArgs.mpStr[nCharPos];
430 12057051 : nGlyphIndex = rFont.FixupGlyphIndex(nGlyphIndex, aChar);
431 : }
432 :
433 12057051 : bool bInCluster = false;
434 12057051 : if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
435 48 : bInCluster = true;
436 :
437 12057051 : long nGlyphFlags = 0;
438 12057051 : if (bRightToLeft)
439 168 : nGlyphFlags |= GlyphItem::IS_RTL_GLYPH;
440 :
441 12057051 : if (bInCluster)
442 48 : nGlyphFlags |= GlyphItem::IS_IN_CLUSTER;
443 :
444 12057051 : bool bDiacritic = false;
445 12057051 : if (hb_ot_layout_has_glyph_classes(mpHbFace))
446 : {
447 : // the font has GDEF table
448 12047137 : if (hb_ot_layout_get_glyph_class(mpHbFace, nGlyphIndex) == HB_OT_LAYOUT_GLYPH_CLASS_MARK)
449 8 : bDiacritic = true;
450 : }
451 : else
452 : {
453 : // the font lacks GDEF table
454 : // HACK: if the resolved glyph advance is zero assume it is a
455 : // combining mark. The whole IS_DIACRITIC concept is a hack to
456 : // fix the other hacks we use to second-guess glyph advances in
457 : // ApplyDXArray and the likes and it needs to die
458 9914 : if (pHbPositions[i].x_advance == 0)
459 0 : bDiacritic = true;
460 : }
461 :
462 12057051 : if (bDiacritic)
463 8 : nGlyphFlags |= GlyphItem::IS_DIACRITIC;
464 :
465 12057051 : int32_t nXOffset = pHbPositions[i].x_offset >> 6;
466 12057051 : int32_t nYOffset = pHbPositions[i].y_offset >> 6;
467 12057051 : int32_t nXAdvance = pHbPositions[i].x_advance >> 6;
468 12057051 : int32_t nYAdvance = pHbPositions[i].y_advance >> 6;
469 :
470 12057051 : Point aNewPos = Point(aCurrPos.X() + nXOffset, -(aCurrPos.Y() + nYOffset));
471 12057051 : const GlyphItem aGI(nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nXAdvance, nXOffset);
472 12057051 : rLayout.AppendGlyph(aGI);
473 :
474 12057051 : aCurrPos.X() += nXAdvance;
475 12057051 : aCurrPos.Y() += nYAdvance;
476 : }
477 :
478 728157 : hb_buffer_destroy(pHbBuffer);
479 728157 : }
480 :
481 728181 : hb_font_destroy(pHbFont);
482 :
483 : // sort glyphs in visual order
484 : // and then in logical order (e.g. diacritics after cluster start)
485 : // XXX: why?
486 728181 : rLayout.SortGlyphItems();
487 :
488 : // determine need for kashida justification
489 728181 : if((rArgs.mpDXArray || rArgs.mnLayoutWidth)
490 20947 : && ((meScriptCode == USCRIPT_ARABIC) || (meScriptCode == USCRIPT_SYRIAC)))
491 0 : rArgs.mnFlags |= SAL_LAYOUT_KASHIDA_JUSTIFICATON;
492 :
493 1456338 : return true;
494 : }
495 :
496 : // =======================================================================
497 :
498 728181 : ServerFontLayoutEngine* ServerFont::GetLayoutEngine()
499 : {
500 : // find best layout engine for font, platform, script and language
501 728181 : if (!mpLayoutEngine) {
502 1716 : mpLayoutEngine = new HbLayoutEngine(*this);
503 : }
504 728181 : return mpLayoutEngine;
505 465 : }
506 :
507 : // =======================================================================
508 :
509 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|