blob: 63691094cc8df2b3b30c76b4c4c770809b0f1800 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "Minikin"
#include "minikin/Layout.h"
#include <hb-icu.h>
#include <hb-ot.h>
#include <log/log.h>
#include <unicode/ubidi.h>
#include <unicode/utf16.h>
#include <utils/LruCache.h>
#include <cmath>
#include <iostream>
#include <mutex>
#include <string>
#include <vector>
#include "BidiUtils.h"
#include "LayoutSplitter.h"
#include "LayoutUtils.h"
#include "LocaleListCache.h"
#include "MinikinInternal.h"
#include "minikin/Emoji.h"
#include "minikin/HbUtils.h"
#include "minikin/LayoutCache.h"
#include "minikin/LayoutPieces.h"
#include "minikin/Macros.h"
namespace minikin {
void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
const MinikinPaint& paint, StartHyphenEdit startHyphen,
EndHyphenEdit endHyphen) {
const uint32_t count = range.getLength();
mAdvances.resize(count, 0);
mGlyphs.reserve(count);
for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
startHyphen, endHyphen, this, nullptr, nullptr);
}
}
float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
const MinikinPaint& paint, StartHyphenEdit startHyphen,
EndHyphenEdit endHyphen, float* advances, MinikinRect* bounds) {
float advance = 0;
MinikinRect tmpBounds;
for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
const size_t offset = range.toRangeOffset(runInfo.range.getStart());
float* advancesForRun = advances ? advances + offset : nullptr;
tmpBounds.setEmpty();
float run_advance = doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0,
startHyphen, endHyphen, nullptr, advancesForRun,
bounds ? &tmpBounds : nullptr);
if (bounds) {
bounds->join(tmpBounds, advance, 0);
}
advance += run_advance;
}
return advance;
}
float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
const MinikinPaint& paint, size_t dstStart,
StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
Layout* layout, float* advances, MinikinRect* bounds) {
if (!range.isValid()) {
return 0.0f; // ICU failed to retrieve the bidi run?
}
float advance = 0;
MinikinRect tmpBounds;
for (const auto[context, piece] : LayoutSplitter(textBuf, range, isRtl)) {
// Hyphenation only applies to the start/end of run.
const StartHyphenEdit pieceStartHyphen =
(piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
const EndHyphenEdit pieceEndHyphen =
(piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
float* advancesForRun =
advances ? advances + (piece.getStart() - range.getStart()) : nullptr;
tmpBounds.setEmpty();
float word_advance = doLayoutWord(
textBuf.data() + context.getStart(), piece.getStart() - context.getStart(),
piece.getLength(), context.getLength(), isRtl, paint, piece.getStart() - dstStart,
pieceStartHyphen, pieceEndHyphen, layout, advancesForRun,
bounds ? &tmpBounds : nullptr);
if (bounds) {
bounds->join(tmpBounds, advance, 0);
}
advance += word_advance;
}
return advance;
}
class LayoutAppendFunctor {
public:
LayoutAppendFunctor(Layout* layout, float* advances, uint32_t outOffset, float wordSpacing,
MinikinRect* bounds)
: mLayout(layout),
mAdvances(advances),
mOutOffset(outOffset),
mWordSpacing(wordSpacing),
mBounds(bounds) {}
void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
const MinikinRect& bounds) {
if (mLayout) {
mLayout->appendLayout(layoutPiece, mOutOffset, mWordSpacing);
}
if (mAdvances) {
const std::vector<float>& advances = layoutPiece.advances();
std::copy(advances.begin(), advances.end(), mAdvances);
}
if (mBounds) {
mBounds->join(bounds, mTotalAdvance, 0);
}
mTotalAdvance += layoutPiece.advance();
}
float getTotalAdvance() { return mTotalAdvance; }
private:
Layout* mLayout;
float* mAdvances;
float mTotalAdvance{0};
const uint32_t mOutOffset;
const float mWordSpacing;
MinikinRect* mBounds;
};
float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, const MinikinPaint& paint, size_t bufStart,
StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
float* advances, MinikinRect* bounds) {
float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
float totalAdvance = 0;
const bool boundsCalculation = bounds != nullptr;
const U16StringPiece textBuf(buf, bufSize);
const Range range(start, start + count);
LayoutAppendFunctor f(layout, advances, bufStart, wordSpacing, bounds);
LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
boundsCalculation, f);
totalAdvance = f.getTotalAdvance();
if (wordSpacing != 0) {
totalAdvance += wordSpacing;
if (advances) {
advances[0] += wordSpacing;
}
}
return totalAdvance;
}
void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvance) {
for (size_t i = 0; i < src.glyphCount(); i++) {
mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), mAdvance + src.pointAt(i).x,
src.pointAt(i).y);
}
const std::vector<float>& advances = src.advances();
for (size_t i = 0; i < advances.size(); i++) {
mAdvances[i + start] = advances[i];
if (i == 0) {
mAdvances[start] += extraAdvance;
}
}
mAdvance += src.advance() + extraAdvance;
}
void Layout::purgeCaches() {
LayoutCache::getInstance().clear();
}
} // namespace minikin