1 /*
2 * Copyright (c) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
32
33 // clang-format off
34 #include <hb.h>
35 #include <hb-ot.h>
36 // clang-format on
37
38 #include <memory>
39
40 #include "build/build_config.h"
41 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
42 #include "third_party/blink/renderer/platform/fonts/font_global_context.h"
43 #include "third_party/blink/renderer/platform/fonts/font_platform_data.h"
44 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h"
45 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h"
46 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
47 #include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
48 #include "third_party/blink/renderer/platform/fonts/skia/skia_text_metrics.h"
49 #include "third_party/blink/renderer/platform/fonts/unicode_range_set.h"
50 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
51 #include "third_party/blink/renderer/platform/resolution_units.h"
52 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
53 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
54 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
55 #include "third_party/harfbuzz-ng/utils/hb_scoped.h"
56 #include "third_party/skia/include/core/SkPaint.h"
57 #include "third_party/skia/include/core/SkPath.h"
58 #include "third_party/skia/include/core/SkPoint.h"
59 #include "third_party/skia/include/core/SkRect.h"
60 #include "third_party/skia/include/core/SkStream.h"
61 #include "third_party/skia/include/core/SkTypeface.h"
62
63 namespace blink {
64
65 namespace {
66
67 #if defined(OS_MACOSX)
DetermineTrakSbix(SkTypeface * typeface,bool * has_trak,bool * has_sbix)68 void DetermineTrakSbix(SkTypeface* typeface, bool* has_trak, bool* has_sbix) {
69 int num_tags = typeface->countTables();
70
71 SkFontTableTag tags[num_tags];
72
73 int returned_tags = typeface->getTableTags(tags);
74 DCHECK_EQ(num_tags, returned_tags);
75
76 for (auto& tag : tags) {
77 if (tag == SkSetFourByteTag('t', 'r', 'a', 'k'))
78 *has_trak = true;
79 if (tag == SkSetFourByteTag('s', 'b', 'i', 'x'))
80 *has_sbix = true;
81 }
82 }
83 #endif
84
85 } // namespace
86
87 static scoped_refptr<HbFontCacheEntry> CreateHbFontCacheEntry(
88 hb_face_t*,
89 SkTypeface* typefaces);
90
HarfBuzzFace(FontPlatformData * platform_data,uint64_t unique_id)91 HarfBuzzFace::HarfBuzzFace(FontPlatformData* platform_data, uint64_t unique_id)
92 : platform_data_(platform_data), unique_id_(unique_id) {
93 HarfBuzzFontCache::AddResult result =
94 FontGlobalContext::GetHarfBuzzFontCache()->insert(unique_id_, nullptr);
95 if (result.is_new_entry) {
96 HbScoped<hb_face_t> face(CreateFace());
97 result.stored_value->value =
98 CreateHbFontCacheEntry(face.get(), platform_data->Typeface());
99 }
100 result.stored_value->value->AddRef();
101 unscaled_font_ = result.stored_value->value->HbFont();
102 harfbuzz_font_data_ = result.stored_value->value->HbFontData();
103 }
104
~HarfBuzzFace()105 HarfBuzzFace::~HarfBuzzFace() {
106 HarfBuzzFontCache* harfbuzz_font_cache =
107 FontGlobalContext::GetHarfBuzzFontCache();
108 HarfBuzzFontCache::iterator result = harfbuzz_font_cache->find(unique_id_);
109 SECURITY_DCHECK(result != harfbuzz_font_cache->end());
110 DCHECK(!result.Get()->value->HasOneRef());
111 result.Get()->value->Release();
112 if (result.Get()->value->HasOneRef())
113 harfbuzz_font_cache->erase(unique_id_);
114 }
115
HarfBuzzGetGlyph(hb_font_t * hb_font,void * font_data,hb_codepoint_t unicode,hb_codepoint_t variation_selector,hb_codepoint_t * glyph,void * user_data)116 static hb_bool_t HarfBuzzGetGlyph(hb_font_t* hb_font,
117 void* font_data,
118 hb_codepoint_t unicode,
119 hb_codepoint_t variation_selector,
120 hb_codepoint_t* glyph,
121 void* user_data) {
122 HarfBuzzFontData* hb_font_data =
123 reinterpret_cast<HarfBuzzFontData*>(font_data);
124
125 CHECK(hb_font_data);
126 if (hb_font_data->range_set_ && !hb_font_data->range_set_->Contains(unicode))
127 return false;
128
129 return hb_font_get_glyph(hb_font_get_parent(hb_font), unicode,
130 variation_selector, glyph);
131 }
132
HarfBuzzGetNominalGlyph(hb_font_t * hb_font,void * font_data,hb_codepoint_t unicode,hb_codepoint_t * glyph,void * user_data)133 static hb_bool_t HarfBuzzGetNominalGlyph(hb_font_t* hb_font,
134 void* font_data,
135 hb_codepoint_t unicode,
136 hb_codepoint_t* glyph,
137 void* user_data) {
138 return HarfBuzzGetGlyph(hb_font, font_data, unicode, 0, glyph, user_data);
139 }
140
HarfBuzzGetGlyphHorizontalAdvance(hb_font_t * hb_font,void * font_data,hb_codepoint_t glyph,void * user_data)141 static hb_position_t HarfBuzzGetGlyphHorizontalAdvance(hb_font_t* hb_font,
142 void* font_data,
143 hb_codepoint_t glyph,
144 void* user_data) {
145 HarfBuzzFontData* hb_font_data =
146 reinterpret_cast<HarfBuzzFontData*>(font_data);
147 hb_position_t advance = 0;
148
149 SkFontGetGlyphWidthForHarfBuzz(hb_font_data->font_, glyph, &advance);
150 return advance;
151 }
152
HarfBuzzGetGlyphHorizontalAdvances(hb_font_t * font,void * font_data,unsigned count,const hb_codepoint_t * first_glyph,unsigned int glyph_stride,hb_position_t * first_advance,unsigned int advance_stride,void * user_data)153 static void HarfBuzzGetGlyphHorizontalAdvances(
154 hb_font_t* font,
155 void* font_data,
156 unsigned count,
157 const hb_codepoint_t* first_glyph,
158 unsigned int glyph_stride,
159 hb_position_t* first_advance,
160 unsigned int advance_stride,
161 void* user_data) {
162 HarfBuzzFontData* hb_font_data =
163 reinterpret_cast<HarfBuzzFontData*>(font_data);
164 SkFontGetGlyphWidthForHarfBuzz(hb_font_data->font_, count, first_glyph,
165 glyph_stride, first_advance, advance_stride);
166 }
167
HarfBuzzGetGlyphVerticalOrigin(hb_font_t * hb_font,void * font_data,hb_codepoint_t glyph,hb_position_t * x,hb_position_t * y,void * user_data)168 static hb_bool_t HarfBuzzGetGlyphVerticalOrigin(hb_font_t* hb_font,
169 void* font_data,
170 hb_codepoint_t glyph,
171 hb_position_t* x,
172 hb_position_t* y,
173 void* user_data) {
174 HarfBuzzFontData* hb_font_data =
175 reinterpret_cast<HarfBuzzFontData*>(font_data);
176 scoped_refptr<OpenTypeVerticalData> vertical_data =
177 hb_font_data->VerticalData();
178 if (!vertical_data)
179 return false;
180
181 float result[] = {0, 0};
182 Glyph the_glyph = glyph;
183 vertical_data->GetVerticalTranslationsForGlyphs(hb_font_data->font_,
184 &the_glyph, 1, result);
185 *x = SkiaScalarToHarfBuzzPosition(-result[0]);
186 *y = SkiaScalarToHarfBuzzPosition(-result[1]);
187 return true;
188 }
189
HarfBuzzGetGlyphVerticalAdvance(hb_font_t * hb_font,void * font_data,hb_codepoint_t glyph,void * user_data)190 static hb_position_t HarfBuzzGetGlyphVerticalAdvance(hb_font_t* hb_font,
191 void* font_data,
192 hb_codepoint_t glyph,
193 void* user_data) {
194 HarfBuzzFontData* hb_font_data =
195 reinterpret_cast<HarfBuzzFontData*>(font_data);
196 scoped_refptr<OpenTypeVerticalData> vertical_data =
197 hb_font_data->VerticalData();
198 if (!vertical_data) {
199 return SkiaScalarToHarfBuzzPosition(hb_font_data->height_fallback_);
200 }
201
202 Glyph the_glyph = glyph;
203 float advance_height = -vertical_data->AdvanceHeight(the_glyph);
204 return SkiaScalarToHarfBuzzPosition(SkFloatToScalar(advance_height));
205 }
206
HarfBuzzGetGlyphExtents(hb_font_t * hb_font,void * font_data,hb_codepoint_t glyph,hb_glyph_extents_t * extents,void * user_data)207 static hb_bool_t HarfBuzzGetGlyphExtents(hb_font_t* hb_font,
208 void* font_data,
209 hb_codepoint_t glyph,
210 hb_glyph_extents_t* extents,
211 void* user_data) {
212 HarfBuzzFontData* hb_font_data =
213 reinterpret_cast<HarfBuzzFontData*>(font_data);
214
215 SkFontGetGlyphExtentsForHarfBuzz(hb_font_data->font_, glyph, extents);
216 return true;
217 }
218
TableHasSpace(hb_face_t * face,hb_set_t * glyphs,hb_tag_t tag,hb_codepoint_t space)219 static inline bool TableHasSpace(hb_face_t* face,
220 hb_set_t* glyphs,
221 hb_tag_t tag,
222 hb_codepoint_t space) {
223 unsigned count = hb_ot_layout_table_get_lookup_count(face, tag);
224 for (unsigned i = 0; i < count; i++) {
225 hb_ot_layout_lookup_collect_glyphs(face, tag, i, glyphs, glyphs, glyphs,
226 nullptr);
227 if (hb_set_has(glyphs, space))
228 return true;
229 }
230 return false;
231 }
232
GetSpaceGlyph(hb_font_t * font,hb_codepoint_t & space)233 static bool GetSpaceGlyph(hb_font_t* font, hb_codepoint_t& space) {
234 return hb_font_get_nominal_glyph(font, kSpaceCharacter, &space);
235 }
236
HasSpaceInLigaturesOrKerning(TypesettingFeatures features)237 bool HarfBuzzFace::HasSpaceInLigaturesOrKerning(TypesettingFeatures features) {
238 const hb_codepoint_t kInvalidCodepoint = static_cast<hb_codepoint_t>(-1);
239 hb_codepoint_t space = kInvalidCodepoint;
240
241 HbScoped<hb_set_t> glyphs(hb_set_create());
242
243 // Check whether computing is needed and compute for gpos/gsub.
244 if (features & kKerning &&
245 harfbuzz_font_data_->space_in_gpos_ ==
246 HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Unknown) {
247 if (space == kInvalidCodepoint && !GetSpaceGlyph(unscaled_font_, space))
248 return false;
249 // Compute for gpos.
250 hb_face_t* face = hb_font_get_face(unscaled_font_);
251 DCHECK(face);
252 harfbuzz_font_data_->space_in_gpos_ =
253 hb_ot_layout_has_positioning(face) &&
254 TableHasSpace(face, glyphs.get(), HB_OT_TAG_GPOS, space)
255 ? HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present
256 : HarfBuzzFontData::SpaceGlyphInOpenTypeTables::NotPresent;
257 }
258
259 hb_set_clear(glyphs.get());
260
261 if (features & kLigatures &&
262 harfbuzz_font_data_->space_in_gsub_ ==
263 HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Unknown) {
264 if (space == kInvalidCodepoint && !GetSpaceGlyph(unscaled_font_, space))
265 return false;
266 // Compute for gpos.
267 hb_face_t* face = hb_font_get_face(unscaled_font_);
268 DCHECK(face);
269 harfbuzz_font_data_->space_in_gsub_ =
270 hb_ot_layout_has_substitution(face) &&
271 TableHasSpace(face, glyphs.get(), HB_OT_TAG_GSUB, space)
272 ? HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present
273 : HarfBuzzFontData::SpaceGlyphInOpenTypeTables::NotPresent;
274 }
275
276 return (features & kKerning &&
277 harfbuzz_font_data_->space_in_gpos_ ==
278 HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present) ||
279 (features & kLigatures &&
280 harfbuzz_font_data_->space_in_gsub_ ==
281 HarfBuzzFontData::SpaceGlyphInOpenTypeTables::Present);
282 }
283
UnitsPerEmFromHeadTable()284 unsigned HarfBuzzFace::UnitsPerEmFromHeadTable() {
285 hb_face_t* face = hb_font_get_face(unscaled_font_);
286 return hb_face_get_upem(face);
287 }
288
ShouldSubpixelPosition()289 bool HarfBuzzFace::ShouldSubpixelPosition() {
290 return harfbuzz_font_data_->font_.isSubpixel();
291 }
292
create_populated_hb_font_funcs(FontGlobalContext::HorizontalAdvanceSource horizontal_advance_source)293 static hb_font_funcs_t* create_populated_hb_font_funcs(
294 FontGlobalContext::HorizontalAdvanceSource horizontal_advance_source) {
295 hb_font_funcs_t* funcs = hb_font_funcs_create();
296
297 if (horizontal_advance_source == FontGlobalContext::kSkiaHorizontalAdvances) {
298 hb_font_funcs_set_glyph_h_advance_func(
299 funcs, HarfBuzzGetGlyphHorizontalAdvance, nullptr, nullptr);
300 hb_font_funcs_set_glyph_h_advances_func(
301 funcs, (hb_font_get_glyph_h_advances_func_t)HarfBuzzGetGlyphHorizontalAdvances, nullptr, nullptr);
302 }
303 hb_font_funcs_set_variation_glyph_func(funcs, HarfBuzzGetGlyph, nullptr,
304 nullptr);
305 hb_font_funcs_set_nominal_glyph_func(funcs, HarfBuzzGetNominalGlyph, nullptr,
306 nullptr);
307 // TODO(https://crbug.com/899718): Replace vertical metrics callbacks with
308 // HarfBuzz VORG/VMTX internal implementation by deregistering those.
309 hb_font_funcs_set_glyph_v_advance_func(funcs, HarfBuzzGetGlyphVerticalAdvance,
310 nullptr, nullptr);
311 hb_font_funcs_set_glyph_v_origin_func(funcs, HarfBuzzGetGlyphVerticalOrigin,
312 nullptr, nullptr);
313 hb_font_funcs_set_glyph_extents_func(funcs, HarfBuzzGetGlyphExtents, nullptr,
314 nullptr);
315
316 hb_font_funcs_make_immutable(funcs);
317 return funcs;
318 }
319
HarfBuzzSkiaGetFontFuncs(FontGlobalContext::HorizontalAdvanceSource advance_source)320 static hb_font_funcs_t* HarfBuzzSkiaGetFontFuncs(
321 FontGlobalContext::HorizontalAdvanceSource advance_source) {
322 hb_font_funcs_t* funcs =
323 FontGlobalContext::GetHarfBuzzFontFuncs(advance_source);
324
325 // We don't set callback functions which we can't support.
326 // HarfBuzz will use the fallback implementation if they aren't set.
327 if (!funcs) {
328 funcs = create_populated_hb_font_funcs(advance_source);
329 FontGlobalContext::SetHarfBuzzFontFuncs(advance_source, funcs);
330 }
331 DCHECK(funcs);
332 return funcs;
333 }
334
HarfBuzzSkiaGetTable(hb_face_t * face,hb_tag_t tag,void * user_data)335 static hb_blob_t* HarfBuzzSkiaGetTable(hb_face_t* face,
336 hb_tag_t tag,
337 void* user_data) {
338 SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data);
339
340 const wtf_size_t table_size =
341 SafeCast<wtf_size_t>(typeface->getTableSize(tag));
342 if (!table_size) {
343 return nullptr;
344 }
345
346 char* buffer = reinterpret_cast<char*>(WTF::Partitions::FastMalloc(
347 table_size, WTF_HEAP_PROFILER_TYPE_NAME(HarfBuzzFontData)));
348 if (!buffer)
349 return nullptr;
350 size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer);
351 if (table_size != actual_size) {
352 WTF::Partitions::FastFree(buffer);
353 return nullptr;
354 }
355 return hb_blob_create(const_cast<char*>(buffer), table_size,
356 HB_MEMORY_MODE_WRITABLE, buffer,
357 WTF::Partitions::FastFree);
358 }
359
360 #if !defined(OS_MACOSX)
DeleteTypefaceStream(void * stream_asset_ptr)361 static void DeleteTypefaceStream(void* stream_asset_ptr) {
362 SkStreamAsset* stream_asset =
363 reinterpret_cast<SkStreamAsset*>(stream_asset_ptr);
364 delete stream_asset;
365 }
366 #endif
367
CreateFace()368 hb_face_t* HarfBuzzFace::CreateFace() {
369 hb_face_t* face = nullptr;
370
371 DEFINE_THREAD_SAFE_STATIC_LOCAL(BooleanHistogram, zero_copy_success_histogram,
372 ("Blink.Fonts.HarfBuzzFaceZeroCopyAccess"));
373 SkTypeface* typeface = platform_data_->Typeface();
374 CHECK(typeface);
375 // The attempt of doing zero copy-mmaped memory access to the font blobs does
376 // not work efficiently on Mac, since what is returned from
377 // typeface->openStream is a synthesized font assembled from copying all font
378 // tables on Mac. See the implementation of SkTypeface_Mac::onOpenStream.
379 #if !defined(OS_MACOSX)
380 int ttc_index = 0;
381 std::unique_ptr<SkStreamAsset> tf_stream(typeface->openStream(&ttc_index));
382 if (tf_stream && tf_stream->getMemoryBase()) {
383 const void* tf_memory = tf_stream->getMemoryBase();
384 size_t tf_size = tf_stream->getLength();
385 HbScoped<hb_blob_t> face_blob(
386 hb_blob_create(reinterpret_cast<const char*>(tf_memory),
387 SafeCast<unsigned int>(tf_size), HB_MEMORY_MODE_READONLY,
388 tf_stream.release(), DeleteTypefaceStream));
389 face = hb_face_create(face_blob.get(), ttc_index);
390 }
391 #endif
392
393 // Fallback to table copies if there is no in-memory access.
394 if (!face) {
395 face = hb_face_create_for_tables(HarfBuzzSkiaGetTable,
396 platform_data_->Typeface(), nullptr);
397 zero_copy_success_histogram.Count(false);
398 } else {
399 zero_copy_success_histogram.Count(true);
400 }
401
402 DCHECK(face);
403 return face;
404 }
405
CreateHbFontCacheEntry(hb_face_t * face,SkTypeface * typeface)406 scoped_refptr<HbFontCacheEntry> CreateHbFontCacheEntry(hb_face_t* face,
407 SkTypeface* typeface) {
408 HbScoped<hb_font_t> ot_font(hb_font_create(face));
409 hb_ot_font_set_funcs(ot_font.get());
410
411 int axis_count = typeface->getVariationDesignPosition(nullptr, 0);
412 if (axis_count > 0) {
413 Vector<SkFontArguments::VariationPosition::Coordinate> axis_values;
414 axis_values.resize(axis_count);
415 if (typeface->getVariationDesignPosition(axis_values.data(),
416 axis_values.size()) > 0) {
417 hb_font_set_variations(
418 ot_font.get(), reinterpret_cast<hb_variation_t*>(axis_values.data()),
419 axis_values.size());
420 }
421 }
422
423 // Creating a sub font means that non-available functions
424 // are found from the parent.
425 hb_font_t* unscaled_font = hb_font_create_sub_font(ot_font.get());
426 scoped_refptr<HbFontCacheEntry> cache_entry =
427 HbFontCacheEntry::Create(unscaled_font);
428
429 FontGlobalContext::HorizontalAdvanceSource advance_source =
430 FontGlobalContext::kSkiaHorizontalAdvances;
431 #if defined(OS_MACOSX)
432 bool has_trak = false;
433 bool has_sbix = false;
434 DetermineTrakSbix(typeface, &has_trak, &has_sbix);
435 if (has_trak && !has_sbix)
436 advance_source = FontGlobalContext::kHarfBuzzHorizontalAdvances;
437 #endif
438 hb_font_set_funcs(unscaled_font, HarfBuzzSkiaGetFontFuncs(advance_source),
439 cache_entry->HbFontData(), nullptr);
440 return cache_entry;
441 }
442
443 static_assert(
444 std::is_same<decltype(SkFontArguments::VariationPosition::Coordinate::axis),
445 decltype(hb_variation_t::tag)>::value &&
446 std::is_same<
447 decltype(SkFontArguments::VariationPosition::Coordinate::value),
448 decltype(hb_variation_t::value)>::value &&
449 sizeof(SkFontArguments::VariationPosition::Coordinate) ==
450 sizeof(hb_variation_t),
451 "Skia and HarfBuzz Variation parameter types must match in structure and "
452 "size.");
453
GetScaledFont(scoped_refptr<UnicodeRangeSet> range_set,VerticalLayoutCallbacks vertical_layout) const454 hb_font_t* HarfBuzzFace::GetScaledFont(
455 scoped_refptr<UnicodeRangeSet> range_set,
456 VerticalLayoutCallbacks vertical_layout) const {
457 harfbuzz_font_data_->range_set_ = std::move(range_set);
458 harfbuzz_font_data_->UpdateFallbackMetricsAndScale(*platform_data_,
459 vertical_layout);
460
461 int scale = SkiaScalarToHarfBuzzPosition(platform_data_->size());
462 hb_font_set_scale(unscaled_font_, scale, scale);
463 // See contended discussion in https://github.com/harfbuzz/harfbuzz/pull/1484
464 // Setting ptem here is critical for HarfBuzz to know where to lookup spacing
465 // offset in the AAT trak table, the unit pt in ptem here means "CoreText"
466 // points. After discussion on the pull request and with Apple developers, the
467 // meaning of HarfBuzz' hb_font_set_ptem API was changed to expect the
468 // equivalent of CSS pixels here.
469 hb_font_set_ptem(unscaled_font_, platform_data_->size());
470
471 return unscaled_font_;
472 }
473
474 } // namespace blink
475