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 "CSSFontFaceSet.h" |
28 | |
29 | #include "CSSFontFaceSource.h" |
30 | #include "CSSFontFamily.h" |
31 | #include "CSSFontSelector.h" |
32 | #include "CSSFontStyleValue.h" |
33 | #include "CSSParser.h" |
34 | #include "CSSPrimitiveValue.h" |
35 | #include "CSSSegmentedFontFace.h" |
36 | #include "CSSValueList.h" |
37 | #include "CSSValuePool.h" |
38 | #include "FontCache.h" |
39 | #include "StyleBuilderConverter.h" |
40 | #include "StyleProperties.h" |
41 | |
42 | namespace WebCore { |
43 | |
44 | CSSFontFaceSet::CSSFontFaceSet(CSSFontSelector* owningFontSelector) |
45 | : m_owningFontSelector(makeWeakPtr(owningFontSelector)) |
46 | { |
47 | } |
48 | |
49 | CSSFontFaceSet::~CSSFontFaceSet() |
50 | { |
51 | for (auto& face : m_faces) |
52 | face->removeClient(*this); |
53 | |
54 | for (auto& pair : m_locallyInstalledFacesLookupTable) { |
55 | for (auto& face : pair.value) |
56 | face->removeClient(*this); |
57 | } |
58 | } |
59 | |
60 | void CSSFontFaceSet::addClient(CSSFontFaceSetClient& client) |
61 | { |
62 | m_clients.add(&client); |
63 | } |
64 | |
65 | void CSSFontFaceSet::removeClient(CSSFontFaceSetClient& client) |
66 | { |
67 | ASSERT(m_clients.contains(&client)); |
68 | m_clients.remove(&client); |
69 | } |
70 | |
71 | void CSSFontFaceSet::incrementActiveCount() |
72 | { |
73 | ++m_activeCount; |
74 | if (m_activeCount == 1) { |
75 | m_status = Status::Loading; |
76 | for (auto* client : m_clients) |
77 | client->startedLoading(); |
78 | } |
79 | } |
80 | |
81 | void CSSFontFaceSet::decrementActiveCount() |
82 | { |
83 | --m_activeCount; |
84 | if (!m_activeCount) { |
85 | m_status = Status::Loaded; |
86 | for (auto* client : m_clients) |
87 | client->completedLoading(); |
88 | } |
89 | } |
90 | |
91 | bool CSSFontFaceSet::hasFace(const CSSFontFace& face) const |
92 | { |
93 | for (auto& myFace : m_faces) { |
94 | if (myFace.ptr() == &face) |
95 | return true; |
96 | } |
97 | |
98 | return false; |
99 | } |
100 | |
101 | void CSSFontFaceSet::ensureLocalFontFacesForFamilyRegistered(const String& familyName) |
102 | { |
103 | ASSERT(m_owningFontSelector); |
104 | if (m_locallyInstalledFacesLookupTable.contains(familyName)) |
105 | return; |
106 | |
107 | AllowUserInstalledFonts allowUserInstalledFonts = AllowUserInstalledFonts::Yes; |
108 | if (m_owningFontSelector->document()) |
109 | allowUserInstalledFonts = m_owningFontSelector->document()->settings().shouldAllowUserInstalledFonts() ? AllowUserInstalledFonts::Yes : AllowUserInstalledFonts::No; |
110 | Vector<FontSelectionCapabilities> capabilities = FontCache::singleton().getFontSelectionCapabilitiesInFamily(familyName, allowUserInstalledFonts); |
111 | if (capabilities.isEmpty()) |
112 | return; |
113 | |
114 | Vector<Ref<CSSFontFace>> faces; |
115 | for (auto item : capabilities) { |
116 | Ref<CSSFontFace> face = CSSFontFace::create(m_owningFontSelector.get(), nullptr, nullptr, true); |
117 | |
118 | Ref<CSSValueList> familyList = CSSValueList::createCommaSeparated(); |
119 | familyList->append(CSSValuePool::singleton().createFontFamilyValue(familyName)); |
120 | face->setFamilies(familyList.get()); |
121 | face->setFontSelectionCapabilities(item); |
122 | face->adoptSource(std::make_unique<CSSFontFaceSource>(face.get(), familyName)); |
123 | ASSERT(!face->computeFailureState()); |
124 | faces.append(WTFMove(face)); |
125 | } |
126 | m_locallyInstalledFacesLookupTable.add(familyName, WTFMove(faces)); |
127 | } |
128 | |
129 | String CSSFontFaceSet::familyNameFromPrimitive(const CSSPrimitiveValue& value) |
130 | { |
131 | if (value.isFontFamily()) |
132 | return value.fontFamily().familyName; |
133 | if (!value.isValueID()) |
134 | return { }; |
135 | |
136 | // We need to use the raw text for all the generic family types, since @font-face is a way of actually |
137 | // defining what font to use for those types. |
138 | switch (value.valueID()) { |
139 | case CSSValueSerif: |
140 | return serifFamily.get(); |
141 | case CSSValueSansSerif: |
142 | return sansSerifFamily.get(); |
143 | case CSSValueCursive: |
144 | return cursiveFamily.get(); |
145 | case CSSValueFantasy: |
146 | return fantasyFamily.get(); |
147 | case CSSValueMonospace: |
148 | return monospaceFamily.get(); |
149 | case CSSValueWebkitPictograph: |
150 | return pictographFamily.get(); |
151 | case CSSValueSystemUi: |
152 | return systemUiFamily.get(); |
153 | default: |
154 | return { }; |
155 | } |
156 | } |
157 | |
158 | void CSSFontFaceSet::addToFacesLookupTable(CSSFontFace& face) |
159 | { |
160 | if (!face.families()) |
161 | return; |
162 | |
163 | for (auto& item : *face.families()) { |
164 | String familyName = CSSFontFaceSet::familyNameFromPrimitive(downcast<CSSPrimitiveValue>(item.get())); |
165 | if (familyName.isEmpty()) |
166 | continue; |
167 | |
168 | auto addResult = m_facesLookupTable.add(familyName, Vector<Ref<CSSFontFace>>()); |
169 | auto& familyFontFaces = addResult.iterator->value; |
170 | if (addResult.isNewEntry) { |
171 | // m_locallyInstalledFontFaces grows without bound, eventually encorporating every font installed on the system. |
172 | // This is by design. |
173 | if (m_owningFontSelector) |
174 | ensureLocalFontFacesForFamilyRegistered(familyName); |
175 | familyFontFaces = { }; |
176 | } |
177 | |
178 | familyFontFaces.append(face); |
179 | } |
180 | } |
181 | |
182 | void CSSFontFaceSet::add(CSSFontFace& face) |
183 | { |
184 | ASSERT(!hasFace(face)); |
185 | |
186 | for (auto* client : m_clients) |
187 | client->fontModified(); |
188 | |
189 | face.addClient(*this); |
190 | m_cache.clear(); |
191 | |
192 | if (face.cssConnection()) |
193 | m_faces.insert(m_facesPartitionIndex++, face); |
194 | else |
195 | m_faces.append(face); |
196 | |
197 | addToFacesLookupTable(face); |
198 | |
199 | if (face.status() == CSSFontFace::Status::Loading || face.status() == CSSFontFace::Status::TimedOut) |
200 | incrementActiveCount(); |
201 | |
202 | if (face.cssConnection()) { |
203 | ASSERT(!m_constituentCSSConnections.contains(face.cssConnection())); |
204 | m_constituentCSSConnections.add(face.cssConnection(), &face); |
205 | } |
206 | } |
207 | |
208 | void CSSFontFaceSet::removeFromFacesLookupTable(const CSSFontFace& face, const CSSValueList& familiesToSearchFor) |
209 | { |
210 | for (auto& item : familiesToSearchFor) { |
211 | String familyName = CSSFontFaceSet::familyNameFromPrimitive(downcast<CSSPrimitiveValue>(item.get())); |
212 | if (familyName.isEmpty()) |
213 | continue; |
214 | |
215 | auto iterator = m_facesLookupTable.find(familyName); |
216 | ASSERT(iterator != m_facesLookupTable.end()); |
217 | bool found = false; |
218 | for (size_t i = 0; i < iterator->value.size(); ++i) { |
219 | if (iterator->value[i].ptr() == &face) { |
220 | found = true; |
221 | iterator->value.remove(i); |
222 | break; |
223 | } |
224 | } |
225 | ASSERT_UNUSED(found, found); |
226 | if (!iterator->value.size()) |
227 | m_facesLookupTable.remove(iterator); |
228 | } |
229 | } |
230 | |
231 | void CSSFontFaceSet::remove(const CSSFontFace& face) |
232 | { |
233 | auto protect = makeRef(face); |
234 | |
235 | m_cache.clear(); |
236 | |
237 | for (auto* client : m_clients) |
238 | client->fontModified(); |
239 | |
240 | if (face.families()) |
241 | removeFromFacesLookupTable(face, *face.families()); |
242 | |
243 | if (face.cssConnection()) { |
244 | ASSERT(m_constituentCSSConnections.get(face.cssConnection()) == &face); |
245 | m_constituentCSSConnections.remove(face.cssConnection()); |
246 | } |
247 | |
248 | for (size_t i = 0; i < m_faces.size(); ++i) { |
249 | if (m_faces[i].ptr() == &face) { |
250 | if (i < m_facesPartitionIndex) |
251 | --m_facesPartitionIndex; |
252 | m_faces[i]->removeClient(*this); |
253 | m_faces.remove(i); |
254 | if (face.status() == CSSFontFace::Status::Loading || face.status() == CSSFontFace::Status::TimedOut) |
255 | decrementActiveCount(); |
256 | return; |
257 | } |
258 | } |
259 | ASSERT_NOT_REACHED(); |
260 | } |
261 | |
262 | CSSFontFace* CSSFontFaceSet::lookUpByCSSConnection(StyleRuleFontFace& target) |
263 | { |
264 | return m_constituentCSSConnections.get(&target); |
265 | } |
266 | |
267 | void CSSFontFaceSet::purge() |
268 | { |
269 | Vector<Ref<CSSFontFace>> toRemove; |
270 | for (auto& face : m_faces) { |
271 | if (face->purgeable()) |
272 | toRemove.append(face.copyRef()); |
273 | } |
274 | |
275 | for (auto& item : toRemove) |
276 | remove(item.get()); |
277 | } |
278 | |
279 | void CSSFontFaceSet::emptyCaches() |
280 | { |
281 | m_cache.clear(); |
282 | } |
283 | |
284 | void CSSFontFaceSet::clear() |
285 | { |
286 | for (auto& face : m_faces) |
287 | face->removeClient(*this); |
288 | m_faces.clear(); |
289 | m_facesLookupTable.clear(); |
290 | m_locallyInstalledFacesLookupTable.clear(); |
291 | m_cache.clear(); |
292 | m_constituentCSSConnections.clear(); |
293 | m_facesPartitionIndex = 0; |
294 | m_status = Status::Loaded; |
295 | } |
296 | |
297 | CSSFontFace& CSSFontFaceSet::operator[](size_t i) |
298 | { |
299 | ASSERT(i < faceCount()); |
300 | return m_faces[i]; |
301 | } |
302 | |
303 | static FontSelectionRequest computeFontSelectionRequest(MutableStyleProperties& style) |
304 | { |
305 | RefPtr<CSSValue> weightValue = style.getPropertyCSSValue(CSSPropertyFontWeight).get(); |
306 | if (!weightValue) |
307 | weightValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal).ptr(); |
308 | |
309 | RefPtr<CSSValue> stretchValue = style.getPropertyCSSValue(CSSPropertyFontStretch).get(); |
310 | if (!stretchValue) |
311 | stretchValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal).ptr(); |
312 | |
313 | RefPtr<CSSValue> styleValue = style.getPropertyCSSValue(CSSPropertyFontStyle).get(); |
314 | if (!styleValue) |
315 | styleValue = CSSFontStyleValue::create(CSSValuePool::singleton().createIdentifierValue(CSSValueNormal)); |
316 | |
317 | auto weightSelectionValue = StyleBuilderConverter::convertFontWeightFromValue(*weightValue); |
318 | auto stretchSelectionValue = StyleBuilderConverter::convertFontStretchFromValue(*stretchValue); |
319 | auto styleSelectionValue = StyleBuilderConverter::convertFontStyleFromValue(*styleValue); |
320 | |
321 | return { weightSelectionValue, stretchSelectionValue, styleSelectionValue }; |
322 | } |
323 | |
324 | static HashSet<UChar32> codePointsFromString(StringView stringView) |
325 | { |
326 | HashSet<UChar32> result; |
327 | auto graphemeClusters = stringView.graphemeClusters(); |
328 | for (auto cluster : graphemeClusters) { |
329 | ASSERT(cluster.length() > 0); |
330 | UChar32 character = 0; |
331 | if (cluster.is8Bit()) |
332 | character = cluster[0]; |
333 | else |
334 | U16_GET(cluster.characters16(), 0, 0, cluster.length(), character); |
335 | result.add(character); |
336 | } |
337 | return result; |
338 | } |
339 | |
340 | ExceptionOr<Vector<std::reference_wrapper<CSSFontFace>>> CSSFontFaceSet::matchingFacesExcludingPreinstalledFonts(const String& font, const String& string) |
341 | { |
342 | auto style = MutableStyleProperties::create(); |
343 | auto parseResult = CSSParser::parseValue(style, CSSPropertyFont, font, true, HTMLStandardMode); |
344 | if (parseResult == CSSParser::ParseResult::Error) |
345 | return Exception { SyntaxError }; |
346 | |
347 | FontSelectionRequest request = computeFontSelectionRequest(style.get()); |
348 | |
349 | auto family = style->getPropertyCSSValue(CSSPropertyFontFamily); |
350 | if (!is<CSSValueList>(family)) |
351 | return Exception { SyntaxError }; |
352 | CSSValueList& familyList = downcast<CSSValueList>(*family); |
353 | |
354 | HashSet<AtomicString> uniqueFamilies; |
355 | Vector<AtomicString> familyOrder; |
356 | for (auto& family : familyList) { |
357 | auto& primitive = downcast<CSSPrimitiveValue>(family.get()); |
358 | if (!primitive.isFontFamily()) |
359 | continue; |
360 | if (uniqueFamilies.add(primitive.fontFamily().familyName).isNewEntry) |
361 | familyOrder.append(primitive.fontFamily().familyName); |
362 | } |
363 | |
364 | HashSet<CSSFontFace*> resultConstituents; |
365 | for (auto codePoint : codePointsFromString(string)) { |
366 | bool found = false; |
367 | for (auto& family : familyOrder) { |
368 | auto* faces = fontFace(request, family); |
369 | if (!faces) |
370 | continue; |
371 | for (auto& constituentFace : faces->constituentFaces()) { |
372 | if (constituentFace->isLocalFallback()) |
373 | continue; |
374 | if (constituentFace->rangesMatchCodePoint(codePoint)) { |
375 | resultConstituents.add(constituentFace.ptr()); |
376 | found = true; |
377 | break; |
378 | } |
379 | } |
380 | if (found) |
381 | break; |
382 | } |
383 | } |
384 | |
385 | Vector<std::reference_wrapper<CSSFontFace>> result; |
386 | result.reserveInitialCapacity(resultConstituents.size()); |
387 | for (auto* constituent : resultConstituents) |
388 | result.uncheckedAppend(*constituent); |
389 | return result; |
390 | } |
391 | |
392 | ExceptionOr<bool> CSSFontFaceSet::check(const String& font, const String& text) |
393 | { |
394 | auto matchingFaces = this->matchingFacesExcludingPreinstalledFonts(font, text); |
395 | if (matchingFaces.hasException()) |
396 | return matchingFaces.releaseException(); |
397 | |
398 | for (auto& face : matchingFaces.releaseReturnValue()) { |
399 | if (face.get().status() == CSSFontFace::Status::Pending) |
400 | return false; |
401 | } |
402 | return true; |
403 | } |
404 | |
405 | CSSSegmentedFontFace* CSSFontFaceSet::fontFace(FontSelectionRequest request, const AtomicString& family) |
406 | { |
407 | auto iterator = m_facesLookupTable.find(family); |
408 | if (iterator == m_facesLookupTable.end()) |
409 | return nullptr; |
410 | auto& familyFontFaces = iterator->value; |
411 | |
412 | auto& segmentedFontFaceCache = m_cache.add(family, FontSelectionHashMap()).iterator->value; |
413 | |
414 | auto& face = segmentedFontFaceCache.add(request, nullptr).iterator->value; |
415 | if (face) |
416 | return face.get(); |
417 | |
418 | face = CSSSegmentedFontFace::create(); |
419 | |
420 | Vector<std::reference_wrapper<CSSFontFace>, 32> candidateFontFaces; |
421 | for (int i = familyFontFaces.size() - 1; i >= 0; --i) { |
422 | CSSFontFace& candidate = familyFontFaces[i]; |
423 | auto capabilities = candidate.fontSelectionCapabilities(); |
424 | if (!isItalic(request.slope) && isItalic(capabilities.slope.minimum)) |
425 | continue; |
426 | candidateFontFaces.append(candidate); |
427 | } |
428 | |
429 | auto localIterator = m_locallyInstalledFacesLookupTable.find(family); |
430 | if (localIterator != m_locallyInstalledFacesLookupTable.end()) { |
431 | for (auto& candidate : localIterator->value) { |
432 | auto capabilities = candidate->fontSelectionCapabilities(); |
433 | if (!isItalic(request.slope) && isItalic(capabilities.slope.minimum)) |
434 | continue; |
435 | candidateFontFaces.append(candidate); |
436 | } |
437 | } |
438 | |
439 | if (!candidateFontFaces.isEmpty()) { |
440 | Vector<FontSelectionCapabilities> capabilities; |
441 | capabilities.reserveInitialCapacity(candidateFontFaces.size()); |
442 | for (auto& face : candidateFontFaces) |
443 | capabilities.uncheckedAppend(face.get().fontSelectionCapabilities()); |
444 | FontSelectionAlgorithm fontSelectionAlgorithm(request, capabilities); |
445 | std::stable_sort(candidateFontFaces.begin(), candidateFontFaces.end(), [&fontSelectionAlgorithm](const CSSFontFace& first, const CSSFontFace& second) { |
446 | auto firstCapabilities = first.fontSelectionCapabilities(); |
447 | auto secondCapabilities = second.fontSelectionCapabilities(); |
448 | |
449 | auto stretchDistanceFirst = fontSelectionAlgorithm.stretchDistance(firstCapabilities).distance; |
450 | auto stretchDistanceSecond = fontSelectionAlgorithm.stretchDistance(secondCapabilities).distance; |
451 | if (stretchDistanceFirst < stretchDistanceSecond) |
452 | return true; |
453 | if (stretchDistanceFirst > stretchDistanceSecond) |
454 | return false; |
455 | |
456 | auto styleDistanceFirst = fontSelectionAlgorithm.styleDistance(firstCapabilities).distance; |
457 | auto styleDistanceSecond = fontSelectionAlgorithm.styleDistance(secondCapabilities).distance; |
458 | if (styleDistanceFirst < styleDistanceSecond) |
459 | return true; |
460 | if (styleDistanceFirst > styleDistanceSecond) |
461 | return false; |
462 | |
463 | auto weightDistanceFirst = fontSelectionAlgorithm.weightDistance(firstCapabilities).distance; |
464 | auto weightDistanceSecond = fontSelectionAlgorithm.weightDistance(secondCapabilities).distance; |
465 | if (weightDistanceFirst < weightDistanceSecond) |
466 | return true; |
467 | return false; |
468 | }); |
469 | for (auto& candidate : candidateFontFaces) |
470 | face->appendFontFace(candidate.get()); |
471 | } |
472 | |
473 | return face.get(); |
474 | } |
475 | |
476 | void CSSFontFaceSet::fontStateChanged(CSSFontFace& face, CSSFontFace::Status oldState, CSSFontFace::Status newState) |
477 | { |
478 | ASSERT(hasFace(face)); |
479 | if (oldState == CSSFontFace::Status::Pending) { |
480 | ASSERT(newState == CSSFontFace::Status::Loading); |
481 | incrementActiveCount(); |
482 | } |
483 | if (newState == CSSFontFace::Status::Success || newState == CSSFontFace::Status::Failure) { |
484 | ASSERT(oldState == CSSFontFace::Status::Loading || oldState == CSSFontFace::Status::TimedOut); |
485 | for (auto* client : m_clients) |
486 | client->faceFinished(face, newState); |
487 | decrementActiveCount(); |
488 | } |
489 | } |
490 | |
491 | void CSSFontFaceSet::fontPropertyChanged(CSSFontFace& face, CSSValueList* oldFamilies) |
492 | { |
493 | m_cache.clear(); |
494 | |
495 | if (oldFamilies) { |
496 | removeFromFacesLookupTable(face, *oldFamilies); |
497 | addToFacesLookupTable(face); |
498 | } |
499 | |
500 | for (auto* client : m_clients) |
501 | client->fontModified(); |
502 | } |
503 | |
504 | } |
505 | |