/src/mozilla-central/layout/mathml/nsMathMLChar.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsMathMLChar.h" |
8 | | |
9 | | #include "gfxContext.h" |
10 | | #include "gfxTextRun.h" |
11 | | #include "gfxUtils.h" |
12 | | #include "mozilla/gfx/2D.h" |
13 | | #include "mozilla/ComputedStyle.h" |
14 | | #include "mozilla/MathAlgorithms.h" |
15 | | #include "mozilla/Unused.h" |
16 | | |
17 | | #include "nsAutoPtr.h" |
18 | | #include "nsCOMPtr.h" |
19 | | #include "nsDeviceContext.h" |
20 | | #include "nsFontMetrics.h" |
21 | | #include "nsIFrame.h" |
22 | | #include "nsLayoutUtils.h" |
23 | | #include "nsPresContext.h" |
24 | | #include "nsUnicharUtils.h" |
25 | | |
26 | | #include "mozilla/Preferences.h" |
27 | | #include "nsIPersistentProperties2.h" |
28 | | #include "nsIObserverService.h" |
29 | | #include "nsIObserver.h" |
30 | | #include "nsNetUtil.h" |
31 | | #include "nsContentUtils.h" |
32 | | |
33 | | #include "mozilla/LookAndFeel.h" |
34 | | #include "nsCSSRendering.h" |
35 | | #include "mozilla/Sprintf.h" |
36 | | |
37 | | #include "nsDisplayList.h" |
38 | | |
39 | | #include "nsMathMLOperators.h" |
40 | | #include <algorithm> |
41 | | |
42 | | #include "gfxMathTable.h" |
43 | | #include "nsUnicodeScriptCodes.h" |
44 | | |
45 | | using namespace mozilla; |
46 | | using namespace mozilla::gfx; |
47 | | using namespace mozilla::image; |
48 | | |
49 | | //#define NOISY_SEARCH 1 |
50 | | |
51 | | // BUG 848725 Drawing failure with stretchy horizontal parenthesis when no fonts |
52 | | // are installed. "kMaxScaleFactor" is required to limit the scale for the |
53 | | // vertical and horizontal stretchy operators. |
54 | | static const float kMaxScaleFactor = 20.0; |
55 | | static const float kLargeOpFactor = float(M_SQRT2); |
56 | | static const float kIntegralFactor = 2.0; |
57 | | |
58 | | static void |
59 | | NormalizeDefaultFont(nsFont& aFont, float aFontSizeInflation) |
60 | 0 | { |
61 | 0 | if (aFont.fontlist.GetDefaultFontType() != eFamily_none) { |
62 | 0 | nsTArray<FontFamilyName> names; |
63 | 0 | names.AppendElements(aFont.fontlist.GetFontlist()->mNames); |
64 | 0 | names.AppendElement(FontFamilyName(aFont.fontlist.GetDefaultFontType())); |
65 | 0 |
|
66 | 0 | aFont.fontlist.SetFontlist(std::move(names)); |
67 | 0 | aFont.fontlist.SetDefaultFontType(eFamily_none); |
68 | 0 | } |
69 | 0 | aFont.size = NSToCoordRound(aFont.size * aFontSizeInflation); |
70 | 0 | } |
71 | | |
72 | | // ----------------------------------------------------------------------------- |
73 | | static const nsGlyphCode kNullGlyph = {{{0, 0}}, 0}; |
74 | | |
75 | | // ----------------------------------------------------------------------------- |
76 | | // nsGlyphTable is a class that provides an interface for accessing glyphs |
77 | | // of stretchy chars. It acts like a table that stores the variants of bigger |
78 | | // sizes (if any) and the partial glyphs needed to build extensible symbols. |
79 | | // |
80 | | // Bigger sizes (if any) of the char can then be retrieved with BigOf(...). |
81 | | // Partial glyphs can be retrieved with ElementAt(...). |
82 | | // |
83 | | // A table consists of "nsGlyphCode"s which are viewed either as Unicode |
84 | | // points (for nsPropertiesTable) or as direct glyph indices (for |
85 | | // nsOpenTypeTable) |
86 | | // ----------------------------------------------------------------------------- |
87 | | |
88 | | class nsGlyphTable { |
89 | | public: |
90 | 0 | virtual ~nsGlyphTable() {} |
91 | | |
92 | | virtual const FontFamilyName& |
93 | | FontNameFor(const nsGlyphCode& aGlyphCode) const = 0; |
94 | | |
95 | | // Getters for the parts |
96 | | virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget, |
97 | | int32_t aAppUnitsPerDevPixel, |
98 | | gfxFontGroup* aFontGroup, |
99 | | char16_t aChar, |
100 | | bool aVertical, |
101 | | uint32_t aPosition) = 0; |
102 | | virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget, |
103 | | int32_t aAppUnitsPerDevPixel, |
104 | | gfxFontGroup* aFontGroup, |
105 | | char16_t aChar, |
106 | | bool aVertical, |
107 | | uint32_t aSize) = 0; |
108 | | |
109 | | // True if this table contains parts to render this char |
110 | | virtual bool HasPartsOf(DrawTarget* aDrawTarget, |
111 | | int32_t aAppUnitsPerDevPixel, |
112 | | gfxFontGroup* aFontGroup, |
113 | | char16_t aChar, |
114 | | bool aVertical) = 0; |
115 | | |
116 | | virtual already_AddRefed<gfxTextRun> |
117 | | MakeTextRun(DrawTarget* aDrawTarget, |
118 | | int32_t aAppUnitsPerDevPixel, |
119 | | gfxFontGroup* aFontGroup, |
120 | | const nsGlyphCode& aGlyph) = 0; |
121 | | protected: |
122 | 0 | nsGlyphTable() : mCharCache(0) {} |
123 | | // For speedy re-use, we always cache the last data used in the table. |
124 | | // mCharCache is the Unicode point of the last char that was queried in this |
125 | | // table. |
126 | | char16_t mCharCache; |
127 | | }; |
128 | | |
129 | | // An instance of nsPropertiesTable is associated with one primary font. Extra |
130 | | // glyphs can be taken in other additional fonts when stretching certain |
131 | | // characters. |
132 | | // These supplementary fonts are referred to as "external" fonts to the table. |
133 | | |
134 | | // General format of MathFont Property Files from which glyph data are |
135 | | // retrieved: |
136 | | // ----------------------------------------------------------------------------- |
137 | | // Each font should have its set of glyph data. For example, the glyph data for |
138 | | // the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties" |
139 | | // and "mathfontMTExtra.properties", respectively. The mathfont property file |
140 | | // is a set of all the stretchy MathML characters that can be rendered with that |
141 | | // font using larger and/or partial glyphs. The entry of each stretchy character |
142 | | // in the mathfont property file gives, in that order, the 4 partial glyphs: |
143 | | // Top (or Left), Middle, Bottom (or Right), Glue; and the variants of bigger |
144 | | // sizes (if any). |
145 | | // A position that is not relevant to a particular character is indicated there |
146 | | // with the UNICODE REPLACEMENT CHARACTER 0xFFFD. |
147 | | // ----------------------------------------------------------------------------- |
148 | | |
149 | 0 | #define NS_TABLE_STATE_ERROR -1 |
150 | 0 | #define NS_TABLE_STATE_EMPTY 0 |
151 | 0 | #define NS_TABLE_STATE_READY 1 |
152 | | |
153 | | // helper to trim off comments from data in a MathFont Property File |
154 | | static void |
155 | | Clean(nsString& aValue) |
156 | 0 | { |
157 | 0 | // chop the trailing # comment portion if any ... |
158 | 0 | int32_t comment = aValue.RFindChar('#'); |
159 | 0 | if (comment > 0) aValue.Truncate(comment); |
160 | 0 | aValue.CompressWhitespace(); |
161 | 0 | } |
162 | | |
163 | | // helper to load a MathFont Property File |
164 | | static nsresult |
165 | | LoadProperties(const nsACString& aName, |
166 | | nsCOMPtr<nsIPersistentProperties>& aProperties) |
167 | 0 | { |
168 | 0 | nsAutoCString uriStr; |
169 | 0 | uriStr.AssignLiteral("resource://gre/res/fonts/mathfont"); |
170 | 0 | uriStr.Append(aName); |
171 | 0 | uriStr.StripWhitespace(); // that may come from aName |
172 | 0 | uriStr.AppendLiteral(".properties"); |
173 | 0 | return NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(aProperties), |
174 | 0 | uriStr); |
175 | 0 | } |
176 | | |
177 | | class nsPropertiesTable final : public nsGlyphTable { |
178 | | public: |
179 | | explicit nsPropertiesTable(const nsACString& aPrimaryFontName) |
180 | | : mState(NS_TABLE_STATE_EMPTY) |
181 | 0 | { |
182 | 0 | MOZ_COUNT_CTOR(nsPropertiesTable); |
183 | 0 | mGlyphCodeFonts.AppendElement(FontFamilyName(aPrimaryFontName, |
184 | 0 | eUnquotedName)); |
185 | 0 | } |
186 | | |
187 | | ~nsPropertiesTable() |
188 | 0 | { |
189 | 0 | MOZ_COUNT_DTOR(nsPropertiesTable); |
190 | 0 | } |
191 | | |
192 | | const FontFamilyName& PrimaryFontName() const |
193 | 0 | { |
194 | 0 | return mGlyphCodeFonts[0]; |
195 | 0 | } |
196 | | |
197 | | const FontFamilyName& |
198 | | FontNameFor(const nsGlyphCode& aGlyphCode) const override |
199 | 0 | { |
200 | 0 | NS_ASSERTION(!aGlyphCode.IsGlyphID(), |
201 | 0 | "nsPropertiesTable can only access glyphs by code point"); |
202 | 0 | return mGlyphCodeFonts[aGlyphCode.font]; |
203 | 0 | } |
204 | | |
205 | | virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget, |
206 | | int32_t aAppUnitsPerDevPixel, |
207 | | gfxFontGroup* aFontGroup, |
208 | | char16_t aChar, |
209 | | bool aVertical, |
210 | | uint32_t aPosition) override; |
211 | | |
212 | | virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget, |
213 | | int32_t aAppUnitsPerDevPixel, |
214 | | gfxFontGroup* aFontGroup, |
215 | | char16_t aChar, |
216 | | bool aVertical, |
217 | | uint32_t aSize) override |
218 | 0 | { |
219 | 0 | return ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, |
220 | 0 | aChar, aVertical, 4 + aSize); |
221 | 0 | } |
222 | | |
223 | | virtual bool HasPartsOf(DrawTarget* aDrawTarget, |
224 | | int32_t aAppUnitsPerDevPixel, |
225 | | gfxFontGroup* aFontGroup, |
226 | | char16_t aChar, |
227 | | bool aVertical) override |
228 | 0 | { |
229 | 0 | return (ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, |
230 | 0 | aChar, aVertical, 0).Exists() || |
231 | 0 | ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, |
232 | 0 | aChar, aVertical, 1).Exists() || |
233 | 0 | ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, |
234 | 0 | aChar, aVertical, 2).Exists() || |
235 | 0 | ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, |
236 | 0 | aChar, aVertical, 3).Exists()); |
237 | 0 | } |
238 | | |
239 | | virtual already_AddRefed<gfxTextRun> |
240 | | MakeTextRun(DrawTarget* aDrawTarget, |
241 | | int32_t aAppUnitsPerDevPixel, |
242 | | gfxFontGroup* aFontGroup, |
243 | | const nsGlyphCode& aGlyph) override; |
244 | | private: |
245 | | |
246 | | // mGlyphCodeFonts[0] is the primary font associated to this table. The |
247 | | // others are possible "external" fonts for glyphs not in the primary font |
248 | | // but which are needed to stretch certain characters in the table |
249 | | nsTArray<FontFamilyName> mGlyphCodeFonts; |
250 | | |
251 | | // Tri-state variable for error/empty/ready |
252 | | int32_t mState; |
253 | | |
254 | | // The set of glyph data in this table, as provided by the MathFont Property |
255 | | // File |
256 | | nsCOMPtr<nsIPersistentProperties> mGlyphProperties; |
257 | | |
258 | | // mGlyphCache is a buffer containing the glyph data associated with |
259 | | // mCharCache. |
260 | | // For a property line 'key = value' in the MathFont Property File, |
261 | | // mCharCache will retain the 'key' -- which is a Unicode point, while |
262 | | // mGlyphCache will retain the 'value', which is a consecutive list of |
263 | | // nsGlyphCodes, i.e., the pairs of 'code@font' needed by the char -- in |
264 | | // which 'code@0' can be specified |
265 | | // without the optional '@0'. However, to ease subsequent processing, |
266 | | // mGlyphCache excludes the '@' symbol and explicitly inserts all optional '0' |
267 | | // that indicates the primary font identifier. Specifically therefore, the |
268 | | // k-th glyph is characterized by : |
269 | | // 1) mGlyphCache[3*k],mGlyphCache[3*k+1] : its Unicode point |
270 | | // 2) mGlyphCache[3*k+2] : the numeric identifier of the font where it comes |
271 | | // from. |
272 | | // A font identifier of '0' means the default primary font associated to this |
273 | | // table. Other digits map to the "external" fonts that may have been |
274 | | // specified in the MathFont Property File. |
275 | | nsString mGlyphCache; |
276 | | }; |
277 | | |
278 | | /* virtual */ |
279 | | nsGlyphCode |
280 | | nsPropertiesTable::ElementAt(DrawTarget* /* aDrawTarget */, |
281 | | int32_t /* aAppUnitsPerDevPixel */, |
282 | | gfxFontGroup* /* aFontGroup */, |
283 | | char16_t aChar, |
284 | | bool /* aVertical */, |
285 | | uint32_t aPosition) |
286 | 0 | { |
287 | 0 | if (mState == NS_TABLE_STATE_ERROR) return kNullGlyph; |
288 | 0 | // Load glyph properties if this is the first time we have been here |
289 | 0 | if (mState == NS_TABLE_STATE_EMPTY) { |
290 | 0 | nsAutoCString primaryFontName; |
291 | 0 | mGlyphCodeFonts[0].AppendToString(primaryFontName); |
292 | 0 | nsresult rv = LoadProperties(primaryFontName, mGlyphProperties); |
293 | | #ifdef DEBUG |
294 | | nsAutoCString uriStr; |
295 | | uriStr.AssignLiteral("resource://gre/res/fonts/mathfont"); |
296 | | uriStr.Append(primaryFontName); |
297 | | uriStr.StripWhitespace(); // that may come from mGlyphCodeFonts |
298 | | uriStr.AppendLiteral(".properties"); |
299 | | printf("Loading %s ... %s\n", |
300 | | uriStr.get(), |
301 | | (NS_FAILED(rv)) ? "Failed" : "Done"); |
302 | | #endif |
303 | 0 | if (NS_FAILED(rv)) { |
304 | 0 | mState = NS_TABLE_STATE_ERROR; // never waste time with this table again |
305 | 0 | return kNullGlyph; |
306 | 0 | } |
307 | 0 | mState = NS_TABLE_STATE_READY; |
308 | 0 |
|
309 | 0 | // see if there are external fonts needed for certain chars in this table |
310 | 0 | nsAutoCString key; |
311 | 0 | nsAutoString value; |
312 | 0 | for (int32_t i = 1; ; i++) { |
313 | 0 | key.AssignLiteral("external."); |
314 | 0 | key.AppendInt(i, 10); |
315 | 0 | rv = mGlyphProperties->GetStringProperty(key, value); |
316 | 0 | if (NS_FAILED(rv)) break; |
317 | 0 | Clean(value); |
318 | 0 | mGlyphCodeFonts.AppendElement(FontFamilyName(NS_ConvertUTF16toUTF8(value), |
319 | 0 | eUnquotedName)); // i.e., mGlyphCodeFonts[i] holds this font name |
320 | 0 | } |
321 | 0 | } |
322 | 0 |
|
323 | 0 | // Update our cache if it is not associated to this character |
324 | 0 | if (mCharCache != aChar) { |
325 | 0 | // The key in the property file is interpreted as ASCII and kept |
326 | 0 | // as such ... |
327 | 0 | char key[10]; SprintfLiteral(key, "\\u%04X", aChar); |
328 | 0 | nsAutoString value; |
329 | 0 | nsresult rv = mGlyphProperties->GetStringProperty(nsDependentCString(key), |
330 | 0 | value); |
331 | 0 | if (NS_FAILED(rv)) return kNullGlyph; |
332 | 0 | Clean(value); |
333 | 0 | // See if this char uses external fonts; e.g., if the 2nd glyph is taken |
334 | 0 | // from the external font '1', the property line looks like |
335 | 0 | // \uNNNN = \uNNNN\uNNNN@1\uNNNN. |
336 | 0 | // This is where mGlyphCache is pre-processed to explicitly store all glyph |
337 | 0 | // codes as combined pairs of 'code@font', excluding the '@' separator. This |
338 | 0 | // means that mGlyphCache[3*k],mGlyphCache[3*k+1] will later be rendered |
339 | 0 | // with mGlyphCodeFonts[mGlyphCache[3*k+2]] |
340 | 0 | // Note: font identifier is internally an ASCII digit to avoid the null |
341 | 0 | // char issue |
342 | 0 | nsAutoString buffer; |
343 | 0 | int32_t length = value.Length(); |
344 | 0 | int32_t i = 0; // index in value |
345 | 0 | while (i < length) { |
346 | 0 | char16_t code = value[i]; |
347 | 0 | ++i; |
348 | 0 | buffer.Append(code); |
349 | 0 | // Read the next word if we have a non-BMP character. |
350 | 0 | if (i < length && NS_IS_HIGH_SURROGATE(code)) { |
351 | 0 | code = value[i]; |
352 | 0 | ++i; |
353 | 0 | } else { |
354 | 0 | code = char16_t('\0'); |
355 | 0 | } |
356 | 0 | buffer.Append(code); |
357 | 0 |
|
358 | 0 | // See if an external font is needed for the code point. |
359 | 0 | // Limit of 9 external fonts |
360 | 0 | char16_t font = 0; |
361 | 0 | if (i+1 < length && value[i] == char16_t('@') && |
362 | 0 | value[i+1] >= char16_t('0') && value[i+1] <= char16_t('9')) { |
363 | 0 | ++i; |
364 | 0 | font = value[i] - '0'; |
365 | 0 | ++i; |
366 | 0 | if (font >= mGlyphCodeFonts.Length()) { |
367 | 0 | NS_ERROR("Nonexistent font referenced in glyph table"); |
368 | 0 | return kNullGlyph; |
369 | 0 | } |
370 | 0 | // The char cannot be handled if this font is not installed |
371 | 0 | if (!mGlyphCodeFonts[font].mName) { |
372 | 0 | return kNullGlyph; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | buffer.Append(font); |
376 | 0 | } |
377 | 0 | // update our cache with the new settings |
378 | 0 | mGlyphCache.Assign(buffer); |
379 | 0 | mCharCache = aChar; |
380 | 0 | } |
381 | 0 |
|
382 | 0 | // 3* is to account for the code@font pairs |
383 | 0 | uint32_t index = 3*aPosition; |
384 | 0 | if (index+2 >= mGlyphCache.Length()) return kNullGlyph; |
385 | 0 | nsGlyphCode ch; |
386 | 0 | ch.code[0] = mGlyphCache.CharAt(index); |
387 | 0 | ch.code[1] = mGlyphCache.CharAt(index + 1); |
388 | 0 | ch.font = mGlyphCache.CharAt(index + 2); |
389 | 0 | return ch.code[0] == char16_t(0xFFFD) ? kNullGlyph : ch; |
390 | 0 | } |
391 | | |
392 | | /* virtual */ |
393 | | already_AddRefed<gfxTextRun> |
394 | | nsPropertiesTable::MakeTextRun(DrawTarget* aDrawTarget, |
395 | | int32_t aAppUnitsPerDevPixel, |
396 | | gfxFontGroup* aFontGroup, |
397 | | const nsGlyphCode& aGlyph) |
398 | 0 | { |
399 | 0 | NS_ASSERTION(!aGlyph.IsGlyphID(), |
400 | 0 | "nsPropertiesTable can only access glyphs by code point"); |
401 | 0 | return aFontGroup-> |
402 | 0 | MakeTextRun(aGlyph.code, aGlyph.Length(), aDrawTarget, aAppUnitsPerDevPixel, |
403 | 0 | gfx::ShapedTextFlags(), nsTextFrameUtils::Flags(), nullptr); |
404 | 0 | } |
405 | | |
406 | | // An instance of nsOpenTypeTable is associated with one gfxFontEntry that |
407 | | // corresponds to an Open Type font with a MATH table. All the glyphs come from |
408 | | // the same font and the calls to access size variants and parts are directly |
409 | | // forwarded to the gfx code. |
410 | | class nsOpenTypeTable final : public nsGlyphTable { |
411 | | public: |
412 | | ~nsOpenTypeTable() |
413 | 0 | { |
414 | 0 | MOZ_COUNT_DTOR(nsOpenTypeTable); |
415 | 0 | } |
416 | | |
417 | | virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget, |
418 | | int32_t aAppUnitsPerDevPixel, |
419 | | gfxFontGroup* aFontGroup, |
420 | | char16_t aChar, |
421 | | bool aVertical, |
422 | | uint32_t aPosition) override; |
423 | | virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget, |
424 | | int32_t aAppUnitsPerDevPixel, |
425 | | gfxFontGroup* aFontGroup, |
426 | | char16_t aChar, |
427 | | bool aVertical, |
428 | | uint32_t aSize) override; |
429 | | virtual bool HasPartsOf(DrawTarget* aDrawTarget, |
430 | | int32_t aAppUnitsPerDevPixel, |
431 | | gfxFontGroup* aFontGroup, |
432 | | char16_t aChar, |
433 | | bool aVertical) override; |
434 | | |
435 | | const FontFamilyName& |
436 | 0 | FontNameFor(const nsGlyphCode& aGlyphCode) const override { |
437 | 0 | NS_ASSERTION(aGlyphCode.IsGlyphID(), |
438 | 0 | "nsOpenTypeTable can only access glyphs by id"); |
439 | 0 | return mFontFamilyName; |
440 | 0 | } |
441 | | |
442 | | virtual already_AddRefed<gfxTextRun> |
443 | | MakeTextRun(DrawTarget* aDrawTarget, |
444 | | int32_t aAppUnitsPerDevPixel, |
445 | | gfxFontGroup* aFontGroup, |
446 | | const nsGlyphCode& aGlyph) override; |
447 | | |
448 | | // This returns a new OpenTypeTable instance to give access to OpenType MATH |
449 | | // table or nullptr if the font does not have such table. Ownership is passed |
450 | | // to the caller. |
451 | | static nsOpenTypeTable* Create(gfxFont* aFont) |
452 | 0 | { |
453 | 0 | if (!aFont->TryGetMathTable()) { |
454 | 0 | return nullptr; |
455 | 0 | } |
456 | 0 | return new nsOpenTypeTable(aFont); |
457 | 0 | } |
458 | | |
459 | | private: |
460 | | RefPtr<gfxFont> mFont; |
461 | | FontFamilyName mFontFamilyName; |
462 | | uint32_t mGlyphID; |
463 | | |
464 | | explicit nsOpenTypeTable(gfxFont* aFont) |
465 | | : mFont(aFont) |
466 | | , mFontFamilyName(aFont->GetFontEntry()->FamilyName(), eUnquotedName) |
467 | | , mGlyphID(0) |
468 | 0 | { |
469 | 0 | MOZ_COUNT_CTOR(nsOpenTypeTable); |
470 | 0 | } |
471 | | |
472 | | void UpdateCache(DrawTarget* aDrawTarget, |
473 | | int32_t aAppUnitsPerDevPixel, |
474 | | gfxFontGroup* aFontGroup, |
475 | | char16_t aChar); |
476 | | }; |
477 | | |
478 | | void |
479 | | nsOpenTypeTable::UpdateCache(DrawTarget* aDrawTarget, |
480 | | int32_t aAppUnitsPerDevPixel, |
481 | | gfxFontGroup* aFontGroup, |
482 | | char16_t aChar) |
483 | 0 | { |
484 | 0 | if (mCharCache != aChar) { |
485 | 0 | RefPtr<gfxTextRun> textRun = aFontGroup-> |
486 | 0 | MakeTextRun(&aChar, 1, aDrawTarget, aAppUnitsPerDevPixel, |
487 | 0 | gfx::ShapedTextFlags(), nsTextFrameUtils::Flags(), |
488 | 0 | nullptr); |
489 | 0 | const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0]; |
490 | 0 | if (data.IsSimpleGlyph()) { |
491 | 0 | mGlyphID = data.GetSimpleGlyph(); |
492 | 0 | } else if (data.GetGlyphCount() == 1) { |
493 | 0 | mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID; |
494 | 0 | } else { |
495 | 0 | mGlyphID = 0; |
496 | 0 | } |
497 | 0 | mCharCache = aChar; |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | | /* virtual */ |
502 | | nsGlyphCode |
503 | | nsOpenTypeTable::ElementAt(DrawTarget* aDrawTarget, |
504 | | int32_t aAppUnitsPerDevPixel, |
505 | | gfxFontGroup* aFontGroup, |
506 | | char16_t aChar, |
507 | | bool aVertical, |
508 | | uint32_t aPosition) |
509 | 0 | { |
510 | 0 | UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar); |
511 | 0 |
|
512 | 0 | uint32_t parts[4]; |
513 | 0 | if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, parts)) { |
514 | 0 | return kNullGlyph; |
515 | 0 | } |
516 | 0 | |
517 | 0 | uint32_t glyphID = parts[aPosition]; |
518 | 0 | if (!glyphID) { |
519 | 0 | return kNullGlyph; |
520 | 0 | } |
521 | 0 | nsGlyphCode glyph; |
522 | 0 | glyph.glyphID = glyphID; |
523 | 0 | glyph.font = -1; |
524 | 0 | return glyph; |
525 | 0 | } |
526 | | |
527 | | /* virtual */ |
528 | | nsGlyphCode |
529 | | nsOpenTypeTable::BigOf(DrawTarget* aDrawTarget, |
530 | | int32_t aAppUnitsPerDevPixel, |
531 | | gfxFontGroup* aFontGroup, |
532 | | char16_t aChar, |
533 | | bool aVertical, |
534 | | uint32_t aSize) |
535 | 0 | { |
536 | 0 | UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar); |
537 | 0 |
|
538 | 0 | uint32_t glyphID = |
539 | 0 | mFont->MathTable()->VariantsSize(mGlyphID, aVertical, aSize); |
540 | 0 | if (!glyphID) { |
541 | 0 | return kNullGlyph; |
542 | 0 | } |
543 | 0 | |
544 | 0 | nsGlyphCode glyph; |
545 | 0 | glyph.glyphID = glyphID; |
546 | 0 | glyph.font = -1; |
547 | 0 | return glyph; |
548 | 0 | } |
549 | | |
550 | | /* virtual */ |
551 | | bool |
552 | | nsOpenTypeTable::HasPartsOf(DrawTarget* aDrawTarget, |
553 | | int32_t aAppUnitsPerDevPixel, |
554 | | gfxFontGroup* aFontGroup, |
555 | | char16_t aChar, |
556 | | bool aVertical) |
557 | 0 | { |
558 | 0 | UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar); |
559 | 0 |
|
560 | 0 | uint32_t parts[4]; |
561 | 0 | if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, parts)) { |
562 | 0 | return false; |
563 | 0 | } |
564 | 0 | |
565 | 0 | return parts[0] || parts[1] || parts[2] || parts[3]; |
566 | 0 | } |
567 | | |
568 | | /* virtual */ |
569 | | already_AddRefed<gfxTextRun> |
570 | | nsOpenTypeTable::MakeTextRun(DrawTarget* aDrawTarget, |
571 | | int32_t aAppUnitsPerDevPixel, |
572 | | gfxFontGroup* aFontGroup, |
573 | | const nsGlyphCode& aGlyph) |
574 | 0 | { |
575 | 0 | NS_ASSERTION(aGlyph.IsGlyphID(), |
576 | 0 | "nsOpenTypeTable can only access glyphs by id"); |
577 | 0 |
|
578 | 0 | gfxTextRunFactory::Parameters params = { |
579 | 0 | aDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel |
580 | 0 | }; |
581 | 0 | RefPtr<gfxTextRun> textRun = |
582 | 0 | gfxTextRun::Create(¶ms, 1, aFontGroup, |
583 | 0 | gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()); |
584 | 0 | textRun->AddGlyphRun(aFontGroup->GetFirstValidFont(), |
585 | 0 | gfxTextRange::MatchType::kFontGroup, 0, |
586 | 0 | false, gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL); |
587 | 0 | // We don't care about CSS writing mode here; |
588 | 0 | // math runs are assumed to be horizontal. |
589 | 0 | gfxTextRun::DetailedGlyph detailedGlyph; |
590 | 0 | detailedGlyph.mGlyphID = aGlyph.glyphID; |
591 | 0 | detailedGlyph.mAdvance = |
592 | 0 | NSToCoordRound(aAppUnitsPerDevPixel * |
593 | 0 | aFontGroup->GetFirstValidFont()-> |
594 | 0 | GetGlyphHAdvance(aDrawTarget, aGlyph.glyphID)); |
595 | 0 | textRun->SetGlyphs(0, |
596 | 0 | gfxShapedText::CompressedGlyph::MakeComplex(true, true, 1), |
597 | 0 | &detailedGlyph); |
598 | 0 |
|
599 | 0 | return textRun.forget(); |
600 | 0 | } |
601 | | |
602 | | // ----------------------------------------------------------------------------- |
603 | | // This is the list of all the applicable glyph tables. |
604 | | // We will maintain a single global instance that will only reveal those |
605 | | // glyph tables that are associated to fonts currently installed on the |
606 | | // user' system. The class is an XPCOM shutdown observer to allow us to |
607 | | // free its allocated data at shutdown |
608 | | |
609 | | class nsGlyphTableList final : public nsIObserver |
610 | | { |
611 | | public: |
612 | | NS_DECL_ISUPPORTS |
613 | | NS_DECL_NSIOBSERVER |
614 | | |
615 | | nsPropertiesTable mUnicodeTable; |
616 | | |
617 | | nsGlyphTableList() |
618 | | : mUnicodeTable(NS_LITERAL_CSTRING("Unicode")) |
619 | 0 | { |
620 | 0 | } |
621 | | |
622 | | nsresult Initialize(); |
623 | | nsresult Finalize(); |
624 | | |
625 | | // Add a glyph table in the list, return the new table that was added |
626 | | nsGlyphTable* |
627 | | AddGlyphTable(const nsACString& aPrimaryFontName); |
628 | | |
629 | | // Find the glyph table in the list corresponding to the given font family. |
630 | | nsGlyphTable* |
631 | | GetGlyphTableFor(const nsACString& aFamily); |
632 | | |
633 | | private: |
634 | | ~nsGlyphTableList() |
635 | 0 | { |
636 | 0 | } |
637 | | |
638 | 0 | nsPropertiesTable* PropertiesTableAt(int32_t aIndex) { |
639 | 0 | return &mPropertiesTableList.ElementAt(aIndex); |
640 | 0 | } |
641 | 0 | int32_t PropertiesTableCount() { |
642 | 0 | return mPropertiesTableList.Length(); |
643 | 0 | } |
644 | | // List of glyph tables; |
645 | | nsTArray<nsPropertiesTable> mPropertiesTableList; |
646 | | }; |
647 | | |
648 | | NS_IMPL_ISUPPORTS(nsGlyphTableList, nsIObserver) |
649 | | |
650 | | // ----------------------------------------------------------------------------- |
651 | | // Here is the global list of applicable glyph tables that we will be using |
652 | | static nsGlyphTableList* gGlyphTableList = nullptr; |
653 | | |
654 | | static bool gGlyphTableInitialized = false; |
655 | | |
656 | | // XPCOM shutdown observer |
657 | | NS_IMETHODIMP |
658 | | nsGlyphTableList::Observe(nsISupports* aSubject, |
659 | | const char* aTopic, |
660 | | const char16_t* someData) |
661 | 0 | { |
662 | 0 | Finalize(); |
663 | 0 | return NS_OK; |
664 | 0 | } |
665 | | |
666 | | // Add an observer to XPCOM shutdown so that we can free our data at shutdown |
667 | | nsresult |
668 | | nsGlyphTableList::Initialize() |
669 | 0 | { |
670 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
671 | 0 | if (!obs) |
672 | 0 | return NS_ERROR_FAILURE; |
673 | 0 | |
674 | 0 | nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
675 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
676 | 0 |
|
677 | 0 | return NS_OK; |
678 | 0 | } |
679 | | |
680 | | // Remove our observer and free the memory that were allocated for us |
681 | | nsresult |
682 | | nsGlyphTableList::Finalize() |
683 | 0 | { |
684 | 0 | // Remove our observer from the observer service |
685 | 0 | nsresult rv = NS_OK; |
686 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
687 | 0 | if (obs) |
688 | 0 | rv = obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
689 | 0 | else |
690 | 0 | rv = NS_ERROR_FAILURE; |
691 | 0 |
|
692 | 0 | gGlyphTableInitialized = false; |
693 | 0 | // our oneself will be destroyed when our |Release| is called by the observer |
694 | 0 | NS_IF_RELEASE(gGlyphTableList); |
695 | 0 | return rv; |
696 | 0 | } |
697 | | |
698 | | nsGlyphTable* |
699 | | nsGlyphTableList::AddGlyphTable(const nsACString& aPrimaryFontName) |
700 | 0 | { |
701 | 0 | // See if there is already a special table for this family. |
702 | 0 | nsGlyphTable* glyphTable = GetGlyphTableFor(aPrimaryFontName); |
703 | 0 | if (glyphTable != &mUnicodeTable) |
704 | 0 | return glyphTable; |
705 | 0 | |
706 | 0 | // allocate a table |
707 | 0 | glyphTable = mPropertiesTableList.AppendElement(aPrimaryFontName); |
708 | 0 | return glyphTable; |
709 | 0 | } |
710 | | |
711 | | nsGlyphTable* |
712 | | nsGlyphTableList::GetGlyphTableFor(const nsACString& aFamily) |
713 | 0 | { |
714 | 0 | for (int32_t i = 0; i < PropertiesTableCount(); i++) { |
715 | 0 | nsPropertiesTable* glyphTable = PropertiesTableAt(i); |
716 | 0 | const FontFamilyName& primaryFontName = glyphTable->PrimaryFontName(); |
717 | 0 | nsAutoCString primaryFontNameStr; |
718 | 0 | primaryFontName.AppendToString(primaryFontNameStr); |
719 | 0 | // TODO: would be nice to consider StripWhitespace and other aliasing |
720 | 0 | if (primaryFontNameStr.Equals(aFamily, nsCaseInsensitiveCStringComparator())) { |
721 | 0 | return glyphTable; |
722 | 0 | } |
723 | 0 | } |
724 | 0 | // Fall back to default Unicode table |
725 | 0 | return &mUnicodeTable; |
726 | 0 | } |
727 | | |
728 | | // ----------------------------------------------------------------------------- |
729 | | |
730 | | static nsresult |
731 | | InitCharGlobals() |
732 | 0 | { |
733 | 0 | NS_ASSERTION(!gGlyphTableInitialized, "Error -- already initialized"); |
734 | 0 | gGlyphTableInitialized = true; |
735 | 0 |
|
736 | 0 | // Allocate the placeholders for the preferred parts and variants |
737 | 0 | nsresult rv = NS_ERROR_OUT_OF_MEMORY; |
738 | 0 | RefPtr<nsGlyphTableList> glyphTableList = new nsGlyphTableList(); |
739 | 0 | if (glyphTableList) { |
740 | 0 | rv = glyphTableList->Initialize(); |
741 | 0 | } |
742 | 0 | if (NS_FAILED(rv)) { |
743 | 0 | return rv; |
744 | 0 | } |
745 | 0 | // The gGlyphTableList has been successfully registered as a shutdown |
746 | 0 | // observer and will be deleted at shutdown. We now add some private |
747 | 0 | // per font-family tables for stretchy operators, in order of preference. |
748 | 0 | // Do not include the Unicode table in this list. |
749 | 0 | if (!glyphTableList->AddGlyphTable(NS_LITERAL_CSTRING("STIXGeneral"))) { |
750 | 0 | rv = NS_ERROR_OUT_OF_MEMORY; |
751 | 0 | } |
752 | 0 |
|
753 | 0 | glyphTableList.forget(&gGlyphTableList); |
754 | 0 | return rv; |
755 | 0 | } |
756 | | |
757 | | // ----------------------------------------------------------------------------- |
758 | | // And now the implementation of nsMathMLChar |
759 | | |
760 | | nsMathMLChar::~nsMathMLChar() |
761 | 0 | { |
762 | 0 | MOZ_COUNT_DTOR(nsMathMLChar); |
763 | 0 | } |
764 | | |
765 | | ComputedStyle* |
766 | | nsMathMLChar::GetComputedStyle() const |
767 | 0 | { |
768 | 0 | NS_ASSERTION(mComputedStyle, "chars should always have a ComputedStyle"); |
769 | 0 | return mComputedStyle; |
770 | 0 | } |
771 | | |
772 | | void |
773 | | nsMathMLChar::SetComputedStyle(ComputedStyle* aComputedStyle) |
774 | 0 | { |
775 | 0 | MOZ_ASSERT(aComputedStyle); |
776 | 0 | mComputedStyle = aComputedStyle; |
777 | 0 | } |
778 | | |
779 | | void |
780 | | nsMathMLChar::SetData(nsString& aData) |
781 | 0 | { |
782 | 0 | if (!gGlyphTableInitialized) { |
783 | 0 | InitCharGlobals(); |
784 | 0 | } |
785 | 0 | mData = aData; |
786 | 0 | // some assumptions until proven otherwise |
787 | 0 | // note that mGlyph is not initialized |
788 | 0 | mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED; |
789 | 0 | mBoundingMetrics = nsBoundingMetrics(); |
790 | 0 | // check if stretching is applicable ... |
791 | 0 | if (gGlyphTableList && (1 == mData.Length())) { |
792 | 0 | mDirection = nsMathMLOperators::GetStretchyDirection(mData); |
793 | 0 | // default tentative table (not the one that is necessarily going |
794 | 0 | // to be used) |
795 | 0 | } |
796 | 0 | } |
797 | | |
798 | | // ----------------------------------------------------------------------------- |
799 | | /* |
800 | | The Stretch: |
801 | | @param aContainerSize - suggested size for the stretched char |
802 | | @param aDesiredStretchSize - OUT parameter. The desired size |
803 | | after stretching. If no stretching is done, the output will |
804 | | simply give the base size. |
805 | | |
806 | | How it works? |
807 | | Summary:- |
808 | | The Stretch() method first looks for a glyph of appropriate |
809 | | size; If a glyph is found, it is cached by this object and |
810 | | its size is returned in aDesiredStretchSize. The cached |
811 | | glyph will then be used at the painting stage. |
812 | | If no glyph of appropriate size is found, a search is made |
813 | | to see if the char can be built by parts. |
814 | | |
815 | | Details:- |
816 | | A character gets stretched through the following pipeline : |
817 | | |
818 | | 1) If the base size of the char is sufficient to cover the |
819 | | container' size, we use that. If not, it will still be |
820 | | used as a fallback if the other stages in the pipeline fail. |
821 | | Issues : |
822 | | a) The base size, the parts and the variants of a char can |
823 | | be in different fonts. For eg., the base size for '(' should |
824 | | come from a normal ascii font if CMEX10 is used, since CMEX10 |
825 | | only contains the stretched versions. Hence, there are two |
826 | | ComputedStyles in use throughout the process. The leaf style |
827 | | context of the char holds fonts with which to try to stretch |
828 | | the char. The parent ComputedStyle of the char contains fonts |
829 | | for normal rendering. So the parent context is the one used |
830 | | to get the initial base size at the start of the pipeline. |
831 | | b) For operators that can be largeop's in display mode, |
832 | | we will skip the base size even if it fits, so that |
833 | | the next stage in the pipeline is given a chance to find |
834 | | a largeop variant. If the next stage fails, we fallback |
835 | | to the base size. |
836 | | |
837 | | 2) We search for the first larger variant of the char that fits the |
838 | | container' size. We first search for larger variants using the glyph |
839 | | table corresponding to the first existing font specified in the list of |
840 | | stretchy fonts held by the leaf ComputedStyle (from -moz-math-stretchy in |
841 | | mathml.css). Generic fonts are resolved by the preference |
842 | | "font.mathfont-family". |
843 | | Issues : |
844 | | a) the largeop and display settings determine the starting |
845 | | size when we do the above search, regardless of whether |
846 | | smaller variants already fit the container' size. |
847 | | b) if it is a largeopOnly request (i.e., a displaystyle operator |
848 | | with largeop=true and stretchy=false), we break after finding |
849 | | the first starting variant, regardless of whether that |
850 | | variant fits the container's size. |
851 | | |
852 | | 3) If a variant of appropriate size wasn't found, we see if the char |
853 | | can be built by parts using the same glyph table. |
854 | | Issue: |
855 | | There are chars that have no middle and glue glyphs. For |
856 | | such chars, the parts need to be joined using the rule. |
857 | | By convention (TeXbook p.225), the descent of the parts is |
858 | | zero while their ascent gives the thickness of the rule that |
859 | | should be used to join them. |
860 | | |
861 | | 4) If a match was not found in that glyph table, repeat from 2 to search the |
862 | | ordered list of stretchy fonts for the first font with a glyph table that |
863 | | provides a fit to the container size. If no fit is found, the closest fit |
864 | | is used. |
865 | | |
866 | | Of note: |
867 | | When the pipeline completes successfully, the desired size of the |
868 | | stretched char can actually be slightly larger or smaller than |
869 | | aContainerSize. But it is the responsibility of the caller to |
870 | | account for the spacing when setting aContainerSize, and to leave |
871 | | any extra margin when placing the stretched char. |
872 | | */ |
873 | | // ----------------------------------------------------------------------------- |
874 | | |
875 | | |
876 | | // plain TeX settings (TeXbook p.152) |
877 | 0 | #define NS_MATHML_DELIMITER_FACTOR 0.901f |
878 | 0 | #define NS_MATHML_DELIMITER_SHORTFALL_POINTS 5.0f |
879 | | |
880 | | static bool |
881 | | IsSizeOK(nscoord a, nscoord b, uint32_t aHint) |
882 | 0 | { |
883 | 0 | // Normal: True if 'a' is around +/-10% of the target 'b' (10% is |
884 | 0 | // 1-DelimiterFactor). This often gives a chance to the base size to |
885 | 0 | // win, especially in the context of <mfenced> without tall elements |
886 | 0 | // or in sloppy markups without protective <mrow></mrow> |
887 | 0 | bool isNormal = |
888 | 0 | (aHint & NS_STRETCH_NORMAL) && |
889 | 0 | Abs<float>(a - b) < (1.0f - NS_MATHML_DELIMITER_FACTOR) * float(b); |
890 | 0 |
|
891 | 0 | // Nearer: True if 'a' is around max{ +/-10% of 'b' , 'b' - 5pt }, |
892 | 0 | // as documented in The TeXbook, Ch.17, p.152. |
893 | 0 | // i.e. within 10% and within 5pt |
894 | 0 | bool isNearer = false; |
895 | 0 | if (aHint & (NS_STRETCH_NEARER | NS_STRETCH_LARGEOP)) { |
896 | 0 | float c = std::max(float(b) * NS_MATHML_DELIMITER_FACTOR, |
897 | 0 | float(b) - nsPresContext:: |
898 | 0 | CSSPointsToAppUnits(NS_MATHML_DELIMITER_SHORTFALL_POINTS)); |
899 | 0 | isNearer = Abs<float>(b - a) <= float(b) - c; |
900 | 0 | } |
901 | 0 |
|
902 | 0 | // Smaller: Mainly for transitory use, to compare two candidate |
903 | 0 | // choices |
904 | 0 | bool isSmaller = |
905 | 0 | (aHint & NS_STRETCH_SMALLER) && |
906 | 0 | float(a) >= NS_MATHML_DELIMITER_FACTOR * float(b) && |
907 | 0 | a <= b; |
908 | 0 |
|
909 | 0 | // Larger: Critical to the sqrt code to ensure that the radical |
910 | 0 | // size is tall enough |
911 | 0 | bool isLarger = |
912 | 0 | (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) && |
913 | 0 | a >= b; |
914 | 0 |
|
915 | 0 | return (isNormal || isSmaller || isNearer || isLarger); |
916 | 0 | } |
917 | | |
918 | | static bool |
919 | | IsSizeBetter(nscoord a, nscoord olda, nscoord b, uint32_t aHint) |
920 | 0 | { |
921 | 0 | if (0 == olda) |
922 | 0 | return true; |
923 | 0 | if (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) |
924 | 0 | return (a >= olda) ? (olda < b) : (a >= b); |
925 | 0 | if (aHint & NS_STRETCH_SMALLER) |
926 | 0 | return (a <= olda) ? (olda > b) : (a <= b); |
927 | 0 | |
928 | 0 | // XXXkt prob want log scale here i.e. 1.5 is closer to 1 than 0.5 |
929 | 0 | return Abs(a - b) < Abs(olda - b); |
930 | 0 | } |
931 | | |
932 | | // We want to place the glyphs even when they don't fit at their |
933 | | // full extent, i.e., we may clip to tolerate a small amount of |
934 | | // overlap between the parts. This is important to cater for fonts |
935 | | // with long glues. |
936 | | static nscoord |
937 | | ComputeSizeFromParts(nsPresContext* aPresContext, |
938 | | nsGlyphCode* aGlyphs, |
939 | | nscoord* aSizes, |
940 | | nscoord aTargetSize) |
941 | 0 | { |
942 | 0 | enum {first, middle, last, glue}; |
943 | 0 | // Add the parts that cannot be left out. |
944 | 0 | nscoord sum = 0; |
945 | 0 | for (int32_t i = first; i <= last; i++) { |
946 | 0 | sum += aSizes[i]; |
947 | 0 | } |
948 | 0 |
|
949 | 0 | // Determine how much is used in joins |
950 | 0 | nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); |
951 | 0 | int32_t joins = aGlyphs[middle] == aGlyphs[glue] ? 1 : 2; |
952 | 0 |
|
953 | 0 | // Pick a maximum size using a maximum number of glue glyphs that we are |
954 | 0 | // prepared to draw for one character. |
955 | 0 | const int32_t maxGlyphs = 1000; |
956 | 0 |
|
957 | 0 | // This also takes into account the fact that, if the glue has no size, |
958 | 0 | // then the character can't be lengthened. |
959 | 0 | nscoord maxSize = sum - 2 * joins * oneDevPixel + maxGlyphs * aSizes[glue]; |
960 | 0 | if (maxSize < aTargetSize) |
961 | 0 | return maxSize; // settle with the maximum size |
962 | 0 | |
963 | 0 | // Get the minimum allowable size using some flex. |
964 | 0 | nscoord minSize = NSToCoordRound(NS_MATHML_DELIMITER_FACTOR * sum); |
965 | 0 |
|
966 | 0 | if (minSize > aTargetSize) |
967 | 0 | return minSize; // settle with the minimum size |
968 | 0 | |
969 | 0 | // Fill-up the target area |
970 | 0 | return aTargetSize; |
971 | 0 | } |
972 | | |
973 | | // Update the font if there is a family change and returns the font group. |
974 | | bool |
975 | | nsMathMLChar::SetFontFamily(nsPresContext* aPresContext, |
976 | | const nsGlyphTable* aGlyphTable, |
977 | | const nsGlyphCode& aGlyphCode, |
978 | | const FontFamilyList& aDefaultFamilyList, |
979 | | nsFont& aFont, |
980 | | RefPtr<gfxFontGroup>* aFontGroup) |
981 | 0 | { |
982 | 0 | FontFamilyList glyphCodeFont; |
983 | 0 |
|
984 | 0 | if (aGlyphCode.font) { |
985 | 0 | nsTArray<FontFamilyName> names; |
986 | 0 | names.AppendElement(aGlyphTable->FontNameFor(aGlyphCode)); |
987 | 0 | glyphCodeFont.SetFontlist(std::move(names)); |
988 | 0 | } |
989 | 0 |
|
990 | 0 | const FontFamilyList& familyList = |
991 | 0 | aGlyphCode.font ? glyphCodeFont : aDefaultFamilyList; |
992 | 0 |
|
993 | 0 | if (!*aFontGroup || !(aFont.fontlist == familyList)) { |
994 | 0 | nsFont font = aFont; |
995 | 0 | font.fontlist = familyList; |
996 | 0 | const nsStyleFont* styleFont = mComputedStyle->StyleFont(); |
997 | 0 | nsFontMetrics::Params params; |
998 | 0 | params.language = styleFont->mLanguage; |
999 | 0 | params.explicitLanguage = styleFont->mExplicitLanguage; |
1000 | 0 | params.userFontSet = aPresContext->GetUserFontSet(); |
1001 | 0 | params.textPerf = aPresContext->GetTextPerfMetrics(); |
1002 | 0 | RefPtr<nsFontMetrics> fm = |
1003 | 0 | aPresContext->DeviceContext()->GetMetricsFor(font, params); |
1004 | 0 | // Set the font if it is an unicode table |
1005 | 0 | // or if the same family name has been found |
1006 | 0 | gfxFont *firstFont = fm->GetThebesFontGroup()->GetFirstValidFont(); |
1007 | 0 | FontFamilyList firstFontList( |
1008 | 0 | firstFont->GetFontEntry()->FamilyName(), eUnquotedName); |
1009 | 0 | if (aGlyphTable == &gGlyphTableList->mUnicodeTable || |
1010 | 0 | firstFontList == familyList) { |
1011 | 0 | aFont.fontlist = familyList; |
1012 | 0 | *aFontGroup = fm->GetThebesFontGroup(); |
1013 | 0 | } else { |
1014 | 0 | return false; // We did not set the font |
1015 | 0 | } |
1016 | 0 | } |
1017 | 0 | return true; |
1018 | 0 | } |
1019 | | |
1020 | | static nsBoundingMetrics |
1021 | | MeasureTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun) |
1022 | 0 | { |
1023 | 0 | gfxTextRun::Metrics metrics = |
1024 | 0 | aTextRun->MeasureText(gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, aDrawTarget); |
1025 | 0 |
|
1026 | 0 | nsBoundingMetrics bm; |
1027 | 0 | bm.leftBearing = NSToCoordFloor(metrics.mBoundingBox.X()); |
1028 | 0 | bm.rightBearing = NSToCoordCeil(metrics.mBoundingBox.XMost()); |
1029 | 0 | bm.ascent = NSToCoordCeil(-metrics.mBoundingBox.Y()); |
1030 | 0 | bm.descent = NSToCoordCeil(metrics.mBoundingBox.YMost()); |
1031 | 0 | bm.width = NSToCoordRound(metrics.mAdvanceWidth); |
1032 | 0 |
|
1033 | 0 | return bm; |
1034 | 0 | } |
1035 | | |
1036 | | class nsMathMLChar::StretchEnumContext { |
1037 | | public: |
1038 | | StretchEnumContext(nsMathMLChar* aChar, |
1039 | | nsPresContext* aPresContext, |
1040 | | DrawTarget* aDrawTarget, |
1041 | | float aFontSizeInflation, |
1042 | | nsStretchDirection aStretchDirection, |
1043 | | nscoord aTargetSize, |
1044 | | uint32_t aStretchHint, |
1045 | | nsBoundingMetrics& aStretchedMetrics, |
1046 | | const FontFamilyList& aFamilyList, |
1047 | | bool& aGlyphFound) |
1048 | | : mChar(aChar), |
1049 | | mPresContext(aPresContext), |
1050 | | mDrawTarget(aDrawTarget), |
1051 | | mFontSizeInflation(aFontSizeInflation), |
1052 | | mDirection(aStretchDirection), |
1053 | | mTargetSize(aTargetSize), |
1054 | | mStretchHint(aStretchHint), |
1055 | | mBoundingMetrics(aStretchedMetrics), |
1056 | | mFamilyList(aFamilyList), |
1057 | | mTryVariants(true), |
1058 | | mTryParts(true), |
1059 | 0 | mGlyphFound(aGlyphFound) {} |
1060 | | |
1061 | | static bool |
1062 | | EnumCallback(const FontFamilyName& aFamily, bool aGeneric, void *aData); |
1063 | | |
1064 | | private: |
1065 | | bool TryVariants(nsGlyphTable* aGlyphTable, |
1066 | | RefPtr<gfxFontGroup>* aFontGroup, |
1067 | | const FontFamilyList& aFamilyList); |
1068 | | bool TryParts(nsGlyphTable* aGlyphTable, |
1069 | | RefPtr<gfxFontGroup>* aFontGroup, |
1070 | | const FontFamilyList& aFamilyList); |
1071 | | |
1072 | | nsMathMLChar* mChar; |
1073 | | nsPresContext* mPresContext; |
1074 | | DrawTarget* mDrawTarget; |
1075 | | float mFontSizeInflation; |
1076 | | const nsStretchDirection mDirection; |
1077 | | const nscoord mTargetSize; |
1078 | | const uint32_t mStretchHint; |
1079 | | nsBoundingMetrics& mBoundingMetrics; |
1080 | | // Font families to search |
1081 | | const FontFamilyList& mFamilyList; |
1082 | | |
1083 | | public: |
1084 | | bool mTryVariants; |
1085 | | bool mTryParts; |
1086 | | |
1087 | | private: |
1088 | | AutoTArray<nsGlyphTable*,16> mTablesTried; |
1089 | | bool& mGlyphFound; |
1090 | | }; |
1091 | | |
1092 | | |
1093 | | // 2. See if there are any glyphs of the appropriate size. |
1094 | | // Returns true if the size is OK, false to keep searching. |
1095 | | // Always updates the char if a better match is found. |
1096 | | bool |
1097 | | nsMathMLChar:: |
1098 | | StretchEnumContext::TryVariants(nsGlyphTable* aGlyphTable, |
1099 | | RefPtr<gfxFontGroup>* aFontGroup, |
1100 | | const FontFamilyList& aFamilyList) |
1101 | 0 | { |
1102 | 0 | // Use our stretchy ComputedStyle now that stretching is in progress |
1103 | 0 | ComputedStyle *sc = mChar->mComputedStyle; |
1104 | 0 | nsFont font = sc->StyleFont()->mFont; |
1105 | 0 | NormalizeDefaultFont(font, mFontSizeInflation); |
1106 | 0 |
|
1107 | 0 | bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL); |
1108 | 0 | nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel(); |
1109 | 0 | char16_t uchar = mChar->mData[0]; |
1110 | 0 | bool largeop = (NS_STRETCH_LARGEOP & mStretchHint) != 0; |
1111 | 0 | bool largeopOnly = |
1112 | 0 | largeop && (NS_STRETCH_VARIABLE_MASK & mStretchHint) == 0; |
1113 | 0 | bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0; |
1114 | 0 |
|
1115 | 0 | nscoord bestSize = |
1116 | 0 | isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent |
1117 | 0 | : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; |
1118 | 0 | bool haveBetter = false; |
1119 | 0 |
|
1120 | 0 | // start at size = 1 (size = 0 is the char at its normal size) |
1121 | 0 | int32_t size = 1; |
1122 | 0 | nsGlyphCode ch; |
1123 | 0 | nscoord displayOperatorMinHeight = 0; |
1124 | 0 | if (largeopOnly) { |
1125 | 0 | NS_ASSERTION(isVertical, "Stretching should be in the vertical direction"); |
1126 | 0 | ch = aGlyphTable->BigOf(mDrawTarget, oneDevPixel, *aFontGroup, uchar, |
1127 | 0 | isVertical, 0); |
1128 | 0 | if (ch.IsGlyphID()) { |
1129 | 0 | gfxFont* mathFont = aFontGroup->get()->GetFirstMathFont(); |
1130 | 0 | // For OpenType MATH fonts, we will rely on the DisplayOperatorMinHeight |
1131 | 0 | // to select the right size variant. Note that the value is sometimes too |
1132 | 0 | // small so we use kLargeOpFactor/kIntegralFactor as a minimum value. |
1133 | 0 | if (mathFont) { |
1134 | 0 | displayOperatorMinHeight = mathFont->MathTable()-> |
1135 | 0 | Constant(gfxMathTable::DisplayOperatorMinHeight, oneDevPixel); |
1136 | 0 | RefPtr<gfxTextRun> textRun = |
1137 | 0 | aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, *aFontGroup, ch); |
1138 | 0 | nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun.get()); |
1139 | 0 | float largeopFactor = kLargeOpFactor; |
1140 | 0 | if (NS_STRETCH_INTEGRAL & mStretchHint) { |
1141 | 0 | // integrals are drawn taller |
1142 | 0 | largeopFactor = kIntegralFactor; |
1143 | 0 | } |
1144 | 0 | nscoord minHeight = largeopFactor * (bm.ascent + bm.descent); |
1145 | 0 | if (displayOperatorMinHeight < minHeight) { |
1146 | 0 | displayOperatorMinHeight = minHeight; |
1147 | 0 | } |
1148 | 0 | } |
1149 | 0 | } |
1150 | 0 | } |
1151 | | #ifdef NOISY_SEARCH |
1152 | | printf(" searching in %s ...\n", |
1153 | | NS_LossyConvertUTF16toASCII(aFamily).get()); |
1154 | | #endif |
1155 | 0 | while ((ch = aGlyphTable->BigOf(mDrawTarget, oneDevPixel, *aFontGroup, |
1156 | 0 | uchar, isVertical, size)).Exists()) { |
1157 | 0 |
|
1158 | 0 | if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamilyList, font, |
1159 | 0 | aFontGroup)) { |
1160 | 0 | // if largeopOnly is set, break now |
1161 | 0 | if (largeopOnly) break; |
1162 | 0 | ++size; |
1163 | 0 | continue; |
1164 | 0 | } |
1165 | 0 | |
1166 | 0 | RefPtr<gfxTextRun> textRun = |
1167 | 0 | aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, *aFontGroup, ch); |
1168 | 0 | nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun.get()); |
1169 | 0 | if (ch.IsGlyphID()) { |
1170 | 0 | gfxFont* mathFont = aFontGroup->get()->GetFirstMathFont(); |
1171 | 0 | if (mathFont) { |
1172 | 0 | // MeasureTextRun should have set the advance width to the right |
1173 | 0 | // bearing for OpenType MATH fonts. We now subtract the italic |
1174 | 0 | // correction, so that nsMathMLmmultiscripts will place the scripts |
1175 | 0 | // correctly. |
1176 | 0 | // Note that STIX-Word does not provide italic corrections but its |
1177 | 0 | // advance widths do not match right bearings. |
1178 | 0 | // (http://sourceforge.net/p/stixfonts/tracking/50/) |
1179 | 0 | gfxFloat italicCorrection = |
1180 | 0 | mathFont->MathTable()->ItalicsCorrection(ch.glyphID); |
1181 | 0 | if (italicCorrection) { |
1182 | 0 | bm.width -= |
1183 | 0 | NSToCoordRound(italicCorrection * oneDevPixel); |
1184 | 0 | if (bm.width < 0) { |
1185 | 0 | bm.width = 0; |
1186 | 0 | } |
1187 | 0 | } |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 |
|
1191 | 0 | nscoord charSize = |
1192 | 0 | isVertical ? bm.ascent + bm.descent |
1193 | 0 | : bm.rightBearing - bm.leftBearing; |
1194 | 0 |
|
1195 | 0 | if (largeopOnly || |
1196 | 0 | IsSizeBetter(charSize, bestSize, mTargetSize, mStretchHint)) { |
1197 | 0 | mGlyphFound = true; |
1198 | 0 | if (maxWidth) { |
1199 | 0 | // IsSizeBetter() checked that charSize < maxsize; |
1200 | 0 | // Leave ascent, descent, and bestsize as these contain maxsize. |
1201 | 0 | if (mBoundingMetrics.width < bm.width) |
1202 | 0 | mBoundingMetrics.width = bm.width; |
1203 | 0 | if (mBoundingMetrics.leftBearing > bm.leftBearing) |
1204 | 0 | mBoundingMetrics.leftBearing = bm.leftBearing; |
1205 | 0 | if (mBoundingMetrics.rightBearing < bm.rightBearing) |
1206 | 0 | mBoundingMetrics.rightBearing = bm.rightBearing; |
1207 | 0 | // Continue to check other sizes unless largeopOnly |
1208 | 0 | haveBetter = largeopOnly; |
1209 | 0 | } |
1210 | 0 | else { |
1211 | 0 | mBoundingMetrics = bm; |
1212 | 0 | haveBetter = true; |
1213 | 0 | bestSize = charSize; |
1214 | 0 | mChar->mGlyphs[0] = std::move(textRun); |
1215 | 0 | mChar->mDraw = DRAW_VARIANT; |
1216 | 0 | } |
1217 | | #ifdef NOISY_SEARCH |
1218 | | printf(" size:%d Current best\n", size); |
1219 | | #endif |
1220 | | } |
1221 | 0 | else { |
1222 | | #ifdef NOISY_SEARCH |
1223 | | printf(" size:%d Rejected!\n", size); |
1224 | | #endif |
1225 | 0 | if (haveBetter) |
1226 | 0 | break; // Not making an futher progress, stop searching |
1227 | 0 | } |
1228 | 0 | |
1229 | 0 | // If this a largeop only operator, we stop if the glyph is large enough. |
1230 | 0 | if (largeopOnly && (bm.ascent + bm.descent) >= displayOperatorMinHeight) { |
1231 | 0 | break; |
1232 | 0 | } |
1233 | 0 | ++size; |
1234 | 0 | } |
1235 | 0 |
|
1236 | 0 | return haveBetter && |
1237 | 0 | (largeopOnly || IsSizeOK(bestSize, mTargetSize, mStretchHint)); |
1238 | 0 | } |
1239 | | |
1240 | | // 3. Build by parts. |
1241 | | // Returns true if the size is OK, false to keep searching. |
1242 | | // Always updates the char if a better match is found. |
1243 | | bool |
1244 | | nsMathMLChar::StretchEnumContext::TryParts(nsGlyphTable* aGlyphTable, |
1245 | | RefPtr<gfxFontGroup>* aFontGroup, |
1246 | | const FontFamilyList& aFamilyList) |
1247 | 0 | { |
1248 | 0 | // Use our stretchy ComputedStyle now that stretching is in progress |
1249 | 0 | nsFont font = mChar->mComputedStyle->StyleFont()->mFont; |
1250 | 0 | NormalizeDefaultFont(font, mFontSizeInflation); |
1251 | 0 |
|
1252 | 0 | // Compute the bounding metrics of all partial glyphs |
1253 | 0 | RefPtr<gfxTextRun> textRun[4]; |
1254 | 0 | nsGlyphCode chdata[4]; |
1255 | 0 | nsBoundingMetrics bmdata[4]; |
1256 | 0 | nscoord sizedata[4]; |
1257 | 0 |
|
1258 | 0 | bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL); |
1259 | 0 | nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel(); |
1260 | 0 | char16_t uchar = mChar->mData[0]; |
1261 | 0 | bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0; |
1262 | 0 | if (!aGlyphTable->HasPartsOf(mDrawTarget, oneDevPixel, *aFontGroup, |
1263 | 0 | uchar, isVertical)) |
1264 | 0 | return false; // to next table |
1265 | 0 | |
1266 | 0 | for (int32_t i = 0; i < 4; i++) { |
1267 | 0 | nsGlyphCode ch = aGlyphTable->ElementAt(mDrawTarget, oneDevPixel, |
1268 | 0 | *aFontGroup, uchar, isVertical, i); |
1269 | 0 | chdata[i] = ch; |
1270 | 0 | if (ch.Exists()) { |
1271 | 0 | if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamilyList, font, |
1272 | 0 | aFontGroup)) |
1273 | 0 | return false; |
1274 | 0 | |
1275 | 0 | textRun[i] = aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, |
1276 | 0 | *aFontGroup, ch); |
1277 | 0 | nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun[i].get()); |
1278 | 0 | bmdata[i] = bm; |
1279 | 0 | sizedata[i] = isVertical ? bm.ascent + bm.descent |
1280 | 0 | : bm.rightBearing - bm.leftBearing; |
1281 | 0 | } else { |
1282 | 0 | // Null glue indicates that a rule will be drawn, which can stretch to |
1283 | 0 | // fill any space. |
1284 | 0 | textRun[i] = nullptr; |
1285 | 0 | bmdata[i] = nsBoundingMetrics(); |
1286 | 0 | sizedata[i] = i == 3 ? mTargetSize : 0; |
1287 | 0 | } |
1288 | 0 | } |
1289 | 0 |
|
1290 | 0 | // For the Unicode table, we check that all the glyphs are actually found and |
1291 | 0 | // come from the same font. |
1292 | 0 | if (aGlyphTable == &gGlyphTableList->mUnicodeTable) { |
1293 | 0 | gfxFont* unicodeFont = nullptr; |
1294 | 0 | for (int32_t i = 0; i < 4; i++) { |
1295 | 0 | if (!textRun[i]) { |
1296 | 0 | continue; |
1297 | 0 | } |
1298 | 0 | if (textRun[i]->GetLength() != 1 || |
1299 | 0 | textRun[i]->GetCharacterGlyphs()[0].IsMissing()) { |
1300 | 0 | return false; |
1301 | 0 | } |
1302 | 0 | uint32_t numGlyphRuns; |
1303 | 0 | const gfxTextRun::GlyphRun* glyphRuns = |
1304 | 0 | textRun[i]->GetGlyphRuns(&numGlyphRuns); |
1305 | 0 | if (numGlyphRuns != 1) { |
1306 | 0 | return false; |
1307 | 0 | } |
1308 | 0 | if (!unicodeFont) { |
1309 | 0 | unicodeFont = glyphRuns[0].mFont; |
1310 | 0 | } else if (unicodeFont != glyphRuns[0].mFont) { |
1311 | 0 | return false; |
1312 | 0 | } |
1313 | 0 | } |
1314 | 0 | } |
1315 | 0 |
|
1316 | 0 | // Build by parts if we have successfully computed the |
1317 | 0 | // bounding metrics of all parts. |
1318 | 0 | nscoord computedSize = ComputeSizeFromParts(mPresContext, chdata, sizedata, |
1319 | 0 | mTargetSize); |
1320 | 0 |
|
1321 | 0 | nscoord currentSize = |
1322 | 0 | isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent |
1323 | 0 | : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; |
1324 | 0 |
|
1325 | 0 | if (!IsSizeBetter(computedSize, currentSize, mTargetSize, mStretchHint)) { |
1326 | | #ifdef NOISY_SEARCH |
1327 | | printf(" Font %s Rejected!\n", |
1328 | | NS_LossyConvertUTF16toASCII(fontName).get()); |
1329 | | #endif |
1330 | | return false; // to next table |
1331 | 0 | } |
1332 | 0 |
|
1333 | | #ifdef NOISY_SEARCH |
1334 | | printf(" Font %s Current best!\n", |
1335 | | NS_LossyConvertUTF16toASCII(fontName).get()); |
1336 | | #endif |
1337 | | |
1338 | 0 | // The computed size is the best we have found so far... |
1339 | 0 | // now is the time to compute and cache our bounding metrics |
1340 | 0 | if (isVertical) { |
1341 | 0 | int32_t i; |
1342 | 0 | // Try and find the first existing part and then determine the extremal |
1343 | 0 | // horizontal metrics of the parts. |
1344 | 0 | for (i = 0; i <= 3 && !textRun[i]; i++); |
1345 | 0 | if (i == 4) { |
1346 | 0 | NS_ERROR("Cannot stretch - All parts missing"); |
1347 | 0 | return false; |
1348 | 0 | } |
1349 | 0 | nscoord lbearing = bmdata[i].leftBearing; |
1350 | 0 | nscoord rbearing = bmdata[i].rightBearing; |
1351 | 0 | nscoord width = bmdata[i].width; |
1352 | 0 | i++; |
1353 | 0 | for (; i <= 3; i++) { |
1354 | 0 | if (!textRun[i]) continue; |
1355 | 0 | lbearing = std::min(lbearing, bmdata[i].leftBearing); |
1356 | 0 | rbearing = std::max(rbearing, bmdata[i].rightBearing); |
1357 | 0 | width = std::max(width, bmdata[i].width); |
1358 | 0 | } |
1359 | 0 | if (maxWidth) { |
1360 | 0 | lbearing = std::min(lbearing, mBoundingMetrics.leftBearing); |
1361 | 0 | rbearing = std::max(rbearing, mBoundingMetrics.rightBearing); |
1362 | 0 | width = std::max(width, mBoundingMetrics.width); |
1363 | 0 | } |
1364 | 0 | mBoundingMetrics.width = width; |
1365 | 0 | // When maxWidth, updating ascent and descent indicates that no characters |
1366 | 0 | // larger than this character's minimum size need to be checked as they |
1367 | 0 | // will not be used. |
1368 | 0 | mBoundingMetrics.ascent = bmdata[0].ascent; // not used except with descent |
1369 | 0 | // for height |
1370 | 0 | mBoundingMetrics.descent = computedSize - mBoundingMetrics.ascent; |
1371 | 0 | mBoundingMetrics.leftBearing = lbearing; |
1372 | 0 | mBoundingMetrics.rightBearing = rbearing; |
1373 | 0 | } |
1374 | 0 | else { |
1375 | 0 | int32_t i; |
1376 | 0 | // Try and find the first existing part and then determine the extremal |
1377 | 0 | // vertical metrics of the parts. |
1378 | 0 | for (i = 0; i <= 3 && !textRun[i]; i++); |
1379 | 0 | if (i == 4) { |
1380 | 0 | NS_ERROR("Cannot stretch - All parts missing"); |
1381 | 0 | return false; |
1382 | 0 | } |
1383 | 0 | nscoord ascent = bmdata[i].ascent; |
1384 | 0 | nscoord descent = bmdata[i].descent; |
1385 | 0 | i++; |
1386 | 0 | for (; i <= 3; i++) { |
1387 | 0 | if (!textRun[i]) continue; |
1388 | 0 | ascent = std::max(ascent, bmdata[i].ascent); |
1389 | 0 | descent = std::max(descent, bmdata[i].descent); |
1390 | 0 | } |
1391 | 0 | mBoundingMetrics.width = computedSize; |
1392 | 0 | mBoundingMetrics.ascent = ascent; |
1393 | 0 | mBoundingMetrics.descent = descent; |
1394 | 0 | mBoundingMetrics.leftBearing = 0; |
1395 | 0 | mBoundingMetrics.rightBearing = computedSize; |
1396 | 0 | } |
1397 | 0 | mGlyphFound = true; |
1398 | 0 | if (maxWidth) |
1399 | 0 | return false; // Continue to check other sizes |
1400 | 0 | |
1401 | 0 | // reset |
1402 | 0 | mChar->mDraw = DRAW_PARTS; |
1403 | 0 | for (int32_t i = 0; i < 4; i++) { |
1404 | 0 | mChar->mGlyphs[i] = std::move(textRun[i]); |
1405 | 0 | mChar->mBmData[i] = bmdata[i]; |
1406 | 0 | } |
1407 | 0 |
|
1408 | 0 | return IsSizeOK(computedSize, mTargetSize, mStretchHint); |
1409 | 0 | } |
1410 | | |
1411 | | // This is called for each family, whether it exists or not |
1412 | | bool |
1413 | | nsMathMLChar::StretchEnumContext::EnumCallback(const FontFamilyName& aFamily, |
1414 | | bool aGeneric, void *aData) |
1415 | 0 | { |
1416 | 0 | StretchEnumContext* context = static_cast<StretchEnumContext*>(aData); |
1417 | 0 |
|
1418 | 0 | // for comparisons, force use of unquoted names |
1419 | 0 | FontFamilyName unquotedFamilyName(aFamily); |
1420 | 0 | if (unquotedFamilyName.mType == eFamily_named_quoted) { |
1421 | 0 | unquotedFamilyName.mType = eFamily_named; |
1422 | 0 | } |
1423 | 0 |
|
1424 | 0 | // Check font family if it is not a generic one |
1425 | 0 | // We test with the kNullGlyph |
1426 | 0 | ComputedStyle *sc = context->mChar->mComputedStyle; |
1427 | 0 | nsFont font = sc->StyleFont()->mFont; |
1428 | 0 | NormalizeDefaultFont(font, context->mFontSizeInflation); |
1429 | 0 | RefPtr<gfxFontGroup> fontGroup; |
1430 | 0 | FontFamilyList family(unquotedFamilyName); |
1431 | 0 | if (!aGeneric && !context->mChar->SetFontFamily(context->mPresContext, |
1432 | 0 | nullptr, kNullGlyph, family, |
1433 | 0 | font, &fontGroup)) |
1434 | 0 | return true; // Could not set the family |
1435 | 0 | |
1436 | 0 | // Determine the glyph table to use for this font. |
1437 | 0 | nsAutoPtr<nsOpenTypeTable> openTypeTable; |
1438 | 0 | nsGlyphTable* glyphTable; |
1439 | 0 | if (aGeneric) { |
1440 | 0 | // This is a generic font, use the Unicode table. |
1441 | 0 | glyphTable = &gGlyphTableList->mUnicodeTable; |
1442 | 0 | } else { |
1443 | 0 | // If the font contains an Open Type MATH table, use it. |
1444 | 0 | openTypeTable = nsOpenTypeTable::Create(fontGroup->GetFirstValidFont()); |
1445 | 0 | if (openTypeTable) { |
1446 | 0 | glyphTable = openTypeTable; |
1447 | 0 | } else { |
1448 | 0 | // Otherwise try to find a .properties file corresponding to that font |
1449 | 0 | // family or fallback to the Unicode table. |
1450 | 0 | nsAutoCString familyName; |
1451 | 0 | unquotedFamilyName.AppendToString(familyName); |
1452 | 0 | glyphTable = gGlyphTableList->GetGlyphTableFor(familyName); |
1453 | 0 | } |
1454 | 0 | } |
1455 | 0 |
|
1456 | 0 | if (!openTypeTable) { |
1457 | 0 | if (context->mTablesTried.Contains(glyphTable)) |
1458 | 0 | return true; // already tried this one |
1459 | 0 | |
1460 | 0 | // Only try this table once. |
1461 | 0 | context->mTablesTried.AppendElement(glyphTable); |
1462 | 0 | } |
1463 | 0 |
|
1464 | 0 | // If the unicode table is being used, then search all font families. If a |
1465 | 0 | // special table is being used then the font in this family should have the |
1466 | 0 | // specified glyphs. |
1467 | 0 | const FontFamilyList& familyList = glyphTable == &gGlyphTableList->mUnicodeTable ? |
1468 | 0 | context->mFamilyList : family; |
1469 | 0 |
|
1470 | 0 | if((context->mTryVariants && |
1471 | 0 | context->TryVariants(glyphTable, &fontGroup, familyList)) || |
1472 | 0 | (context->mTryParts && context->TryParts(glyphTable, |
1473 | 0 | &fontGroup, |
1474 | 0 | familyList))) |
1475 | 0 | return false; // no need to continue |
1476 | 0 | |
1477 | 0 | return true; // true means continue |
1478 | 0 | } |
1479 | | |
1480 | | static void |
1481 | | AppendFallbacks(nsTArray<FontFamilyName>& aNames, |
1482 | | const nsTArray<nsCString>& aFallbacks) |
1483 | 0 | { |
1484 | 0 | for (const nsCString& fallback : aFallbacks) { |
1485 | 0 | aNames.AppendElement(FontFamilyName(fallback, |
1486 | 0 | eUnquotedName)); |
1487 | 0 | } |
1488 | 0 | } |
1489 | | |
1490 | | // insert math fallback families just before the first generic or at the end |
1491 | | // when no generic present |
1492 | | static void |
1493 | | InsertMathFallbacks(FontFamilyList& aFamilyList, |
1494 | | nsTArray<nsCString>& aFallbacks) |
1495 | 0 | { |
1496 | 0 | nsTArray<FontFamilyName> mergedList; |
1497 | 0 |
|
1498 | 0 | bool inserted = false; |
1499 | 0 | for (const FontFamilyName& name : aFamilyList.GetFontlist()->mNames) { |
1500 | 0 | if (!inserted && name.IsGeneric()) { |
1501 | 0 | inserted = true; |
1502 | 0 | AppendFallbacks(mergedList, aFallbacks); |
1503 | 0 | } |
1504 | 0 | mergedList.AppendElement(name); |
1505 | 0 | } |
1506 | 0 |
|
1507 | 0 | if (!inserted) { |
1508 | 0 | AppendFallbacks(mergedList, aFallbacks); |
1509 | 0 | } |
1510 | 0 | aFamilyList.SetFontlist(std::move(mergedList)); |
1511 | 0 | } |
1512 | | |
1513 | | nsresult |
1514 | | nsMathMLChar::StretchInternal(nsIFrame* aForFrame, |
1515 | | DrawTarget* aDrawTarget, |
1516 | | float aFontSizeInflation, |
1517 | | nsStretchDirection& aStretchDirection, |
1518 | | const nsBoundingMetrics& aContainerSize, |
1519 | | nsBoundingMetrics& aDesiredStretchSize, |
1520 | | uint32_t aStretchHint, |
1521 | | // These are currently only used when |
1522 | | // aStretchHint & NS_STRETCH_MAXWIDTH: |
1523 | | float aMaxSize, |
1524 | | bool aMaxSizeIsAbsolute) |
1525 | 0 | { |
1526 | 0 | nsPresContext* presContext = aForFrame->PresContext(); |
1527 | 0 |
|
1528 | 0 | // if we have been called before, and we didn't actually stretch, our |
1529 | 0 | // direction may have been set to NS_STRETCH_DIRECTION_UNSUPPORTED. |
1530 | 0 | // So first set our direction back to its instrinsic value |
1531 | 0 | nsStretchDirection direction = nsMathMLOperators::GetStretchyDirection(mData); |
1532 | 0 |
|
1533 | 0 | // Set default font and get the default bounding metrics |
1534 | 0 | // mComputedStyle is a leaf context used only when stretching happens. |
1535 | 0 | // For the base size, the default font should come from the parent context |
1536 | 0 | nsFont font = aForFrame->StyleFont()->mFont; |
1537 | 0 | NormalizeDefaultFont(font, aFontSizeInflation); |
1538 | 0 |
|
1539 | 0 | const nsStyleFont* styleFont = mComputedStyle->StyleFont(); |
1540 | 0 | nsFontMetrics::Params params; |
1541 | 0 | params.language = styleFont->mLanguage; |
1542 | 0 | params.explicitLanguage = styleFont->mExplicitLanguage; |
1543 | 0 | params.userFontSet = presContext->GetUserFontSet(); |
1544 | 0 | params.textPerf = presContext->GetTextPerfMetrics(); |
1545 | 0 | RefPtr<nsFontMetrics> fm = |
1546 | 0 | presContext->DeviceContext()->GetMetricsFor(font, params); |
1547 | 0 | uint32_t len = uint32_t(mData.Length()); |
1548 | 0 | mGlyphs[0] = fm->GetThebesFontGroup()-> |
1549 | 0 | MakeTextRun(static_cast<const char16_t*>(mData.get()), len, aDrawTarget, |
1550 | 0 | presContext->AppUnitsPerDevPixel(), |
1551 | 0 | gfx::ShapedTextFlags(), nsTextFrameUtils::Flags(), |
1552 | 0 | presContext->MissingFontRecorder()); |
1553 | 0 | aDesiredStretchSize = MeasureTextRun(aDrawTarget, mGlyphs[0].get()); |
1554 | 0 |
|
1555 | 0 | bool maxWidth = (NS_STRETCH_MAXWIDTH & aStretchHint) != 0; |
1556 | 0 | if (!maxWidth) { |
1557 | 0 | mUnscaledAscent = aDesiredStretchSize.ascent; |
1558 | 0 | } |
1559 | 0 |
|
1560 | 0 | ////////////////////////////////////////////////////////////////////////////// |
1561 | 0 | // 1. Check the common situations where stretching is not actually needed |
1562 | 0 | ////////////////////////////////////////////////////////////////////////////// |
1563 | 0 |
|
1564 | 0 | // quick return if there is nothing special about this char |
1565 | 0 | if ((aStretchDirection != direction && |
1566 | 0 | aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT) || |
1567 | 0 | (aStretchHint & ~NS_STRETCH_MAXWIDTH) == NS_STRETCH_NONE) { |
1568 | 0 | mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED; |
1569 | 0 | return NS_OK; |
1570 | 0 | } |
1571 | 0 | |
1572 | 0 | // if no specified direction, attempt to stretch in our preferred direction |
1573 | 0 | if (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT) { |
1574 | 0 | aStretchDirection = direction; |
1575 | 0 | } |
1576 | 0 |
|
1577 | 0 | // see if this is a particular largeop or largeopOnly request |
1578 | 0 | bool largeop = (NS_STRETCH_LARGEOP & aStretchHint) != 0; |
1579 | 0 | bool stretchy = (NS_STRETCH_VARIABLE_MASK & aStretchHint) != 0; |
1580 | 0 | bool largeopOnly = largeop && !stretchy; |
1581 | 0 |
|
1582 | 0 | bool isVertical = (direction == NS_STRETCH_DIRECTION_VERTICAL); |
1583 | 0 |
|
1584 | 0 | nscoord targetSize = |
1585 | 0 | isVertical ? aContainerSize.ascent + aContainerSize.descent |
1586 | 0 | : aContainerSize.rightBearing - aContainerSize.leftBearing; |
1587 | 0 |
|
1588 | 0 | if (maxWidth) { |
1589 | 0 | // See if it is only necessary to consider glyphs up to some maximum size. |
1590 | 0 | // Set the current height to the maximum size, and set aStretchHint to |
1591 | 0 | // NS_STRETCH_SMALLER if the size is variable, so that only smaller sizes |
1592 | 0 | // are considered. targetSize from GetMaxWidth() is 0. |
1593 | 0 | if (stretchy) { |
1594 | 0 | // variable size stretch - consider all sizes < maxsize |
1595 | 0 | aStretchHint = |
1596 | 0 | (aStretchHint & ~NS_STRETCH_VARIABLE_MASK) | NS_STRETCH_SMALLER; |
1597 | 0 | } |
1598 | 0 |
|
1599 | 0 | // Use NS_MATHML_DELIMITER_FACTOR to allow some slightly larger glyphs as |
1600 | 0 | // maxsize is not enforced exactly. |
1601 | 0 | if (aMaxSize == NS_MATHML_OPERATOR_SIZE_INFINITY) { |
1602 | 0 | aDesiredStretchSize.ascent = nscoord_MAX; |
1603 | 0 | aDesiredStretchSize.descent = 0; |
1604 | 0 | } |
1605 | 0 | else { |
1606 | 0 | nscoord height = aDesiredStretchSize.ascent + aDesiredStretchSize.descent; |
1607 | 0 | if (height == 0) { |
1608 | 0 | if (aMaxSizeIsAbsolute) { |
1609 | 0 | aDesiredStretchSize.ascent = |
1610 | 0 | NSToCoordRound(aMaxSize / NS_MATHML_DELIMITER_FACTOR); |
1611 | 0 | aDesiredStretchSize.descent = 0; |
1612 | 0 | } |
1613 | 0 | // else: leave height as 0 |
1614 | 0 | } |
1615 | 0 | else { |
1616 | 0 | float scale = aMaxSizeIsAbsolute ? aMaxSize / height : aMaxSize; |
1617 | 0 | scale /= NS_MATHML_DELIMITER_FACTOR; |
1618 | 0 | aDesiredStretchSize.ascent = |
1619 | 0 | NSToCoordRound(scale * aDesiredStretchSize.ascent); |
1620 | 0 | aDesiredStretchSize.descent = |
1621 | 0 | NSToCoordRound(scale * aDesiredStretchSize.descent); |
1622 | 0 | } |
1623 | 0 | } |
1624 | 0 | } |
1625 | 0 |
|
1626 | 0 | nsBoundingMetrics initialSize = aDesiredStretchSize; |
1627 | 0 | nscoord charSize = |
1628 | 0 | isVertical ? initialSize.ascent + initialSize.descent |
1629 | 0 | : initialSize.rightBearing - initialSize.leftBearing; |
1630 | 0 |
|
1631 | 0 | bool done = false; |
1632 | 0 |
|
1633 | 0 | if (!maxWidth && !largeop) { |
1634 | 0 | // Doing Stretch() not GetMaxWidth(), |
1635 | 0 | // and not a largeop in display mode; we're done if size fits |
1636 | 0 | if ((targetSize <= 0) || |
1637 | 0 | ((isVertical && charSize >= targetSize) || |
1638 | 0 | IsSizeOK(charSize, targetSize, aStretchHint))) |
1639 | 0 | done = true; |
1640 | 0 | } |
1641 | 0 |
|
1642 | 0 | ////////////////////////////////////////////////////////////////////////////// |
1643 | 0 | // 2/3. Search for a glyph or set of part glyphs of appropriate size |
1644 | 0 | ////////////////////////////////////////////////////////////////////////////// |
1645 | 0 |
|
1646 | 0 | bool glyphFound = false; |
1647 | 0 |
|
1648 | 0 | if (!done) { // normal case |
1649 | 0 | // Use the css font-family but add preferred fallback fonts. |
1650 | 0 | font = mComputedStyle->StyleFont()->mFont; |
1651 | 0 | NormalizeDefaultFont(font, aFontSizeInflation); |
1652 | 0 |
|
1653 | 0 | // really shouldn't be doing things this way but for now |
1654 | 0 | // insert fallbacks into the list |
1655 | 0 | AutoTArray<nsCString, 16> mathFallbacks; |
1656 | 0 | gfxFontUtils::GetPrefsFontList("font.name.serif.x-math", mathFallbacks); |
1657 | 0 | gfxFontUtils::AppendPrefsFontList("font.name-list.serif.x-math", |
1658 | 0 | mathFallbacks); |
1659 | 0 | InsertMathFallbacks(font.fontlist, mathFallbacks); |
1660 | 0 |
|
1661 | 0 |
|
1662 | | #ifdef NOISY_SEARCH |
1663 | | nsAutoString fontlistStr; |
1664 | | font.fontlist.ToString(fontlistStr, false, true); |
1665 | | printf("Searching in "%s" for a glyph of appropriate size for: 0x%04X:%c\n", |
1666 | | NS_ConvertUTF16toUTF8(fontlistStr).get(), mData[0], mData[0]&0x00FF); |
1667 | | #endif |
1668 | | StretchEnumContext enumData(this, presContext, aDrawTarget, |
1669 | 0 | aFontSizeInflation, |
1670 | 0 | aStretchDirection, targetSize, aStretchHint, |
1671 | 0 | aDesiredStretchSize, font.fontlist, glyphFound); |
1672 | 0 | enumData.mTryParts = !largeopOnly; |
1673 | 0 |
|
1674 | 0 | const nsTArray<FontFamilyName>& fontlist = font.fontlist.GetFontlist()->mNames; |
1675 | 0 | uint32_t i, num = fontlist.Length(); |
1676 | 0 | bool next = true; |
1677 | 0 | for (i = 0; i < num && next; i++) { |
1678 | 0 | const FontFamilyName& name = fontlist[i]; |
1679 | 0 | next = StretchEnumContext::EnumCallback(name, name.IsGeneric(), &enumData); |
1680 | 0 | } |
1681 | 0 | } |
1682 | 0 |
|
1683 | 0 | if (!maxWidth) { |
1684 | 0 | // Now, we know how we are going to draw the char. Update the member |
1685 | 0 | // variables accordingly. |
1686 | 0 | mUnscaledAscent = aDesiredStretchSize.ascent; |
1687 | 0 | } |
1688 | 0 |
|
1689 | 0 | if (glyphFound) { |
1690 | 0 | return NS_OK; |
1691 | 0 | } |
1692 | 0 | |
1693 | 0 | // We did not find a size variant or a glyph assembly to stretch this |
1694 | 0 | // operator. Verify whether a font with an OpenType MATH table is available |
1695 | 0 | // and record missing math script otherwise. |
1696 | 0 | gfxMissingFontRecorder* MFR = presContext->MissingFontRecorder(); |
1697 | 0 | if (MFR && !fm->GetThebesFontGroup()->GetFirstMathFont()) { |
1698 | 0 | MFR->RecordScript(unicode::Script::MATHEMATICAL_NOTATION); |
1699 | 0 | } |
1700 | 0 |
|
1701 | 0 | // If the scale_stretchy_operators option is disabled, we are done. |
1702 | 0 | if (!Preferences::GetBool("mathml.scale_stretchy_operators.enabled", true)) { |
1703 | 0 | return NS_OK; |
1704 | 0 | } |
1705 | 0 | |
1706 | 0 | // stretchy character |
1707 | 0 | if (stretchy) { |
1708 | 0 | if (isVertical) { |
1709 | 0 | float scale = |
1710 | 0 | std::min(kMaxScaleFactor, float(aContainerSize.ascent + aContainerSize.descent) / |
1711 | 0 | (aDesiredStretchSize.ascent + aDesiredStretchSize.descent)); |
1712 | 0 | if (!largeop || scale > 1.0) { |
1713 | 0 | // make the character match the desired height. |
1714 | 0 | if (!maxWidth) { |
1715 | 0 | mScaleY *= scale; |
1716 | 0 | } |
1717 | 0 | aDesiredStretchSize.ascent *= scale; |
1718 | 0 | aDesiredStretchSize.descent *= scale; |
1719 | 0 | } |
1720 | 0 | } else { |
1721 | 0 | float scale = |
1722 | 0 | std::min(kMaxScaleFactor, float(aContainerSize.rightBearing - aContainerSize.leftBearing) / |
1723 | 0 | (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing)); |
1724 | 0 | if (!largeop || scale > 1.0) { |
1725 | 0 | // make the character match the desired width. |
1726 | 0 | if (!maxWidth) { |
1727 | 0 | mScaleX *= scale; |
1728 | 0 | } |
1729 | 0 | aDesiredStretchSize.leftBearing *= scale; |
1730 | 0 | aDesiredStretchSize.rightBearing *= scale; |
1731 | 0 | aDesiredStretchSize.width *= scale; |
1732 | 0 | } |
1733 | 0 | } |
1734 | 0 | } |
1735 | 0 |
|
1736 | 0 | // We do not have a char variant for this largeop in display mode, so we |
1737 | 0 | // apply a scale transform to the base char. |
1738 | 0 | if (largeop) { |
1739 | 0 | float scale; |
1740 | 0 | float largeopFactor = kLargeOpFactor; |
1741 | 0 |
|
1742 | 0 | // increase the width if it is not largeopFactor times larger |
1743 | 0 | // than the initial one. |
1744 | 0 | if ((aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing) < |
1745 | 0 | largeopFactor * (initialSize.rightBearing - initialSize.leftBearing)) { |
1746 | 0 | scale = (largeopFactor * |
1747 | 0 | (initialSize.rightBearing - initialSize.leftBearing)) / |
1748 | 0 | (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing); |
1749 | 0 | if (!maxWidth) { |
1750 | 0 | mScaleX *= scale; |
1751 | 0 | } |
1752 | 0 | aDesiredStretchSize.leftBearing *= scale; |
1753 | 0 | aDesiredStretchSize.rightBearing *= scale; |
1754 | 0 | aDesiredStretchSize.width *= scale; |
1755 | 0 | } |
1756 | 0 |
|
1757 | 0 | // increase the height if it is not largeopFactor times larger |
1758 | 0 | // than the initial one. |
1759 | 0 | if (NS_STRETCH_INTEGRAL & aStretchHint) { |
1760 | 0 | // integrals are drawn taller |
1761 | 0 | largeopFactor = kIntegralFactor; |
1762 | 0 | } |
1763 | 0 | if ((aDesiredStretchSize.ascent + aDesiredStretchSize.descent) < |
1764 | 0 | largeopFactor * (initialSize.ascent + initialSize.descent)) { |
1765 | 0 | scale = (largeopFactor * |
1766 | 0 | (initialSize.ascent + initialSize.descent)) / |
1767 | 0 | (aDesiredStretchSize.ascent + aDesiredStretchSize.descent); |
1768 | 0 | if (!maxWidth) { |
1769 | 0 | mScaleY *= scale; |
1770 | 0 | } |
1771 | 0 | aDesiredStretchSize.ascent *= scale; |
1772 | 0 | aDesiredStretchSize.descent *= scale; |
1773 | 0 | } |
1774 | 0 | } |
1775 | 0 |
|
1776 | 0 | return NS_OK; |
1777 | 0 | } |
1778 | | |
1779 | | nsresult |
1780 | | nsMathMLChar::Stretch(nsIFrame* aForFrame, |
1781 | | DrawTarget* aDrawTarget, |
1782 | | float aFontSizeInflation, |
1783 | | nsStretchDirection aStretchDirection, |
1784 | | const nsBoundingMetrics& aContainerSize, |
1785 | | nsBoundingMetrics& aDesiredStretchSize, |
1786 | | uint32_t aStretchHint, |
1787 | | bool aRTL) |
1788 | 0 | { |
1789 | 0 | NS_ASSERTION(!(aStretchHint & |
1790 | 0 | ~(NS_STRETCH_VARIABLE_MASK | NS_STRETCH_LARGEOP | |
1791 | 0 | NS_STRETCH_INTEGRAL)), |
1792 | 0 | "Unexpected stretch flags"); |
1793 | 0 |
|
1794 | 0 | mDraw = DRAW_NORMAL; |
1795 | 0 | mMirrored = aRTL && nsMathMLOperators::IsMirrorableOperator(mData); |
1796 | 0 | mScaleY = mScaleX = 1.0; |
1797 | 0 | mDirection = aStretchDirection; |
1798 | 0 | nsresult rv = |
1799 | 0 | StretchInternal(aForFrame, aDrawTarget, aFontSizeInflation, mDirection, |
1800 | 0 | aContainerSize, aDesiredStretchSize, aStretchHint); |
1801 | 0 |
|
1802 | 0 | // Record the metrics |
1803 | 0 | mBoundingMetrics = aDesiredStretchSize; |
1804 | 0 |
|
1805 | 0 | return rv; |
1806 | 0 | } |
1807 | | |
1808 | | // What happens here is that the StretchInternal algorithm is used but |
1809 | | // modified by passing the NS_STRETCH_MAXWIDTH stretch hint. That causes |
1810 | | // StretchInternal to return horizontal bounding metrics that are the maximum |
1811 | | // that might be returned from a Stretch. |
1812 | | // |
1813 | | // In order to avoid considering widths of some characters in fonts that will |
1814 | | // not be used for any stretch size, StretchInternal sets the initial height |
1815 | | // to infinity and looks for any characters smaller than this height. When a |
1816 | | // character built from parts is considered, (it will be used by Stretch for |
1817 | | // any characters greater than its minimum size, so) the height is set to its |
1818 | | // minimum size, so that only widths of smaller subsequent characters are |
1819 | | // considered. |
1820 | | nscoord |
1821 | | nsMathMLChar::GetMaxWidth(nsIFrame* aForFrame, |
1822 | | DrawTarget* aDrawTarget, |
1823 | | float aFontSizeInflation, |
1824 | | uint32_t aStretchHint) |
1825 | 0 | { |
1826 | 0 | nsBoundingMetrics bm; |
1827 | 0 | nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL; |
1828 | 0 | const nsBoundingMetrics container; // zero target size |
1829 | 0 |
|
1830 | 0 | StretchInternal(aForFrame, aDrawTarget, aFontSizeInflation, |
1831 | 0 | direction, container, bm, aStretchHint | NS_STRETCH_MAXWIDTH); |
1832 | 0 |
|
1833 | 0 | return std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing); |
1834 | 0 | } |
1835 | | |
1836 | | class nsDisplayMathMLSelectionRect final : public nsDisplayItem { |
1837 | | public: |
1838 | | nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder, |
1839 | | nsIFrame* aFrame, const nsRect& aRect) |
1840 | 0 | : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { |
1841 | 0 | MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect); |
1842 | 0 | } |
1843 | | #ifdef NS_BUILD_REFCNT_LOGGING |
1844 | | virtual ~nsDisplayMathMLSelectionRect() { |
1845 | | MOZ_COUNT_DTOR(nsDisplayMathMLSelectionRect); |
1846 | | } |
1847 | | #endif |
1848 | | |
1849 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
1850 | | gfxContext* aCtx) override; |
1851 | | NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT) |
1852 | | private: |
1853 | | nsRect mRect; |
1854 | | }; |
1855 | | |
1856 | | void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder, |
1857 | | gfxContext* aCtx) |
1858 | 0 | { |
1859 | 0 | DrawTarget* drawTarget = aCtx->GetDrawTarget(); |
1860 | 0 | Rect rect = NSRectToSnappedRect(mRect + ToReferenceFrame(), |
1861 | 0 | mFrame->PresContext()->AppUnitsPerDevPixel(), |
1862 | 0 | *drawTarget); |
1863 | 0 | // get color to use for selection from the look&feel object |
1864 | 0 | nscolor bgColor = |
1865 | 0 | LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground, |
1866 | 0 | NS_RGB(0, 0, 0)); |
1867 | 0 | drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(bgColor))); |
1868 | 0 | } |
1869 | | |
1870 | | class nsDisplayMathMLCharForeground final : public nsDisplayItem { |
1871 | | public: |
1872 | | nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder, |
1873 | | nsIFrame* aFrame, nsMathMLChar* aChar, |
1874 | | uint32_t aIndex, bool aIsSelected) |
1875 | | : nsDisplayItem(aBuilder, aFrame), mChar(aChar), |
1876 | 0 | mIndex(aIndex), mIsSelected(aIsSelected) { |
1877 | 0 | MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground); |
1878 | 0 | } |
1879 | | #ifdef NS_BUILD_REFCNT_LOGGING |
1880 | | virtual ~nsDisplayMathMLCharForeground() { |
1881 | | MOZ_COUNT_DTOR(nsDisplayMathMLCharForeground); |
1882 | | } |
1883 | | #endif |
1884 | | |
1885 | | virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, |
1886 | | bool* aSnap) const override |
1887 | 0 | { |
1888 | 0 | *aSnap = false; |
1889 | 0 | nsRect rect; |
1890 | 0 | mChar->GetRect(rect); |
1891 | 0 | nsPoint offset = ToReferenceFrame() + rect.TopLeft(); |
1892 | 0 | nsBoundingMetrics bm; |
1893 | 0 | mChar->GetBoundingMetrics(bm); |
1894 | 0 | nsRect temp(offset.x + bm.leftBearing, offset.y, |
1895 | 0 | bm.rightBearing - bm.leftBearing, bm.ascent + bm.descent); |
1896 | 0 | // Bug 748220 |
1897 | 0 | temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); |
1898 | 0 | return temp; |
1899 | 0 | } |
1900 | | |
1901 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
1902 | | gfxContext* aCtx) override |
1903 | 0 | { |
1904 | 0 | mChar->PaintForeground(mFrame, *aCtx, ToReferenceFrame(), mIsSelected); |
1905 | 0 | } |
1906 | | |
1907 | | NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND) |
1908 | | |
1909 | | virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override |
1910 | 0 | { |
1911 | 0 | bool snap; |
1912 | 0 | return GetBounds(aBuilder, &snap); |
1913 | 0 | } |
1914 | | |
1915 | 0 | virtual uint32_t GetPerFrameKey() const override { |
1916 | 0 | return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); |
1917 | 0 | } |
1918 | | |
1919 | | private: |
1920 | | nsMathMLChar* mChar; |
1921 | | uint32_t mIndex; |
1922 | | bool mIsSelected; |
1923 | | }; |
1924 | | |
1925 | | #ifdef DEBUG |
1926 | | class nsDisplayMathMLCharDebug final : public nsDisplayItem { |
1927 | | public: |
1928 | | nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder, |
1929 | | nsIFrame* aFrame, const nsRect& aRect) |
1930 | | : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { |
1931 | | MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug); |
1932 | | } |
1933 | | #ifdef NS_BUILD_REFCNT_LOGGING |
1934 | | virtual ~nsDisplayMathMLCharDebug() { |
1935 | | MOZ_COUNT_DTOR(nsDisplayMathMLCharDebug); |
1936 | | } |
1937 | | #endif |
1938 | | |
1939 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
1940 | | gfxContext* aCtx) override; |
1941 | | NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG) |
1942 | | |
1943 | | private: |
1944 | | nsRect mRect; |
1945 | | }; |
1946 | | |
1947 | | void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder, |
1948 | | gfxContext* aCtx) |
1949 | | { |
1950 | | // for visual debug |
1951 | | Sides skipSides; |
1952 | | nsPresContext* presContext = mFrame->PresContext(); |
1953 | | ComputedStyle* computedStyle = mFrame->Style(); |
1954 | | nsRect rect = mRect + ToReferenceFrame(); |
1955 | | |
1956 | | PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages() |
1957 | | ? PaintBorderFlags::SYNC_DECODE_IMAGES |
1958 | | : PaintBorderFlags(); |
1959 | | |
1960 | | // Since this is used only for debugging, we don't need to worry about |
1961 | | // tracking the ImgDrawResult. |
1962 | | Unused << |
1963 | | nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame, GetPaintRect(), |
1964 | | rect, computedStyle, flags, skipSides); |
1965 | | |
1966 | | nsCSSRendering::PaintOutline(presContext, *aCtx, mFrame, |
1967 | | GetPaintRect(), rect, computedStyle); |
1968 | | } |
1969 | | #endif |
1970 | | |
1971 | | |
1972 | | void |
1973 | | nsMathMLChar::Display(nsDisplayListBuilder* aBuilder, |
1974 | | nsIFrame* aForFrame, |
1975 | | const nsDisplayListSet& aLists, |
1976 | | uint32_t aIndex, |
1977 | | const nsRect* aSelectedRect) |
1978 | 0 | { |
1979 | 0 | bool usingParentStyle = false; |
1980 | 0 | ComputedStyle* computedStyle = mComputedStyle; |
1981 | 0 |
|
1982 | 0 | if (mDraw == DRAW_NORMAL) { |
1983 | 0 | // normal drawing if there is nothing special about this char |
1984 | 0 | // Use our parent element's style |
1985 | 0 | usingParentStyle = true; |
1986 | 0 | computedStyle = aForFrame->Style(); |
1987 | 0 | } |
1988 | 0 |
|
1989 | 0 | if (!computedStyle->StyleVisibility()->IsVisible()) |
1990 | 0 | return; |
1991 | 0 | |
1992 | 0 | // if the leaf computed style that we use for stretchy chars has a background |
1993 | 0 | // color we use it -- this feature is mostly used for testing and debugging |
1994 | 0 | // purposes. Normally, users will set the background on the container frame. |
1995 | 0 | // paint the selection background -- beware MathML frames overlap a lot |
1996 | 0 | if (aSelectedRect && !aSelectedRect->IsEmpty()) { |
1997 | 0 | aLists.BorderBackground()->AppendToTop( |
1998 | 0 | MakeDisplayItem<nsDisplayMathMLSelectionRect>(aBuilder, aForFrame, *aSelectedRect)); |
1999 | 0 | } |
2000 | 0 | else if (mRect.width && mRect.height) { |
2001 | 0 | if (!usingParentStyle && |
2002 | 0 | NS_GET_A(computedStyle->StyleBackground()-> |
2003 | 0 | BackgroundColor(computedStyle)) > 0) { |
2004 | 0 | nsDisplayBackgroundImage::AppendBackgroundItemsToTop( |
2005 | 0 | aBuilder, aForFrame, mRect, aLists.BorderBackground(), |
2006 | 0 | /* aAllowWillPaintBorderOptimization */ true, computedStyle); |
2007 | 0 | } |
2008 | 0 | //else |
2009 | 0 | // our container frame will take care of painting its background |
2010 | 0 |
|
2011 | | #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) |
2012 | | // for visual debug |
2013 | | aLists.BorderBackground()->AppendToTop( |
2014 | | MakeDisplayItem<nsDisplayMathMLCharDebug>(aBuilder, aForFrame, mRect)); |
2015 | | #endif |
2016 | | } |
2017 | 0 | aLists.Content()->AppendToTop( |
2018 | 0 | MakeDisplayItem<nsDisplayMathMLCharForeground>(aBuilder, aForFrame, this, |
2019 | 0 | aIndex, |
2020 | 0 | aSelectedRect && |
2021 | 0 | !aSelectedRect->IsEmpty())); |
2022 | 0 | } |
2023 | | |
2024 | | void |
2025 | | nsMathMLChar::ApplyTransforms(gfxContext* aThebesContext, |
2026 | | int32_t aAppUnitsPerGfxUnit, |
2027 | | nsRect &r) |
2028 | 0 | { |
2029 | 0 | // apply the transforms |
2030 | 0 | if (mMirrored) { |
2031 | 0 | nsPoint pt = r.TopRight(); |
2032 | 0 | gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit), |
2033 | 0 | NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit)); |
2034 | 0 | aThebesContext->SetMatrixDouble( |
2035 | 0 | aThebesContext->CurrentMatrixDouble().PreTranslate(devPixelOffset). |
2036 | 0 | PreScale(-mScaleX, mScaleY)); |
2037 | 0 | } else { |
2038 | 0 | nsPoint pt = r.TopLeft(); |
2039 | 0 | gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit), |
2040 | 0 | NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit)); |
2041 | 0 | aThebesContext->SetMatrixDouble( |
2042 | 0 | aThebesContext->CurrentMatrixDouble().PreTranslate(devPixelOffset). |
2043 | 0 | PreScale(mScaleX, mScaleY)); |
2044 | 0 | } |
2045 | 0 |
|
2046 | 0 | // update the bounding rectangle. |
2047 | 0 | r.x = r.y = 0; |
2048 | 0 | r.width /= mScaleX; |
2049 | 0 | r.height /= mScaleY; |
2050 | 0 | } |
2051 | | |
2052 | | void |
2053 | | nsMathMLChar::PaintForeground(nsIFrame* aForFrame, |
2054 | | gfxContext& aRenderingContext, |
2055 | | nsPoint aPt, |
2056 | | bool aIsSelected) |
2057 | 0 | { |
2058 | 0 | ComputedStyle* computedStyle = mComputedStyle; |
2059 | 0 | nsPresContext* presContext = aForFrame->PresContext(); |
2060 | 0 |
|
2061 | 0 | if (mDraw == DRAW_NORMAL) { |
2062 | 0 | // normal drawing if there is nothing special about this char |
2063 | 0 | // Use our parent element's style |
2064 | 0 | computedStyle = aForFrame->Style(); |
2065 | 0 | } |
2066 | 0 |
|
2067 | 0 | // Set color ... |
2068 | 0 | nscolor fgColor = computedStyle-> |
2069 | 0 | GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor); |
2070 | 0 | if (aIsSelected) { |
2071 | 0 | // get color to use for selection from the look&feel object |
2072 | 0 | fgColor = LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground, |
2073 | 0 | fgColor); |
2074 | 0 | } |
2075 | 0 | aRenderingContext.SetColor(Color::FromABGR(fgColor)); |
2076 | 0 | aRenderingContext.Save(); |
2077 | 0 | nsRect r = mRect + aPt; |
2078 | 0 | ApplyTransforms(&aRenderingContext, aForFrame->PresContext()->AppUnitsPerDevPixel(), r); |
2079 | 0 |
|
2080 | 0 | switch(mDraw) |
2081 | 0 | { |
2082 | 0 | case DRAW_NORMAL: |
2083 | 0 | case DRAW_VARIANT: |
2084 | 0 | // draw a single glyph (base size or size variant) |
2085 | 0 | // XXXfredw verify if mGlyphs[0] is non-null to workaround bug 973322. |
2086 | 0 | if (mGlyphs[0]) { |
2087 | 0 | mGlyphs[0]->Draw(Range(mGlyphs[0].get()), |
2088 | 0 | gfx::Point(0.0, mUnscaledAscent), |
2089 | 0 | gfxTextRun::DrawParams(&aRenderingContext)); |
2090 | 0 | } |
2091 | 0 | break; |
2092 | 0 | case DRAW_PARTS: { |
2093 | 0 | // paint by parts |
2094 | 0 | if (NS_STRETCH_DIRECTION_VERTICAL == mDirection) |
2095 | 0 | PaintVertically(presContext, &aRenderingContext, r, fgColor); |
2096 | 0 | else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection) |
2097 | 0 | PaintHorizontally(presContext, &aRenderingContext, r, fgColor); |
2098 | 0 | break; |
2099 | 0 | } |
2100 | 0 | default: |
2101 | 0 | MOZ_ASSERT_UNREACHABLE("Unknown drawing method"); |
2102 | 0 | break; |
2103 | 0 | } |
2104 | 0 |
|
2105 | 0 | aRenderingContext.Restore(); |
2106 | 0 | } |
2107 | | |
2108 | | /* ============================================================================= |
2109 | | Helper routines that actually do the job of painting the char by parts |
2110 | | */ |
2111 | | |
2112 | | class AutoPushClipRect { |
2113 | | gfxContext* mThebesContext; |
2114 | | public: |
2115 | | AutoPushClipRect(gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit, |
2116 | | const nsRect& aRect) |
2117 | 0 | : mThebesContext(aThebesContext) { |
2118 | 0 | mThebesContext->Save(); |
2119 | 0 | mThebesContext->NewPath(); |
2120 | 0 | gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit); |
2121 | 0 | mThebesContext->SnappedRectangle(clip); |
2122 | 0 | mThebesContext->Clip(); |
2123 | 0 | } |
2124 | 0 | ~AutoPushClipRect() { |
2125 | 0 | mThebesContext->Restore(); |
2126 | 0 | } |
2127 | | }; |
2128 | | |
2129 | | static nsPoint |
2130 | | SnapToDevPixels(const gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit, |
2131 | | const nsPoint& aPt) |
2132 | 0 | { |
2133 | 0 | gfxPoint pt(NSAppUnitsToFloatPixels(aPt.x, aAppUnitsPerGfxUnit), |
2134 | 0 | NSAppUnitsToFloatPixels(aPt.y, aAppUnitsPerGfxUnit)); |
2135 | 0 | pt = aThebesContext->UserToDevice(pt); |
2136 | 0 | pt.Round(); |
2137 | 0 | pt = aThebesContext->DeviceToUser(pt); |
2138 | 0 | return nsPoint(NSFloatPixelsToAppUnits(pt.x, aAppUnitsPerGfxUnit), |
2139 | 0 | NSFloatPixelsToAppUnits(pt.y, aAppUnitsPerGfxUnit)); |
2140 | 0 | } |
2141 | | |
2142 | | static void |
2143 | | PaintRule(DrawTarget& aDrawTarget, |
2144 | | int32_t aAppUnitsPerGfxUnit, |
2145 | | nsRect& aRect, |
2146 | | nscolor aColor) |
2147 | 0 | { |
2148 | 0 | Rect rect = NSRectToSnappedRect(aRect, aAppUnitsPerGfxUnit, aDrawTarget); |
2149 | 0 | ColorPattern color(ToDeviceColor(aColor)); |
2150 | 0 | aDrawTarget.FillRect(rect, color); |
2151 | 0 | } |
2152 | | |
2153 | | // paint a stretchy char by assembling glyphs vertically |
2154 | | nsresult |
2155 | | nsMathMLChar::PaintVertically(nsPresContext* aPresContext, |
2156 | | gfxContext* aThebesContext, |
2157 | | nsRect& aRect, |
2158 | | nscolor aColor) |
2159 | 0 | { |
2160 | 0 | DrawTarget& aDrawTarget = *aThebesContext->GetDrawTarget(); |
2161 | 0 |
|
2162 | 0 | // Get the device pixel size in the vertical direction. |
2163 | 0 | // (This makes no effort to optimize for non-translation transformations.) |
2164 | 0 | nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); |
2165 | 0 |
|
2166 | 0 | // get metrics data to be re-used later |
2167 | 0 | int32_t i = 0; |
2168 | 0 | nscoord dx = aRect.x; |
2169 | 0 | nscoord offset[3], start[3], end[3]; |
2170 | 0 | for (i = 0; i <= 2; ++i) { |
2171 | 0 | const nsBoundingMetrics& bm = mBmData[i]; |
2172 | 0 | nscoord dy; |
2173 | 0 | if (0 == i) { // top |
2174 | 0 | dy = aRect.y + bm.ascent; |
2175 | 0 | } |
2176 | 0 | else if (2 == i) { // bottom |
2177 | 0 | dy = aRect.y + aRect.height - bm.descent; |
2178 | 0 | } |
2179 | 0 | else { // middle |
2180 | 0 | dy = aRect.y + bm.ascent + (aRect.height - (bm.ascent + bm.descent))/2; |
2181 | 0 | } |
2182 | 0 | // _cairo_scaled_font_show_glyphs snaps origins to device pixels. |
2183 | 0 | // Do this now so that we can get the other dimensions right. |
2184 | 0 | // (This may not achieve much with non-rectangular transformations.) |
2185 | 0 | dy = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).y; |
2186 | 0 | // abcissa passed to Draw |
2187 | 0 | offset[i] = dy; |
2188 | 0 | // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest |
2189 | 0 | // pixel, so the bm values can include 1 row of faint pixels on each edge. |
2190 | 0 | // Don't rely on this pixel as it can look like a gap. |
2191 | 0 | if (bm.ascent + bm.descent >= 2 * oneDevPixel) { |
2192 | 0 | start[i] = dy - bm.ascent + oneDevPixel; // top join |
2193 | 0 | end[i] = dy + bm.descent - oneDevPixel; // bottom join |
2194 | 0 | } else { |
2195 | 0 | // To avoid overlaps, we don't add one pixel on each side when the part |
2196 | 0 | // is too small. |
2197 | 0 | start[i] = dy - bm.ascent; // top join |
2198 | 0 | end[i] = dy + bm.descent; // bottom join |
2199 | 0 | } |
2200 | 0 | } |
2201 | 0 |
|
2202 | 0 | // If there are overlaps, then join at the mid point |
2203 | 0 | for (i = 0; i < 2; ++i) { |
2204 | 0 | if (end[i] > start[i+1]) { |
2205 | 0 | end[i] = (end[i] + start[i+1]) / 2; |
2206 | 0 | start[i+1] = end[i]; |
2207 | 0 | } |
2208 | 0 | } |
2209 | 0 |
|
2210 | 0 | nsRect unionRect = aRect; |
2211 | 0 | unionRect.x += mBoundingMetrics.leftBearing; |
2212 | 0 | unionRect.width = |
2213 | 0 | mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; |
2214 | 0 | unionRect.Inflate(oneDevPixel, oneDevPixel); |
2215 | 0 |
|
2216 | 0 | gfxTextRun::DrawParams params(aThebesContext); |
2217 | 0 |
|
2218 | 0 | ///////////////////////////////////// |
2219 | 0 | // draw top, middle, bottom |
2220 | 0 | for (i = 0; i <= 2; ++i) { |
2221 | 0 | // glue can be null |
2222 | 0 | if (mGlyphs[i]) { |
2223 | 0 | nscoord dy = offset[i]; |
2224 | 0 | // Draw a glyph in a clipped area so that we don't have hairy chars |
2225 | 0 | // pending outside |
2226 | 0 | nsRect clipRect = unionRect; |
2227 | 0 | // Clip at the join to get a solid edge (without overlap or gap), when |
2228 | 0 | // this won't change the glyph too much. If the glyph is too small to |
2229 | 0 | // clip then we'll overlap rather than have a gap. |
2230 | 0 | nscoord height = mBmData[i].ascent + mBmData[i].descent; |
2231 | 0 | if (height * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) { |
2232 | 0 | if (0 == i) { // top |
2233 | 0 | clipRect.height = end[i] - clipRect.y; |
2234 | 0 | } |
2235 | 0 | else if (2 == i) { // bottom |
2236 | 0 | clipRect.height -= start[i] - clipRect.y; |
2237 | 0 | clipRect.y = start[i]; |
2238 | 0 | } |
2239 | 0 | else { // middle |
2240 | 0 | clipRect.y = start[i]; |
2241 | 0 | clipRect.height = end[i] - start[i]; |
2242 | 0 | } |
2243 | 0 | } |
2244 | 0 | if (!clipRect.IsEmpty()) { |
2245 | 0 | AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); |
2246 | 0 | mGlyphs[i]->Draw(Range(mGlyphs[i].get()), gfx::Point(dx, dy), params); |
2247 | 0 | } |
2248 | 0 | } |
2249 | 0 | } |
2250 | 0 |
|
2251 | 0 | /////////////// |
2252 | 0 | // fill the gap between top and middle, and between middle and bottom. |
2253 | 0 | if (!mGlyphs[3]) { // null glue : draw a rule |
2254 | 0 | // figure out the dimensions of the rule to be drawn : |
2255 | 0 | // set lbearing to rightmost lbearing among the two current successive |
2256 | 0 | // parts. |
2257 | 0 | // set rbearing to leftmost rbearing among the two current successive parts. |
2258 | 0 | // this not only satisfies the convention used for over/underbraces |
2259 | 0 | // in TeX, but also takes care of broken fonts like the stretchy integral |
2260 | 0 | // in Symbol for small font sizes in unix. |
2261 | 0 | nscoord lbearing, rbearing; |
2262 | 0 | int32_t first = 0, last = 1; |
2263 | 0 | while (last <= 2) { |
2264 | 0 | if (mGlyphs[last]) { |
2265 | 0 | lbearing = mBmData[last].leftBearing; |
2266 | 0 | rbearing = mBmData[last].rightBearing; |
2267 | 0 | if (mGlyphs[first]) { |
2268 | 0 | if (lbearing < mBmData[first].leftBearing) |
2269 | 0 | lbearing = mBmData[first].leftBearing; |
2270 | 0 | if (rbearing > mBmData[first].rightBearing) |
2271 | 0 | rbearing = mBmData[first].rightBearing; |
2272 | 0 | } |
2273 | 0 | } |
2274 | 0 | else if (mGlyphs[first]) { |
2275 | 0 | lbearing = mBmData[first].leftBearing; |
2276 | 0 | rbearing = mBmData[first].rightBearing; |
2277 | 0 | } |
2278 | 0 | else { |
2279 | 0 | NS_ERROR("Cannot stretch - All parts missing"); |
2280 | 0 | return NS_ERROR_UNEXPECTED; |
2281 | 0 | } |
2282 | 0 | // paint the rule between the parts |
2283 | 0 | nsRect rule(aRect.x + lbearing, end[first], |
2284 | 0 | rbearing - lbearing, start[last] - end[first]); |
2285 | 0 | PaintRule(aDrawTarget, oneDevPixel, rule, aColor); |
2286 | 0 | first = last; |
2287 | 0 | last++; |
2288 | 0 | } |
2289 | 0 | } |
2290 | 0 | else if (mBmData[3].ascent + mBmData[3].descent > 0) { |
2291 | 0 | // glue is present |
2292 | 0 | nsBoundingMetrics& bm = mBmData[3]; |
2293 | 0 | // Ensure the stride for the glue is not reduced to less than one pixel |
2294 | 0 | if (bm.ascent + bm.descent >= 3 * oneDevPixel) { |
2295 | 0 | // To protect against gaps, pretend the glue is smaller than it is, |
2296 | 0 | // in order to trim off ends and thus get a solid edge for the join. |
2297 | 0 | bm.ascent -= oneDevPixel; |
2298 | 0 | bm.descent -= oneDevPixel; |
2299 | 0 | } |
2300 | 0 |
|
2301 | 0 | nsRect clipRect = unionRect; |
2302 | 0 |
|
2303 | 0 | for (i = 0; i < 2; ++i) { |
2304 | 0 | // Make sure not to draw outside the character |
2305 | 0 | nscoord dy = std::max(end[i], aRect.y); |
2306 | 0 | nscoord fillEnd = std::min(start[i+1], aRect.YMost()); |
2307 | 0 | while (dy < fillEnd) { |
2308 | 0 | clipRect.y = dy; |
2309 | 0 | clipRect.height = std::min(bm.ascent + bm.descent, fillEnd - dy); |
2310 | 0 | AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); |
2311 | 0 | dy += bm.ascent; |
2312 | 0 | mGlyphs[3]->Draw(Range(mGlyphs[3].get()), gfx::Point(dx, dy), params); |
2313 | 0 | dy += bm.descent; |
2314 | 0 | } |
2315 | 0 | } |
2316 | 0 | } |
2317 | | #ifdef DEBUG |
2318 | | else { |
2319 | | for (i = 0; i < 2; ++i) { |
2320 | | NS_ASSERTION(end[i] >= start[i+1], |
2321 | | "gap between parts with missing glue glyph"); |
2322 | | } |
2323 | | } |
2324 | | #endif |
2325 | 0 | return NS_OK; |
2326 | 0 | } |
2327 | | |
2328 | | // paint a stretchy char by assembling glyphs horizontally |
2329 | | nsresult |
2330 | | nsMathMLChar::PaintHorizontally(nsPresContext* aPresContext, |
2331 | | gfxContext* aThebesContext, |
2332 | | nsRect& aRect, |
2333 | | nscolor aColor) |
2334 | 0 | { |
2335 | 0 | DrawTarget& aDrawTarget = *aThebesContext->GetDrawTarget(); |
2336 | 0 |
|
2337 | 0 | // Get the device pixel size in the horizontal direction. |
2338 | 0 | // (This makes no effort to optimize for non-translation transformations.) |
2339 | 0 | nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); |
2340 | 0 |
|
2341 | 0 | // get metrics data to be re-used later |
2342 | 0 | int32_t i = 0; |
2343 | 0 | nscoord dy = aRect.y + mBoundingMetrics.ascent; |
2344 | 0 | nscoord offset[3], start[3], end[3]; |
2345 | 0 | for (i = 0; i <= 2; ++i) { |
2346 | 0 | const nsBoundingMetrics& bm = mBmData[i]; |
2347 | 0 | nscoord dx; |
2348 | 0 | if (0 == i) { // left |
2349 | 0 | dx = aRect.x - bm.leftBearing; |
2350 | 0 | } |
2351 | 0 | else if (2 == i) { // right |
2352 | 0 | dx = aRect.x + aRect.width - bm.rightBearing; |
2353 | 0 | } |
2354 | 0 | else { // middle |
2355 | 0 | dx = aRect.x + (aRect.width - bm.width)/2; |
2356 | 0 | } |
2357 | 0 | // _cairo_scaled_font_show_glyphs snaps origins to device pixels. |
2358 | 0 | // Do this now so that we can get the other dimensions right. |
2359 | 0 | // (This may not achieve much with non-rectangular transformations.) |
2360 | 0 | dx = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).x; |
2361 | 0 | // abcissa passed to Draw |
2362 | 0 | offset[i] = dx; |
2363 | 0 | // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest |
2364 | 0 | // pixel, so the bm values can include 1 row of faint pixels on each edge. |
2365 | 0 | // Don't rely on this pixel as it can look like a gap. |
2366 | 0 | if (bm.rightBearing - bm.leftBearing >= 2 * oneDevPixel) { |
2367 | 0 | start[i] = dx + bm.leftBearing + oneDevPixel; // left join |
2368 | 0 | end[i] = dx + bm.rightBearing - oneDevPixel; // right join |
2369 | 0 | } else { |
2370 | 0 | // To avoid overlaps, we don't add one pixel on each side when the part |
2371 | 0 | // is too small. |
2372 | 0 | start[i] = dx + bm.leftBearing; // left join |
2373 | 0 | end[i] = dx + bm.rightBearing; // right join |
2374 | 0 | } |
2375 | 0 | } |
2376 | 0 |
|
2377 | 0 | // If there are overlaps, then join at the mid point |
2378 | 0 | for (i = 0; i < 2; ++i) { |
2379 | 0 | if (end[i] > start[i+1]) { |
2380 | 0 | end[i] = (end[i] + start[i+1]) / 2; |
2381 | 0 | start[i+1] = end[i]; |
2382 | 0 | } |
2383 | 0 | } |
2384 | 0 |
|
2385 | 0 | nsRect unionRect = aRect; |
2386 | 0 | unionRect.Inflate(oneDevPixel, oneDevPixel); |
2387 | 0 |
|
2388 | 0 | gfxTextRun::DrawParams params(aThebesContext); |
2389 | 0 |
|
2390 | 0 | /////////////////////////// |
2391 | 0 | // draw left, middle, right |
2392 | 0 | for (i = 0; i <= 2; ++i) { |
2393 | 0 | // glue can be null |
2394 | 0 | if (mGlyphs[i]) { |
2395 | 0 | nscoord dx = offset[i]; |
2396 | 0 | nsRect clipRect = unionRect; |
2397 | 0 | // Clip at the join to get a solid edge (without overlap or gap), when |
2398 | 0 | // this won't change the glyph too much. If the glyph is too small to |
2399 | 0 | // clip then we'll overlap rather than have a gap. |
2400 | 0 | nscoord width = mBmData[i].rightBearing - mBmData[i].leftBearing; |
2401 | 0 | if (width * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) { |
2402 | 0 | if (0 == i) { // left |
2403 | 0 | clipRect.width = end[i] - clipRect.x; |
2404 | 0 | } |
2405 | 0 | else if (2 == i) { // right |
2406 | 0 | clipRect.width -= start[i] - clipRect.x; |
2407 | 0 | clipRect.x = start[i]; |
2408 | 0 | } |
2409 | 0 | else { // middle |
2410 | 0 | clipRect.x = start[i]; |
2411 | 0 | clipRect.width = end[i] - start[i]; |
2412 | 0 | } |
2413 | 0 | } |
2414 | 0 | if (!clipRect.IsEmpty()) { |
2415 | 0 | AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); |
2416 | 0 | mGlyphs[i]->Draw(Range(mGlyphs[i].get()), gfx::Point(dx, dy), params); |
2417 | 0 | } |
2418 | 0 | } |
2419 | 0 | } |
2420 | 0 |
|
2421 | 0 | //////////////// |
2422 | 0 | // fill the gap between left and middle, and between middle and right. |
2423 | 0 | if (!mGlyphs[3]) { // null glue : draw a rule |
2424 | 0 | // figure out the dimensions of the rule to be drawn : |
2425 | 0 | // set ascent to lowest ascent among the two current successive parts. |
2426 | 0 | // set descent to highest descent among the two current successive parts. |
2427 | 0 | // this satisfies the convention used for over/underbraces, and helps |
2428 | 0 | // fix broken fonts. |
2429 | 0 | nscoord ascent, descent; |
2430 | 0 | int32_t first = 0, last = 1; |
2431 | 0 | while (last <= 2) { |
2432 | 0 | if (mGlyphs[last]) { |
2433 | 0 | ascent = mBmData[last].ascent; |
2434 | 0 | descent = mBmData[last].descent; |
2435 | 0 | if (mGlyphs[first]) { |
2436 | 0 | if (ascent > mBmData[first].ascent) |
2437 | 0 | ascent = mBmData[first].ascent; |
2438 | 0 | if (descent > mBmData[first].descent) |
2439 | 0 | descent = mBmData[first].descent; |
2440 | 0 | } |
2441 | 0 | } |
2442 | 0 | else if (mGlyphs[first]) { |
2443 | 0 | ascent = mBmData[first].ascent; |
2444 | 0 | descent = mBmData[first].descent; |
2445 | 0 | } |
2446 | 0 | else { |
2447 | 0 | NS_ERROR("Cannot stretch - All parts missing"); |
2448 | 0 | return NS_ERROR_UNEXPECTED; |
2449 | 0 | } |
2450 | 0 | // paint the rule between the parts |
2451 | 0 | nsRect rule(end[first], dy - ascent, |
2452 | 0 | start[last] - end[first], ascent + descent); |
2453 | 0 | PaintRule(aDrawTarget, oneDevPixel, rule, aColor); |
2454 | 0 | first = last; |
2455 | 0 | last++; |
2456 | 0 | } |
2457 | 0 | } |
2458 | 0 | else if (mBmData[3].rightBearing - mBmData[3].leftBearing > 0) { |
2459 | 0 | // glue is present |
2460 | 0 | nsBoundingMetrics& bm = mBmData[3]; |
2461 | 0 | // Ensure the stride for the glue is not reduced to less than one pixel |
2462 | 0 | if (bm.rightBearing - bm.leftBearing >= 3 * oneDevPixel) { |
2463 | 0 | // To protect against gaps, pretend the glue is smaller than it is, |
2464 | 0 | // in order to trim off ends and thus get a solid edge for the join. |
2465 | 0 | bm.leftBearing += oneDevPixel; |
2466 | 0 | bm.rightBearing -= oneDevPixel; |
2467 | 0 | } |
2468 | 0 |
|
2469 | 0 | nsRect clipRect = unionRect; |
2470 | 0 |
|
2471 | 0 | for (i = 0; i < 2; ++i) { |
2472 | 0 | // Make sure not to draw outside the character |
2473 | 0 | nscoord dx = std::max(end[i], aRect.x); |
2474 | 0 | nscoord fillEnd = std::min(start[i+1], aRect.XMost()); |
2475 | 0 | while (dx < fillEnd) { |
2476 | 0 | clipRect.x = dx; |
2477 | 0 | clipRect.width = std::min(bm.rightBearing - bm.leftBearing, fillEnd - dx); |
2478 | 0 | AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); |
2479 | 0 | dx -= bm.leftBearing; |
2480 | 0 | mGlyphs[3]->Draw(Range(mGlyphs[3].get()), gfx::Point(dx, dy), params); |
2481 | 0 | dx += bm.rightBearing; |
2482 | 0 | } |
2483 | 0 | } |
2484 | 0 | } |
2485 | | #ifdef DEBUG |
2486 | | else { // no glue |
2487 | | for (i = 0; i < 2; ++i) { |
2488 | | NS_ASSERTION(end[i] >= start[i+1], |
2489 | | "gap between parts with missing glue glyph"); |
2490 | | } |
2491 | | } |
2492 | | #endif |
2493 | 0 | return NS_OK; |
2494 | 0 | } |