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
47namespace WebCore {
48
49static 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
56ExceptionOr<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
121Ref<FontFace> FontFace::create(CSSFontFace& face)
122{
123 return adoptRef(*new FontFace(face));
124}
125
126FontFace::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
133FontFace::FontFace(CSSFontFace& face)
134 : m_backing(face)
135 , m_loadedPromise(*this, &FontFace::loadedPromiseResolve)
136{
137 m_backing->addClient(*this);
138}
139
140FontFace::~FontFace()
141{
142 m_backing->removeClient(*this);
143}
144
145RefPtr<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
151ExceptionOr<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
166ExceptionOr<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
178ExceptionOr<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
190ExceptionOr<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
202ExceptionOr<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
215ExceptionOr<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
270ExceptionOr<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
282ExceptionOr<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
295String 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
316String 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
346String 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
367String 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
388String 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
399String FontFace::variant() const
400{
401 m_backing->updateStyleIfNeeded();
402 return computeFontVariant(m_backing->variantSettings())->cssText();
403}
404
405String 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
416String FontFace::display() const
417{
418 m_backing->updateStyleIfNeeded();
419 return CSSValuePool::singleton().createValue(m_backing->loadingBehavior())->cssText();
420}
421
422auto 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
440void 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
448void 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
478auto FontFace::load() -> LoadedPromise&
479{
480 m_backing->load();
481 return m_loadedPromise;
482}
483
484FontFace& FontFace::loadedPromiseResolve()
485{
486 return *this;
487}
488
489}
490