1 | /* |
2 | * Copyright (C) 2016 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "FontFace.h" |
28 | |
29 | #include "CSSComputedStyleDeclaration.h" |
30 | #include "CSSFontFaceSource.h" |
31 | #include "CSSFontFeatureValue.h" |
32 | #include "CSSFontStyleValue.h" |
33 | #include "CSSParser.h" |
34 | #include "CSSPrimitiveValueMappings.h" |
35 | #include "CSSUnicodeRangeValue.h" |
36 | #include "CSSValueList.h" |
37 | #include "CSSValuePool.h" |
38 | #include "Document.h" |
39 | #include "FontVariantBuilder.h" |
40 | #include "JSFontFace.h" |
41 | #include "StyleProperties.h" |
42 | #include <JavaScriptCore/ArrayBuffer.h> |
43 | #include <JavaScriptCore/ArrayBufferView.h> |
44 | #include <JavaScriptCore/JSCInlines.h> |
45 | #include <wtf/text/StringBuilder.h> |
46 | |
47 | namespace WebCore { |
48 | |
49 | static bool populateFontFaceWithArrayBuffer(CSSFontFace& fontFace, Ref<JSC::ArrayBufferView>&& arrayBufferView) |
50 | { |
51 | auto source = std::make_unique<CSSFontFaceSource>(fontFace, String(), nullptr, nullptr, WTFMove(arrayBufferView)); |
52 | fontFace.adoptSource(WTFMove(source)); |
53 | return false; |
54 | } |
55 | |
56 | ExceptionOr<Ref<FontFace>> FontFace::create(Document& document, const String& family, Source&& source, const Descriptors& descriptors) |
57 | { |
58 | auto result = adoptRef(*new FontFace(document.fontSelector())); |
59 | |
60 | bool dataRequiresAsynchronousLoading = true; |
61 | |
62 | auto setFamilyResult = result->setFamily(family); |
63 | if (setFamilyResult.hasException()) |
64 | return setFamilyResult.releaseException(); |
65 | |
66 | auto sourceConversionResult = WTF::switchOn(source, |
67 | [&] (String& string) -> ExceptionOr<void> { |
68 | auto value = FontFace::parseString(string, CSSPropertySrc); |
69 | if (!is<CSSValueList>(value)) |
70 | return Exception { SyntaxError }; |
71 | CSSFontFace::appendSources(result->backing(), downcast<CSSValueList>(*value), &document, false); |
72 | return { }; |
73 | }, |
74 | [&] (RefPtr<ArrayBufferView>& arrayBufferView) -> ExceptionOr<void> { |
75 | dataRequiresAsynchronousLoading = populateFontFaceWithArrayBuffer(result->backing(), arrayBufferView.releaseNonNull()); |
76 | return { }; |
77 | }, |
78 | [&] (RefPtr<ArrayBuffer>& arrayBuffer) -> ExceptionOr<void> { |
79 | unsigned byteLength = arrayBuffer->byteLength(); |
80 | auto arrayBufferView = JSC::Uint8Array::create(WTFMove(arrayBuffer), 0, byteLength); |
81 | dataRequiresAsynchronousLoading = populateFontFaceWithArrayBuffer(result->backing(), WTFMove(arrayBufferView)); |
82 | return { }; |
83 | } |
84 | ); |
85 | |
86 | if (sourceConversionResult.hasException()) |
87 | return sourceConversionResult.releaseException(); |
88 | |
89 | // These ternaries match the default strings inside the FontFaceDescriptors dictionary inside FontFace.idl. |
90 | auto setStyleResult = result->setStyle(descriptors.style.isEmpty() ? "normal"_s : descriptors.style); |
91 | if (setStyleResult.hasException()) |
92 | return setStyleResult.releaseException(); |
93 | auto setWeightResult = result->setWeight(descriptors.weight.isEmpty() ? "normal"_s : descriptors.weight); |
94 | if (setWeightResult.hasException()) |
95 | return setWeightResult.releaseException(); |
96 | auto setStretchResult = result->setStretch(descriptors.stretch.isEmpty() ? "normal"_s : descriptors.stretch); |
97 | if (setStretchResult.hasException()) |
98 | return setStretchResult.releaseException(); |
99 | auto setUnicodeRangeResult = result->setUnicodeRange(descriptors.unicodeRange.isEmpty() ? "U+0-10FFFF"_s : descriptors.unicodeRange); |
100 | if (setUnicodeRangeResult.hasException()) |
101 | return setUnicodeRangeResult.releaseException(); |
102 | auto setVariantResult = result->setVariant(descriptors.variant.isEmpty() ? "normal"_s : descriptors.variant); |
103 | if (setVariantResult.hasException()) |
104 | return setVariantResult.releaseException(); |
105 | auto setFeatureSettingsResult = result->setFeatureSettings(descriptors.featureSettings.isEmpty() ? "normal"_s : descriptors.featureSettings); |
106 | if (setFeatureSettingsResult.hasException()) |
107 | return setFeatureSettingsResult.releaseException(); |
108 | auto setDisplayResult = result->setDisplay(descriptors.display.isEmpty() ? "auto"_s : descriptors.display); |
109 | if (setDisplayResult.hasException()) |
110 | return setDisplayResult.releaseException(); |
111 | |
112 | if (!dataRequiresAsynchronousLoading) { |
113 | result->backing().load(); |
114 | auto status = result->backing().status(); |
115 | ASSERT_UNUSED(status, status == CSSFontFace::Status::Success || status == CSSFontFace::Status::Failure); |
116 | } |
117 | |
118 | return result; |
119 | } |
120 | |
121 | Ref<FontFace> FontFace::create(CSSFontFace& face) |
122 | { |
123 | return adoptRef(*new FontFace(face)); |
124 | } |
125 | |
126 | FontFace::FontFace(CSSFontSelector& fontSelector) |
127 | : m_backing(CSSFontFace::create(&fontSelector, nullptr, this)) |
128 | , m_loadedPromise(*this, &FontFace::loadedPromiseResolve) |
129 | { |
130 | m_backing->addClient(*this); |
131 | } |
132 | |
133 | FontFace::FontFace(CSSFontFace& face) |
134 | : m_backing(face) |
135 | , m_loadedPromise(*this, &FontFace::loadedPromiseResolve) |
136 | { |
137 | m_backing->addClient(*this); |
138 | } |
139 | |
140 | FontFace::~FontFace() |
141 | { |
142 | m_backing->removeClient(*this); |
143 | } |
144 | |
145 | RefPtr<CSSValue> FontFace::parseString(const String& string, CSSPropertyID propertyID) |
146 | { |
147 | // FIXME: Should use the Document to get the right parsing mode. |
148 | return CSSParser::parseFontFaceDescriptor(propertyID, string, HTMLStandardMode); |
149 | } |
150 | |
151 | ExceptionOr<void> FontFace::setFamily(const String& family) |
152 | { |
153 | if (family.isEmpty()) |
154 | return Exception { SyntaxError }; |
155 | |
156 | // FIXME: https://bugs.webkit.org/show_bug.cgi?id=196381 Don't use a list here. |
157 | // See consumeFontFamilyDescriptor() in CSSPropertyParser.cpp for why we're using it. |
158 | auto list = CSSValueList::createCommaSeparated(); |
159 | list->append(CSSValuePool::singleton().createFontFamilyValue(family)); |
160 | bool success = m_backing->setFamilies(list); |
161 | if (!success) |
162 | return Exception { SyntaxError }; |
163 | return { }; |
164 | } |
165 | |
166 | ExceptionOr<void> FontFace::setStyle(const String& style) |
167 | { |
168 | if (style.isEmpty()) |
169 | return Exception { SyntaxError }; |
170 | |
171 | if (auto value = parseString(style, CSSPropertyFontStyle)) { |
172 | m_backing->setStyle(*value); |
173 | return { }; |
174 | } |
175 | return Exception { SyntaxError }; |
176 | } |
177 | |
178 | ExceptionOr<void> FontFace::setWeight(const String& weight) |
179 | { |
180 | if (weight.isEmpty()) |
181 | return Exception { SyntaxError }; |
182 | |
183 | if (auto value = parseString(weight, CSSPropertyFontWeight)) { |
184 | m_backing->setWeight(*value); |
185 | return { }; |
186 | } |
187 | return Exception { SyntaxError }; |
188 | } |
189 | |
190 | ExceptionOr<void> FontFace::setStretch(const String& stretch) |
191 | { |
192 | if (stretch.isEmpty()) |
193 | return Exception { SyntaxError }; |
194 | |
195 | if (auto value = parseString(stretch, CSSPropertyFontStretch)) { |
196 | m_backing->setStretch(*value); |
197 | return { }; |
198 | } |
199 | return Exception { SyntaxError }; |
200 | } |
201 | |
202 | ExceptionOr<void> FontFace::setUnicodeRange(const String& unicodeRange) |
203 | { |
204 | if (unicodeRange.isEmpty()) |
205 | return Exception { SyntaxError }; |
206 | |
207 | bool success = false; |
208 | if (auto value = parseString(unicodeRange, CSSPropertyUnicodeRange)) |
209 | success = m_backing->setUnicodeRange(*value); |
210 | if (!success) |
211 | return Exception { SyntaxError }; |
212 | return { }; |
213 | } |
214 | |
215 | ExceptionOr<void> FontFace::setVariant(const String& variant) |
216 | { |
217 | if (variant.isEmpty()) |
218 | return Exception { SyntaxError }; |
219 | |
220 | auto style = MutableStyleProperties::create(); |
221 | auto result = CSSParser::parseValue(style, CSSPropertyFontVariant, variant, true, HTMLStandardMode); |
222 | if (result == CSSParser::ParseResult::Error) |
223 | return Exception { SyntaxError }; |
224 | |
225 | // FIXME: Would be much better to stage the new settings and set them all at once |
226 | // instead of this dance where we make a backup and revert to it if something fails. |
227 | FontVariantSettings backup = m_backing->variantSettings(); |
228 | |
229 | auto normal = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal); |
230 | bool success = true; |
231 | |
232 | if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantLigatures)) |
233 | success &= m_backing->setVariantLigatures(*value); |
234 | else |
235 | m_backing->setVariantLigatures(normal); |
236 | |
237 | if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantPosition)) |
238 | success &= m_backing->setVariantPosition(*value); |
239 | else |
240 | m_backing->setVariantPosition(normal); |
241 | |
242 | if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantCaps)) |
243 | success &= m_backing->setVariantCaps(*value); |
244 | else |
245 | m_backing->setVariantCaps(normal); |
246 | |
247 | if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantNumeric)) |
248 | success &= m_backing->setVariantNumeric(*value); |
249 | else |
250 | m_backing->setVariantNumeric(normal); |
251 | |
252 | if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantAlternates)) |
253 | success &= m_backing->setVariantAlternates(*value); |
254 | else |
255 | m_backing->setVariantAlternates(normal); |
256 | |
257 | if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantEastAsian)) |
258 | success &= m_backing->setVariantEastAsian(*value); |
259 | else |
260 | m_backing->setVariantEastAsian(normal); |
261 | |
262 | if (!success) { |
263 | m_backing->setVariantSettings(backup); |
264 | return Exception { SyntaxError }; |
265 | } |
266 | |
267 | return { }; |
268 | } |
269 | |
270 | ExceptionOr<void> FontFace::setFeatureSettings(const String& featureSettings) |
271 | { |
272 | if (featureSettings.isEmpty()) |
273 | return Exception { SyntaxError }; |
274 | |
275 | auto value = parseString(featureSettings, CSSPropertyFontFeatureSettings); |
276 | if (!value) |
277 | return Exception { SyntaxError }; |
278 | m_backing->setFeatureSettings(*value); |
279 | return { }; |
280 | } |
281 | |
282 | ExceptionOr<void> FontFace::setDisplay(const String& display) |
283 | { |
284 | if (display.isEmpty()) |
285 | return Exception { SyntaxError }; |
286 | |
287 | if (auto value = parseString(display, CSSPropertyFontDisplay)) { |
288 | m_backing->setLoadingBehavior(*value); |
289 | return { }; |
290 | } |
291 | |
292 | return Exception { SyntaxError }; |
293 | } |
294 | |
295 | String FontFace::family() const |
296 | { |
297 | m_backing->updateStyleIfNeeded(); |
298 | |
299 | // FIXME: https://bugs.webkit.org/show_bug.cgi?id=196381 This is only here because CSSFontFace erroneously uses a list of values instead of a single value. |
300 | // See consumeFontFamilyDescriptor() in CSSPropertyParser.cpp. |
301 | if (m_backing->families()->length() == 1) { |
302 | if (m_backing->families()->item(0)) { |
303 | auto& item = *m_backing->families()->item(0); |
304 | if (item.isPrimitiveValue()) { |
305 | auto& primitiveValue = downcast<CSSPrimitiveValue>(item); |
306 | if (primitiveValue.isFontFamily()) { |
307 | auto& fontFamily = primitiveValue.fontFamily(); |
308 | return fontFamily.familyName; |
309 | } |
310 | } |
311 | } |
312 | } |
313 | return m_backing->families()->cssText(); |
314 | } |
315 | |
316 | String FontFace::style() const |
317 | { |
318 | m_backing->updateStyleIfNeeded(); |
319 | auto style = m_backing->italic(); |
320 | |
321 | auto minimum = ComputedStyleExtractor::fontStyleFromStyleValue(style.minimum, FontStyleAxis::ital); |
322 | auto maximum = ComputedStyleExtractor::fontStyleFromStyleValue(style.maximum, FontStyleAxis::ital); |
323 | |
324 | if (minimum.get().equals(maximum.get())) |
325 | return minimum->cssText(); |
326 | |
327 | auto minimumNonKeyword = ComputedStyleExtractor::fontNonKeywordStyleFromStyleValue(style.minimum); |
328 | auto maximumNonKeyword = ComputedStyleExtractor::fontNonKeywordStyleFromStyleValue(style.maximum); |
329 | |
330 | ASSERT(minimumNonKeyword->fontStyleValue->valueID() == CSSValueOblique); |
331 | ASSERT(maximumNonKeyword->fontStyleValue->valueID() == CSSValueOblique); |
332 | |
333 | StringBuilder builder; |
334 | builder.append(minimumNonKeyword->fontStyleValue->cssText()); |
335 | builder.append(' '); |
336 | if (minimum->obliqueValue.get() == maximum->obliqueValue.get()) |
337 | builder.append(minimumNonKeyword->obliqueValue->cssText()); |
338 | else { |
339 | builder.append(minimumNonKeyword->obliqueValue->cssText()); |
340 | builder.append(' '); |
341 | builder.append(maximumNonKeyword->obliqueValue->cssText()); |
342 | } |
343 | return builder.toString(); |
344 | } |
345 | |
346 | String FontFace::weight() const |
347 | { |
348 | m_backing->updateStyleIfNeeded(); |
349 | auto weight = m_backing->weight(); |
350 | |
351 | auto minimum = ComputedStyleExtractor::fontWeightFromStyleValue(weight.minimum); |
352 | auto maximum = ComputedStyleExtractor::fontWeightFromStyleValue(weight.maximum); |
353 | |
354 | if (minimum.get().equals(maximum.get())) |
355 | return minimum->cssText(); |
356 | |
357 | auto minimumNonKeyword = ComputedStyleExtractor::fontNonKeywordWeightFromStyleValue(weight.minimum); |
358 | auto maximumNonKeyword = ComputedStyleExtractor::fontNonKeywordWeightFromStyleValue(weight.maximum); |
359 | |
360 | StringBuilder builder; |
361 | builder.append(minimumNonKeyword->cssText()); |
362 | builder.append(' '); |
363 | builder.append(maximumNonKeyword->cssText()); |
364 | return builder.toString(); |
365 | } |
366 | |
367 | String FontFace::stretch() const |
368 | { |
369 | m_backing->updateStyleIfNeeded(); |
370 | auto stretch = m_backing->stretch(); |
371 | |
372 | auto minimum = ComputedStyleExtractor::fontStretchFromStyleValue(stretch.minimum); |
373 | auto maximum = ComputedStyleExtractor::fontStretchFromStyleValue(stretch.maximum); |
374 | |
375 | if (minimum.get().equals(maximum.get())) |
376 | return minimum->cssText(); |
377 | |
378 | auto minimumNonKeyword = ComputedStyleExtractor::fontNonKeywordStretchFromStyleValue(stretch.minimum); |
379 | auto maximumNonKeyword = ComputedStyleExtractor::fontNonKeywordStretchFromStyleValue(stretch.maximum); |
380 | |
381 | StringBuilder builder; |
382 | builder.append(minimumNonKeyword->cssText()); |
383 | builder.append(' '); |
384 | builder.append(maximumNonKeyword->cssText()); |
385 | return builder.toString(); |
386 | } |
387 | |
388 | String FontFace::unicodeRange() const |
389 | { |
390 | m_backing->updateStyleIfNeeded(); |
391 | if (!m_backing->ranges().size()) |
392 | return "U+0-10FFFF"_s ; |
393 | auto values = CSSValueList::createCommaSeparated(); |
394 | for (auto& range : m_backing->ranges()) |
395 | values->append(CSSUnicodeRangeValue::create(range.from, range.to)); |
396 | return values->cssText(); |
397 | } |
398 | |
399 | String FontFace::variant() const |
400 | { |
401 | m_backing->updateStyleIfNeeded(); |
402 | return computeFontVariant(m_backing->variantSettings())->cssText(); |
403 | } |
404 | |
405 | String FontFace::featureSettings() const |
406 | { |
407 | m_backing->updateStyleIfNeeded(); |
408 | if (!m_backing->featureSettings().size()) |
409 | return "normal"_s ; |
410 | auto list = CSSValueList::createCommaSeparated(); |
411 | for (auto& feature : m_backing->featureSettings()) |
412 | list->append(CSSFontFeatureValue::create(FontTag(feature.tag()), feature.value())); |
413 | return list->cssText(); |
414 | } |
415 | |
416 | String FontFace::display() const |
417 | { |
418 | m_backing->updateStyleIfNeeded(); |
419 | return CSSValuePool::singleton().createValue(m_backing->loadingBehavior())->cssText(); |
420 | } |
421 | |
422 | auto FontFace::status() const -> LoadStatus |
423 | { |
424 | switch (m_backing->status()) { |
425 | case CSSFontFace::Status::Pending: |
426 | return LoadStatus::Unloaded; |
427 | case CSSFontFace::Status::Loading: |
428 | return LoadStatus::Loading; |
429 | case CSSFontFace::Status::TimedOut: |
430 | return LoadStatus::Error; |
431 | case CSSFontFace::Status::Success: |
432 | return LoadStatus::Loaded; |
433 | case CSSFontFace::Status::Failure: |
434 | return LoadStatus::Error; |
435 | } |
436 | ASSERT_NOT_REACHED(); |
437 | return LoadStatus::Error; |
438 | } |
439 | |
440 | void FontFace::adopt(CSSFontFace& newFace) |
441 | { |
442 | m_backing->removeClient(*this); |
443 | m_backing = newFace; |
444 | m_backing->addClient(*this); |
445 | newFace.setWrapper(*this); |
446 | } |
447 | |
448 | void FontFace::fontStateChanged(CSSFontFace& face, CSSFontFace::Status, CSSFontFace::Status newState) |
449 | { |
450 | ASSERT_UNUSED(face, &face == m_backing.ptr()); |
451 | switch (newState) { |
452 | case CSSFontFace::Status::Loading: |
453 | // We still need to resolve promises when loading completes, even if all references to use have fallen out of scope. |
454 | ref(); |
455 | break; |
456 | case CSSFontFace::Status::TimedOut: |
457 | break; |
458 | case CSSFontFace::Status::Success: |
459 | // FIXME: This check should not be needed, but because FontFace's are sometimes adopted after they have already |
460 | // gone through a load cycle, we can sometimes come back through here and try to resolve the promise again. |
461 | if (!m_loadedPromise.isFulfilled()) |
462 | m_loadedPromise.resolve(*this); |
463 | deref(); |
464 | return; |
465 | case CSSFontFace::Status::Failure: |
466 | // FIXME: This check should not be needed, but because FontFace's are sometimes adopted after they have already |
467 | // gone through a load cycle, we can sometimes come back through here and try to resolve the promise again. |
468 | if (!m_loadedPromise.isFulfilled()) |
469 | m_loadedPromise.reject(Exception { NetworkError }); |
470 | deref(); |
471 | return; |
472 | case CSSFontFace::Status::Pending: |
473 | ASSERT_NOT_REACHED(); |
474 | return; |
475 | } |
476 | } |
477 | |
478 | auto FontFace::load() -> LoadedPromise& |
479 | { |
480 | m_backing->load(); |
481 | return m_loadedPromise; |
482 | } |
483 | |
484 | FontFace& FontFace::loadedPromiseResolve() |
485 | { |
486 | return *this; |
487 | } |
488 | |
489 | } |
490 | |