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 VERBOSE_DEBUG
18
19#define LOG_TAG "Minikin"
20#include <cutils/log.h>
21
22#include "unicode/unistr.h"
23#include "unicode/unorm2.h"
24
25#include "MinikinInternal.h"
26#include <minikin/FontCollection.h>
27
28using std::vector;
29
30namespace android {
31
32template <typename T>
33static inline T max(T a, T b) {
34    return a>b ? a : b;
35}
36
37uint32_t FontCollection::sNextId = 0;
38
39FontCollection::FontCollection(const vector<FontFamily*>& typefaces) :
40    mMaxChar(0) {
41    AutoMutex _l(gMinikinLock);
42    mId = sNextId++;
43    vector<uint32_t> lastChar;
44    size_t nTypefaces = typefaces.size();
45#ifdef VERBOSE_DEBUG
46    ALOGD("nTypefaces = %d\n", nTypefaces);
47#endif
48    const FontStyle defaultStyle;
49    for (size_t i = 0; i < nTypefaces; i++) {
50        FontFamily* family = typefaces[i];
51        MinikinFont* typeface = family->getClosestMatch(defaultStyle).font;
52        if (typeface == NULL) {
53            continue;
54        }
55        family->RefLocked();
56        mFamilies.push_back(family);  // emplace_back would be better
57        const SparseBitSet* coverage = family->getCoverage();
58        mMaxChar = max(mMaxChar, coverage->length());
59        lastChar.push_back(coverage->nextSetBit(0));
60    }
61    nTypefaces = mFamilies.size();
62    LOG_ALWAYS_FATAL_IF(nTypefaces == 0,
63        "Font collection must have at least one valid typeface");
64    size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage;
65    size_t offset = 0;
66    for (size_t i = 0; i < nPages; i++) {
67        Range dummy;
68        mRanges.push_back(dummy);
69        Range* range = &mRanges.back();
70#ifdef VERBOSE_DEBUG
71        ALOGD("i=%d: range start = %d\n", i, offset);
72#endif
73        range->start = offset;
74        for (size_t j = 0; j < nTypefaces; j++) {
75            if (lastChar[j] < (i + 1) << kLogCharsPerPage) {
76                FontFamily* family = mFamilies[j];
77                mFamilyVec.push_back(family);
78                offset++;
79                uint32_t nextChar = family->getCoverage()->nextSetBit((i + 1) << kLogCharsPerPage);
80#ifdef VERBOSE_DEBUG
81                ALOGD("nextChar = %d (j = %d)\n", nextChar, j);
82#endif
83                lastChar[j] = nextChar;
84            }
85        }
86        range->end = offset;
87    }
88}
89
90FontCollection::~FontCollection() {
91    for (size_t i = 0; i < mFamilies.size(); i++) {
92        mFamilies[i]->UnrefLocked();
93    }
94}
95
96// Implement heuristic for choosing best-match font. Here are the rules:
97// 1. If first font in the collection has the character, it wins.
98// 2. If a font matches both language and script, it gets a score of 4.
99// 3. If a font matches just language, it gets a score of 2.
100// 4. Matching the "compact" or "elegant" variant adds one to the score.
101// 5. Highest score wins, with ties resolved to the first font.
102FontFamily* FontCollection::getFamilyForChar(uint32_t ch,
103            FontLanguage lang, int variant) const {
104    if (ch >= mMaxChar) {
105        return NULL;
106    }
107    const Range& range = mRanges[ch >> kLogCharsPerPage];
108#ifdef VERBOSE_DEBUG
109    ALOGD("querying range %d:%d\n", range.start, range.end);
110#endif
111    FontFamily* bestFamily = NULL;
112    int bestScore = -1;
113    for (size_t i = range.start; i < range.end; i++) {
114        FontFamily* family = mFamilyVec[i];
115        if (family->getCoverage()->get(ch)) {
116            // First font family in collection always matches
117            if (mFamilies[0] == family) {
118                return family;
119            }
120            int score = lang.match(family->lang()) * 2;
121            if (variant != 0 && variant == family->variant()) {
122                score++;
123            }
124            if (score > bestScore) {
125                bestScore = score;
126                bestFamily = family;
127            }
128        }
129    }
130    if (bestFamily == NULL && !mFamilyVec.empty()) {
131        UErrorCode errorCode = U_ZERO_ERROR;
132        const UNormalizer2* normalizer = unorm2_getNFDInstance(&errorCode);
133        if (U_SUCCESS(errorCode)) {
134            UChar decomposed[4];
135            int len = unorm2_getRawDecomposition(normalizer, ch, decomposed, 4, &errorCode);
136            if (U_SUCCESS(errorCode) && len > 0) {
137                int off = 0;
138                U16_NEXT_UNSAFE(decomposed, off, ch);
139                return getFamilyForChar(ch, lang, variant);
140            }
141        }
142        bestFamily = mFamilies[0];
143    }
144    return bestFamily;
145}
146
147const uint32_t NBSP = 0xa0;
148const uint32_t ZWJ = 0x200c;
149const uint32_t ZWNJ = 0x200d;
150const uint32_t KEYCAP = 0x20e3;
151
152// Characters where we want to continue using existing font run instead of
153// recomputing the best match in the fallback list.
154static const uint32_t stickyWhitelist[] = { '!', ',', '.', ':', ';', '?', NBSP, ZWJ, ZWNJ, KEYCAP };
155
156static bool isStickyWhitelisted(uint32_t c) {
157    for (size_t i = 0; i < sizeof(stickyWhitelist) / sizeof(stickyWhitelist[0]); i++) {
158        if (stickyWhitelist[i] == c) return true;
159    }
160    return false;
161}
162
163void FontCollection::itemize(const uint16_t *string, size_t string_size, FontStyle style,
164        vector<Run>* result) const {
165    FontLanguage lang = style.getLanguage();
166    int variant = style.getVariant();
167    FontFamily* lastFamily = NULL;
168    Run* run = NULL;
169    int nShorts;
170    for (size_t i = 0; i < string_size; i += nShorts) {
171        nShorts = 1;
172        uint32_t ch = string[i];
173        // sigh, decode UTF-16 by hand here
174        if ((ch & 0xfc00) == 0xd800) {
175            if ((i + 1) < string_size) {
176                ch = 0x10000 + ((ch & 0x3ff) << 10) + (string[i + 1] & 0x3ff);
177                nShorts = 2;
178            }
179        }
180        // Continue using existing font as long as it has coverage and is whitelisted
181        if (lastFamily == NULL
182                || !(isStickyWhitelisted(ch) && lastFamily->getCoverage()->get(ch))) {
183            FontFamily* family = getFamilyForChar(ch, lang, variant);
184            if (i == 0 || family != lastFamily) {
185                size_t start = i;
186                // Workaround for Emoji keycap until we implement per-cluster font
187                // selection: if keycap is found in a different font that also
188                // supports previous char, attach previous char to the new run.
189                // Only handles non-surrogate characters.
190                // Bug 7557244.
191                if (ch == KEYCAP && i && family && family->getCoverage()->get(string[i - 1])) {
192                    run->end--;
193                    if (run->start == run->end) {
194                        result->pop_back();
195                    }
196                    start--;
197                }
198                Run dummy;
199                result->push_back(dummy);
200                run = &result->back();
201                if (family == NULL) {
202                    run->fakedFont.font = NULL;
203                } else {
204                    run->fakedFont = family->getClosestMatch(style);
205                }
206                lastFamily = family;
207                run->start = start;
208            }
209        }
210        run->end = i + nShorts;
211    }
212}
213
214MinikinFont* FontCollection::baseFont(FontStyle style) {
215    return baseFontFaked(style).font;
216}
217
218FakedFont FontCollection::baseFontFaked(FontStyle style) {
219    if (mFamilies.empty()) {
220        return FakedFont();
221    }
222    return mFamilies[0]->getClosestMatch(style);
223}
224
225uint32_t FontCollection::getId() const {
226    return mId;
227}
228
229}  // namespace android
230