1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "Minikin"
18
19#include <cutils/log.h>
20#include <stdlib.h>
21#include <stdint.h>
22#include <string.h>
23
24#include <hb.h>
25#include <hb-ot.h>
26
27#include <utils/JenkinsHash.h>
28
29#include "FontLanguage.h"
30#include "FontLanguageListCache.h"
31#include "HbFontCache.h"
32#include "MinikinInternal.h"
33#include <minikin/MinikinFont.h>
34#include <minikin/AnalyzeStyle.h>
35#include <minikin/CmapCoverage.h>
36#include <minikin/FontFamily.h>
37#include <UniquePtr.h>
38
39using std::vector;
40
41namespace android {
42
43FontStyle::FontStyle(int variant, int weight, bool italic)
44        : FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {
45}
46
47FontStyle::FontStyle(uint32_t languageListId, int variant, int weight, bool italic)
48        : bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {
49}
50
51hash_t FontStyle::hash() const {
52    uint32_t hash = JenkinsHashMix(0, bits);
53    hash = JenkinsHashMix(hash, mLanguageListId);
54    return JenkinsHashWhiten(hash);
55}
56
57// static
58uint32_t FontStyle::registerLanguageList(const std::string& languages) {
59    AutoMutex _l(gMinikinLock);
60    return FontLanguageListCache::getId(languages);
61}
62
63// static
64uint32_t FontStyle::pack(int variant, int weight, bool italic) {
65    return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift);
66}
67
68FontFamily::FontFamily() : FontFamily(0 /* variant */) {
69}
70
71FontFamily::FontFamily(int variant) : FontFamily(FontLanguageListCache::kEmptyListId, variant) {
72}
73
74FontFamily::~FontFamily() {
75    for (size_t i = 0; i < mFonts.size(); i++) {
76        mFonts[i].typeface->UnrefLocked();
77    }
78}
79
80bool FontFamily::addFont(MinikinFont* typeface) {
81    AutoMutex _l(gMinikinLock);
82    const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
83    HbBlob os2Table(getFontTable(typeface, os2Tag));
84    if (os2Table.get() == nullptr) return false;
85    int weight;
86    bool italic;
87    if (analyzeStyle(os2Table.get(), os2Table.size(), &weight, &italic)) {
88        //ALOGD("analyzed weight = %d, italic = %s", weight, italic ? "true" : "false");
89        FontStyle style(weight, italic);
90        addFontLocked(typeface, style);
91        return true;
92    } else {
93        ALOGD("failed to analyze style");
94    }
95    return false;
96}
97
98void FontFamily::addFont(MinikinFont* typeface, FontStyle style) {
99    AutoMutex _l(gMinikinLock);
100    addFontLocked(typeface, style);
101}
102
103void FontFamily::addFontLocked(MinikinFont* typeface, FontStyle style) {
104    typeface->RefLocked();
105    mFonts.push_back(Font(typeface, style));
106    mCoverageValid = false;
107}
108
109// Compute a matching metric between two styles - 0 is an exact match
110static int computeMatch(FontStyle style1, FontStyle style2) {
111    if (style1 == style2) return 0;
112    int score = abs(style1.getWeight() - style2.getWeight());
113    if (style1.getItalic() != style2.getItalic()) {
114        score += 2;
115    }
116    return score;
117}
118
119static FontFakery computeFakery(FontStyle wanted, FontStyle actual) {
120    // If desired weight is semibold or darker, and 2 or more grades
121    // higher than actual (for example, medium 500 -> bold 700), then
122    // select fake bold.
123    int wantedWeight = wanted.getWeight();
124    bool isFakeBold = wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;
125    bool isFakeItalic = wanted.getItalic() && !actual.getItalic();
126    return FontFakery(isFakeBold, isFakeItalic);
127}
128
129FakedFont FontFamily::getClosestMatch(FontStyle style) const {
130    const Font* bestFont = NULL;
131    int bestMatch = 0;
132    for (size_t i = 0; i < mFonts.size(); i++) {
133        const Font& font = mFonts[i];
134        int match = computeMatch(font.style, style);
135        if (i == 0 || match < bestMatch) {
136            bestFont = &font;
137            bestMatch = match;
138        }
139    }
140    FakedFont result;
141    if (bestFont == NULL) {
142        result.font = NULL;
143    } else {
144        result.font = bestFont->typeface;
145        result.fakery = computeFakery(style, bestFont->style);
146    }
147    return result;
148}
149
150size_t FontFamily::getNumFonts() const {
151    return mFonts.size();
152}
153
154MinikinFont* FontFamily::getFont(size_t index) const {
155    return mFonts[index].typeface;
156}
157
158FontStyle FontFamily::getStyle(size_t index) const {
159    return mFonts[index].style;
160}
161
162bool FontFamily::isColorEmojiFamily() const {
163    const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
164    for (size_t i = 0; i < languageList.size(); ++i) {
165        if (languageList[i].hasEmojiFlag()) {
166            return true;
167        }
168    }
169    return false;
170}
171
172const SparseBitSet* FontFamily::getCoverage() {
173    if (!mCoverageValid) {
174        const FontStyle defaultStyle;
175        MinikinFont* typeface = getClosestMatch(defaultStyle).font;
176        const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
177        HbBlob cmapTable(getFontTable(typeface, cmapTag));
178        if (cmapTable.get() == nullptr) {
179            ALOGE("Could not get cmap table size!\n");
180            // Note: This means we will retry on the next call to getCoverage, as we can't store
181            //       the failure. This is fine, as we assume this doesn't really happen in practice.
182            return nullptr;
183        }
184        // TODO: Error check?
185        CmapCoverage::getCoverage(mCoverage, cmapTable.get(), cmapTable.size(), &mHasVSTable);
186#ifdef VERBOSE_DEBUG
187        ALOGD("font coverage length=%d, first ch=%x\n", mCoverage.length(),
188                mCoverage.nextSetBit(0));
189#endif
190        mCoverageValid = true;
191    }
192    return &mCoverage;
193}
194
195bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) {
196    assertMinikinLocked();
197    if (variationSelector != 0 && !mHasVSTable) {
198        // Early exit if the variation selector is specified but the font doesn't have a cmap format
199        // 14 subtable.
200        return false;
201    }
202
203    const FontStyle defaultStyle;
204    MinikinFont* minikinFont = getClosestMatch(defaultStyle).font;
205    hb_font_t* font = getHbFontLocked(minikinFont);
206    uint32_t unusedGlyph;
207    bool result = hb_font_get_glyph(font, codepoint, variationSelector, &unusedGlyph);
208    hb_font_destroy(font);
209    return result;
210}
211
212bool FontFamily::hasVSTable() const {
213    LOG_ALWAYS_FATAL_IF(!mCoverageValid, "Do not call this method before getCoverage() call");
214    return mHasVSTable;
215}
216
217}  // namespace android
218