|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "gfxFT2FontBase.h" |
|
7 #include "gfxFT2Utils.h" |
|
8 #include "harfbuzz/hb.h" |
|
9 #include "mozilla/Likely.h" |
|
10 #include "gfxFontConstants.h" |
|
11 #include "gfxFontUtils.h" |
|
12 |
|
13 using namespace mozilla::gfx; |
|
14 |
|
15 gfxFT2FontBase::gfxFT2FontBase(cairo_scaled_font_t *aScaledFont, |
|
16 gfxFontEntry *aFontEntry, |
|
17 const gfxFontStyle *aFontStyle) |
|
18 : gfxFont(aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont), |
|
19 mSpaceGlyph(0), |
|
20 mHasMetrics(false) |
|
21 { |
|
22 cairo_scaled_font_reference(mScaledFont); |
|
23 ConstructFontOptions(); |
|
24 } |
|
25 |
|
26 gfxFT2FontBase::~gfxFT2FontBase() |
|
27 { |
|
28 cairo_scaled_font_destroy(mScaledFont); |
|
29 } |
|
30 |
|
31 uint32_t |
|
32 gfxFT2FontBase::GetGlyph(uint32_t aCharCode) |
|
33 { |
|
34 // FcFreeTypeCharIndex needs to lock the FT_Face and can end up searching |
|
35 // through all the postscript glyph names in the font. Therefore use a |
|
36 // lightweight cache, which is stored on the cairo_font_face_t. |
|
37 |
|
38 cairo_font_face_t *face = |
|
39 cairo_scaled_font_get_font_face(CairoScaledFont()); |
|
40 |
|
41 if (cairo_font_face_status(face) != CAIRO_STATUS_SUCCESS) |
|
42 return 0; |
|
43 |
|
44 // This cache algorithm and size is based on what is done in |
|
45 // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph. I |
|
46 // think the concept is that adjacent characters probably come mostly from |
|
47 // one Unicode block. This assumption is probably not so valid with |
|
48 // scripts with large character sets as used for East Asian languages. |
|
49 |
|
50 struct CmapCacheSlot { |
|
51 uint32_t mCharCode; |
|
52 uint32_t mGlyphIndex; |
|
53 }; |
|
54 const uint32_t kNumSlots = 256; |
|
55 static cairo_user_data_key_t sCmapCacheKey; |
|
56 |
|
57 CmapCacheSlot *slots = static_cast<CmapCacheSlot*> |
|
58 (cairo_font_face_get_user_data(face, &sCmapCacheKey)); |
|
59 |
|
60 if (!slots) { |
|
61 // cairo's caches can keep some cairo_font_faces alive past our last |
|
62 // destroy, so the destroy function (free) for the cache must be |
|
63 // callable from cairo without any assumptions about what other |
|
64 // modules have not been shutdown. |
|
65 slots = static_cast<CmapCacheSlot*> |
|
66 (calloc(kNumSlots, sizeof(CmapCacheSlot))); |
|
67 if (!slots) |
|
68 return 0; |
|
69 |
|
70 cairo_status_t status = |
|
71 cairo_font_face_set_user_data(face, &sCmapCacheKey, slots, free); |
|
72 if (status != CAIRO_STATUS_SUCCESS) { // OOM |
|
73 free(slots); |
|
74 return 0; |
|
75 } |
|
76 |
|
77 // Invalidate slot 0 by setting its char code to something that would |
|
78 // never end up in slot 0. All other slots are already invalid |
|
79 // because they have mCharCode = 0 and a glyph for char code 0 will |
|
80 // always be in the slot 0. |
|
81 slots[0].mCharCode = 1; |
|
82 } |
|
83 |
|
84 CmapCacheSlot *slot = &slots[aCharCode % kNumSlots]; |
|
85 if (slot->mCharCode != aCharCode) { |
|
86 slot->mCharCode = aCharCode; |
|
87 slot->mGlyphIndex = gfxFT2LockedFace(this).GetGlyph(aCharCode); |
|
88 } |
|
89 |
|
90 return slot->mGlyphIndex; |
|
91 } |
|
92 |
|
93 void |
|
94 gfxFT2FontBase::GetGlyphExtents(uint32_t aGlyph, cairo_text_extents_t* aExtents) |
|
95 { |
|
96 NS_PRECONDITION(aExtents != nullptr, "aExtents must not be NULL"); |
|
97 |
|
98 cairo_glyph_t glyphs[1]; |
|
99 glyphs[0].index = aGlyph; |
|
100 glyphs[0].x = 0.0; |
|
101 glyphs[0].y = 0.0; |
|
102 // cairo does some caching for us here but perhaps a small gain could be |
|
103 // made by caching more. It is usually only the advance that is needed, |
|
104 // so caching only the advance could allow many requests to be cached with |
|
105 // little memory use. Ideally this cache would be merged with |
|
106 // gfxGlyphExtents. |
|
107 cairo_scaled_font_glyph_extents(CairoScaledFont(), glyphs, 1, aExtents); |
|
108 } |
|
109 |
|
110 const gfxFont::Metrics& |
|
111 gfxFT2FontBase::GetMetrics() |
|
112 { |
|
113 if (mHasMetrics) |
|
114 return mMetrics; |
|
115 |
|
116 if (MOZ_UNLIKELY(GetStyle()->size <= 0.0)) { |
|
117 new(&mMetrics) gfxFont::Metrics(); // zero initialize |
|
118 mSpaceGlyph = 0; |
|
119 } else { |
|
120 gfxFT2LockedFace face(this); |
|
121 mFUnitsConvFactor = face.XScale(); |
|
122 face.GetMetrics(&mMetrics, &mSpaceGlyph); |
|
123 } |
|
124 |
|
125 SanitizeMetrics(&mMetrics, false); |
|
126 |
|
127 #if 0 |
|
128 // printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size); |
|
129 // printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font))); |
|
130 |
|
131 fprintf (stderr, "Font: %s\n", NS_ConvertUTF16toUTF8(GetName()).get()); |
|
132 fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); |
|
133 fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent); |
|
134 fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading); |
|
135 fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight); |
|
136 fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f suOff: %f suSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize, mMetrics.superscriptOffset, mMetrics.subscriptOffset); |
|
137 #endif |
|
138 |
|
139 mHasMetrics = true; |
|
140 return mMetrics; |
|
141 } |
|
142 |
|
143 // Get the glyphID of a space |
|
144 uint32_t |
|
145 gfxFT2FontBase::GetSpaceGlyph() |
|
146 { |
|
147 NS_ASSERTION(GetStyle()->size != 0, |
|
148 "forgot to short-circuit a text run with zero-sized font?"); |
|
149 GetMetrics(); |
|
150 return mSpaceGlyph; |
|
151 } |
|
152 |
|
153 uint32_t |
|
154 gfxFT2FontBase::GetGlyph(uint32_t unicode, uint32_t variation_selector) |
|
155 { |
|
156 if (variation_selector) { |
|
157 uint32_t id = |
|
158 gfxFT2LockedFace(this).GetUVSGlyph(unicode, variation_selector); |
|
159 if (id) |
|
160 return id; |
|
161 id = gfxFontUtils::GetUVSFallback(unicode, variation_selector); |
|
162 if (id) { |
|
163 unicode = id; |
|
164 } |
|
165 } |
|
166 |
|
167 return GetGlyph(unicode); |
|
168 } |
|
169 |
|
170 int32_t |
|
171 gfxFT2FontBase::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID) |
|
172 { |
|
173 cairo_text_extents_t extents; |
|
174 GetGlyphExtents(aGID, &extents); |
|
175 // convert to 16.16 fixed point |
|
176 return NS_lround(0x10000 * extents.x_advance); |
|
177 } |
|
178 |
|
179 bool |
|
180 gfxFT2FontBase::SetupCairoFont(gfxContext *aContext) |
|
181 { |
|
182 cairo_t *cr = aContext->GetCairo(); |
|
183 |
|
184 // The scaled font ctm is not relevant right here because |
|
185 // cairo_set_scaled_font does not record the scaled font itself, but |
|
186 // merely the font_face, font_matrix, font_options. The scaled_font used |
|
187 // for the target can be different from the scaled_font passed to |
|
188 // cairo_set_scaled_font. (Unfortunately we have measured only for an |
|
189 // identity ctm.) |
|
190 cairo_scaled_font_t *cairoFont = CairoScaledFont(); |
|
191 |
|
192 if (cairo_scaled_font_status(cairoFont) != CAIRO_STATUS_SUCCESS) { |
|
193 // Don't cairo_set_scaled_font as that would propagate the error to |
|
194 // the cairo_t, precluding any further drawing. |
|
195 return false; |
|
196 } |
|
197 // Thoughts on which font_options to set on the context: |
|
198 // |
|
199 // cairoFont has been created for screen rendering. |
|
200 // |
|
201 // When the context is being used for screen rendering, we should set |
|
202 // font_options such that the same scaled_font gets used (when the ctm is |
|
203 // the same). The use of explicit font_options recorded in |
|
204 // CreateScaledFont ensures that this will happen. |
|
205 // |
|
206 // XXXkt: For pdf and ps surfaces, I don't know whether it's better to |
|
207 // remove surface-specific options, or try to draw with the same |
|
208 // scaled_font that was used to measure. As the same font_face is being |
|
209 // used, its font_options will often override some values anyway (unless |
|
210 // perhaps we remove those from the FcPattern at face creation). |
|
211 // |
|
212 // I can't see any significant difference in printing, irrespective of |
|
213 // what is set here. It's too late to change things here as measuring has |
|
214 // already taken place. We should really be measuring with a different |
|
215 // font for pdf and ps surfaces (bug 403513). |
|
216 cairo_set_scaled_font(cr, cairoFont); |
|
217 return true; |
|
218 } |
|
219 |
|
220 void |
|
221 gfxFT2FontBase::ConstructFontOptions() |
|
222 { |
|
223 NS_LossyConvertUTF16toASCII name(this->GetName()); |
|
224 mFontOptions.mName = name.get(); |
|
225 |
|
226 const gfxFontStyle* style = this->GetStyle(); |
|
227 if (style->style == NS_FONT_STYLE_ITALIC) { |
|
228 if (style->weight == NS_FONT_WEIGHT_BOLD) { |
|
229 mFontOptions.mStyle = FontStyle::BOLD_ITALIC; |
|
230 } else { |
|
231 mFontOptions.mStyle = FontStyle::ITALIC; |
|
232 } |
|
233 } else { |
|
234 if (style->weight == NS_FONT_WEIGHT_BOLD) { |
|
235 mFontOptions.mStyle = FontStyle::BOLD; |
|
236 } else { |
|
237 mFontOptions.mStyle = FontStyle::NORMAL; |
|
238 } |
|
239 } |
|
240 } |