blob: bf9469ea4df8981b1da977b7a7a0bba74951e284 [file] [log] [blame]
/*
* Copyright (c) 2012 Google Inc. All rights reserved.
* Copyright (C) 2013 BlackBerry Limited. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform/fonts/shaping/HarfBuzzShaper.h"
#include "platform/Logging.h"
#include "platform/fonts/Font.h"
#include "platform/fonts/FontFallbackIterator.h"
#include "platform/fonts/GlyphBuffer.h"
#include "platform/fonts/UTF16TextIterator.h"
#include "platform/fonts/opentype/OpenTypeCapsSupport.h"
#include "platform/fonts/shaping/CaseMappingHarfBuzzBufferFiller.h"
#include "platform/fonts/shaping/HarfBuzzFace.h"
#include "platform/fonts/shaping/RunSegmenter.h"
#include "platform/fonts/shaping/ShapeResultInlineHeaders.h"
#include "platform/text/Character.h"
#include "platform/text/TextBreakIterator.h"
#include "wtf/Compiler.h"
#include "wtf/MathExtras.h"
#include "wtf/PtrUtil.h"
#include "wtf/text/Unicode.h"
#include <algorithm>
#include <hb.h>
#include <memory>
#include <unicode/uchar.h>
#include <unicode/uscript.h>
namespace blink {
template<typename T>
class HarfBuzzScopedPtr {
STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(HarfBuzzScopedPtr);
public:
typedef void (*DestroyFunction)(T*);
HarfBuzzScopedPtr(T* ptr, DestroyFunction destroy)
: m_ptr(ptr)
, m_destroy(destroy)
{
ASSERT(m_destroy);
}
~HarfBuzzScopedPtr()
{
if (m_ptr)
(*m_destroy)(m_ptr);
}
T* get() { return m_ptr; }
void set(T* ptr) { m_ptr = ptr; }
private:
T* m_ptr;
DestroyFunction m_destroy;
};
static void normalizeCharacters(const TextRun& run, unsigned length, UChar* destination, unsigned* destinationLength)
{
unsigned position = 0;
bool error = false;
const UChar* source;
String stringFor8BitRun;
if (run.is8Bit()) {
stringFor8BitRun = String::make16BitFrom8BitSource(run.characters8(), run.length());
source = stringFor8BitRun.characters16();
} else {
source = run.characters16();
}
*destinationLength = 0;
while (position < length) {
UChar32 character;
U16_NEXT(source, position, length, character);
// Don't normalize tabs as they are not treated as spaces for word-end.
if (run.normalizeSpace() && Character::isNormalizedCanvasSpaceCharacter(character))
character = spaceCharacter;
else if (Character::treatAsSpace(character) && character != noBreakSpaceCharacter)
character = spaceCharacter;
else if (Character::treatAsZeroWidthSpaceInComplexScript(character))
character = zeroWidthSpaceCharacter;
U16_APPEND(destination, *destinationLength, length, character, error);
ASSERT_UNUSED(error, !error);
}
}
HarfBuzzShaper::HarfBuzzShaper(const Font* font, const TextRun& run)
: Shaper(font, run)
, m_normalizedBufferLength(0)
{
m_normalizedBuffer = wrapArrayUnique(new UChar[m_textRun.length() + 1]);
normalizeCharacters(m_textRun, m_textRun.length(), m_normalizedBuffer.get(), &m_normalizedBufferLength);
setFontFeatures();
}
static inline hb_feature_t createFeature(uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4, uint32_t value = 0)
{
return { HB_TAG(c1, c2, c3, c4), value, 0 /* start */, static_cast<unsigned>(-1) /* end */ };
}
void HarfBuzzShaper::setFontFeatures()
{
const FontDescription& description = m_font->getFontDescription();
static hb_feature_t noKern = createFeature('k', 'e', 'r', 'n');
static hb_feature_t noVkrn = createFeature('v', 'k', 'r', 'n');
switch (description.getKerning()) {
case FontDescription::NormalKerning:
// kern/vkrn are enabled by default
break;
case FontDescription::NoneKerning:
m_features.append(description.isVerticalAnyUpright() ? noVkrn : noKern);
break;
case FontDescription::AutoKerning:
break;
}
static hb_feature_t noClig = createFeature('c', 'l', 'i', 'g');
static hb_feature_t noLiga = createFeature('l', 'i', 'g', 'a');
switch (description.commonLigaturesState()) {
case FontDescription::DisabledLigaturesState:
m_features.append(noLiga);
m_features.append(noClig);
break;
case FontDescription::EnabledLigaturesState:
// liga and clig are on by default
break;
case FontDescription::NormalLigaturesState:
break;
}
static hb_feature_t dlig = createFeature('d', 'l', 'i', 'g', 1);
switch (description.discretionaryLigaturesState()) {
case FontDescription::DisabledLigaturesState:
// dlig is off by default
break;
case FontDescription::EnabledLigaturesState:
m_features.append(dlig);
break;
case FontDescription::NormalLigaturesState:
break;
}
static hb_feature_t hlig = createFeature('h', 'l', 'i', 'g', 1);
switch (description.historicalLigaturesState()) {
case FontDescription::DisabledLigaturesState:
// hlig is off by default
break;
case FontDescription::EnabledLigaturesState:
m_features.append(hlig);
break;
case FontDescription::NormalLigaturesState:
break;
}
static hb_feature_t noCalt = createFeature('c', 'a', 'l', 't');
switch (description.contextualLigaturesState()) {
case FontDescription::DisabledLigaturesState:
m_features.append(noCalt);
break;
case FontDescription::EnabledLigaturesState:
// calt is on by default
break;
case FontDescription::NormalLigaturesState:
break;
}
static hb_feature_t hwid = createFeature('h', 'w', 'i', 'd', 1);
static hb_feature_t twid = createFeature('t', 'w', 'i', 'd', 1);
static hb_feature_t qwid = createFeature('q', 'w', 'i', 'd', 1);
switch (description.widthVariant()) {
case HalfWidth:
m_features.append(hwid);
break;
case ThirdWidth:
m_features.append(twid);
break;
case QuarterWidth:
m_features.append(qwid);
break;
case RegularWidth:
break;
}
// font-variant-numeric:
static hb_feature_t lnum = createFeature('l', 'n', 'u', 'm', 1);
if (description.variantNumeric().numericFigureValue() == FontVariantNumeric::LiningNums)
m_features.append(lnum);
static hb_feature_t onum = createFeature('o', 'n', 'u', 'm', 1);
if (description.variantNumeric().numericFigureValue() == FontVariantNumeric::OldstyleNums)
m_features.append(onum);
static hb_feature_t pnum = createFeature('p', 'n', 'u', 'm', 1);
if (description.variantNumeric().numericSpacingValue() == FontVariantNumeric::ProportionalNums)
m_features.append(pnum);
static hb_feature_t tnum = createFeature('t', 'n', 'u', 'm', 1);
if (description.variantNumeric().numericSpacingValue() == FontVariantNumeric::TabularNums)
m_features.append(tnum);
static hb_feature_t afrc = createFeature('a', 'f', 'r', 'c', 1);
if (description.variantNumeric().numericFractionValue() == FontVariantNumeric::StackedFractions)
m_features.append(afrc);
static hb_feature_t frac = createFeature('f', 'r', 'a', 'c', 1);
if (description.variantNumeric().numericFractionValue() == FontVariantNumeric::DiagonalFractions)
m_features.append(frac);
static hb_feature_t ordn = createFeature('o', 'r', 'd', 'n', 1);
if (description.variantNumeric().ordinalValue() == FontVariantNumeric::OrdinalOn)
m_features.append(ordn);
static hb_feature_t zero = createFeature('z', 'e', 'r', 'o', 1);
if (description.variantNumeric().slashedZeroValue() == FontVariantNumeric::SlashedZeroOn)
m_features.append(zero);
FontFeatureSettings* settings = description.featureSettings();
if (!settings)
return;
// TODO(drott): crbug.com/450619 Implement feature resolution instead of
// just appending the font-feature-settings.
unsigned numFeatures = settings->size();
for (unsigned i = 0; i < numFeatures; ++i) {
hb_feature_t feature;
const AtomicString& tag = settings->at(i).tag();
feature.tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]);
feature.value = settings->at(i).value();
feature.start = 0;
feature.end = static_cast<unsigned>(-1);
m_features.append(feature);
}
}
HarfBuzzShaper::CapsFeatureSettingsScopedOverlay::CapsFeatureSettingsScopedOverlay(
FeaturesVector& features,
FontDescription::FontVariantCaps variantCaps)
: m_features(features)
, m_countFeatures(0)
{
overlayCapsFeatures(variantCaps);
}
void HarfBuzzShaper::CapsFeatureSettingsScopedOverlay::overlayCapsFeatures(
FontDescription::FontVariantCaps variantCaps)
{
static hb_feature_t smcp = createFeature('s', 'm', 'c', 'p', 1);
static hb_feature_t pcap = createFeature('p', 'c', 'a', 'p', 1);
static hb_feature_t c2sc = createFeature('c', '2', 's', 'c', 1);
static hb_feature_t c2pc = createFeature('c', '2', 'p', 'c', 1);
static hb_feature_t unic = createFeature('u', 'n', 'i', 'c', 1);
static hb_feature_t titl = createFeature('t', 'i', 't', 'l', 1);
if (variantCaps == FontDescription::SmallCaps
|| variantCaps == FontDescription::AllSmallCaps) {
prependCounting(smcp);
if (variantCaps == FontDescription::AllSmallCaps) {
prependCounting(c2sc);
}
}
if (variantCaps == FontDescription::PetiteCaps
|| variantCaps == FontDescription::AllPetiteCaps) {
prependCounting(pcap);
if (variantCaps == FontDescription::AllPetiteCaps) {
prependCounting(c2pc);
}
}
if (variantCaps == FontDescription::Unicase) {
prependCounting(unic);
}
if (variantCaps == FontDescription::TitlingCaps) {
prependCounting(titl);
}
}
void HarfBuzzShaper::CapsFeatureSettingsScopedOverlay::prependCounting(const hb_feature_t& feature)
{
m_features.prepend(feature);
m_countFeatures++;
}
HarfBuzzShaper::CapsFeatureSettingsScopedOverlay::~CapsFeatureSettingsScopedOverlay()
{
m_features.remove(0, m_countFeatures);
}
// A port of hb_icu_script_to_script because harfbuzz on CrOS is built
// without hb-icu. See http://crbug.com/356929
static inline hb_script_t ICUScriptToHBScript(UScriptCode script)
{
if (UNLIKELY(script == USCRIPT_INVALID_CODE))
return HB_SCRIPT_INVALID;
return hb_script_from_string(uscript_getShortName(script), -1);
}
static inline hb_direction_t TextDirectionToHBDirection(TextDirection dir, FontOrientation orientation, const SimpleFontData* fontData)
{
hb_direction_t harfBuzzDirection = isVerticalAnyUpright(orientation) && !fontData->isTextOrientationFallback() ? HB_DIRECTION_TTB : HB_DIRECTION_LTR;
return dir == RTL ? HB_DIRECTION_REVERSE(harfBuzzDirection) : harfBuzzDirection;
}
inline bool HarfBuzzShaper::shapeRange(hb_buffer_t* harfBuzzBuffer,
unsigned startIndex,
unsigned numCharacters,
const SimpleFontData* currentFont,
PassRefPtr<UnicodeRangeSet> currentFontRangeSet,
UScriptCode currentRunScript,
hb_language_t language)
{
const FontPlatformData* platformData = &(currentFont->platformData());
HarfBuzzFace* face = platformData->harfBuzzFace();
if (!face) {
DLOG(ERROR) << "Could not create HarfBuzzFace from FontPlatformData.";
return false;
}
hb_buffer_set_language(harfBuzzBuffer, language);
hb_buffer_set_script(harfBuzzBuffer, ICUScriptToHBScript(currentRunScript));
hb_buffer_set_direction(harfBuzzBuffer, TextDirectionToHBDirection(m_textRun.direction(),
m_font->getFontDescription().orientation(), currentFont));
hb_font_t* hbFont = face->getScaledFont(currentFontRangeSet);
hb_shape(hbFont, harfBuzzBuffer, m_features.isEmpty() ? 0 : m_features.data(), m_features.size());
return true;
}
bool HarfBuzzShaper::extractShapeResults(hb_buffer_t* harfBuzzBuffer,
ShapeResult* shapeResult,
bool& fontCycleQueued, const HolesQueueItem& currentQueueItem,
const SimpleFontData* currentFont,
UScriptCode currentRunScript,
bool isLastResort)
{
enum ClusterResult {
Shaped,
NotDef,
Unknown
};
ClusterResult currentClusterResult = Unknown;
ClusterResult previousClusterResult = Unknown;
unsigned previousCluster = 0;
unsigned currentCluster = 0;
// Find first notdef glyph in harfBuzzBuffer.
unsigned numGlyphs = hb_buffer_get_length(harfBuzzBuffer);
hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0);
unsigned lastChangePosition = 0;
if (!numGlyphs) {
DLOG(ERROR) << "HarfBuzz returned empty glyph buffer after shaping.";
return false;
}
for (unsigned glyphIndex = 0; glyphIndex <= numGlyphs; ++glyphIndex) {
// Iterating by clusters, check for when the state switches from shaped
// to non-shaped and vice versa. Taking into account the edge cases of
// beginning of the run and end of the run.
previousCluster = currentCluster;
currentCluster = glyphInfo[glyphIndex].cluster;
if (glyphIndex < numGlyphs) {
// Still the same cluster, merge shaping status.
if (previousCluster == currentCluster && glyphIndex != 0) {
if (glyphInfo[glyphIndex].codepoint == 0) {
currentClusterResult = NotDef;
} else {
// We can only call the current cluster fully shapped, if
// all characters that are part of it are shaped, so update
// currentClusterResult to Shaped only if the previous
// characters have been shaped, too.
currentClusterResult = currentClusterResult == Shaped ? Shaped : NotDef;
}
continue;
}
// We've moved to a new cluster.
previousClusterResult = currentClusterResult;
currentClusterResult = glyphInfo[glyphIndex].codepoint == 0 ? NotDef : Shaped;
} else {
// The code below operates on the "flanks"/changes between NotDef
// and Shaped. In order to keep the code below from explictly
// dealing with character indices and run end, we explicitly
// terminate the cluster/run here by setting the result value to the
// opposite of what it was, leading to atChange turning true.
previousClusterResult = currentClusterResult;
currentClusterResult = currentClusterResult == NotDef ? Shaped : NotDef;
}
bool atChange = (previousClusterResult != currentClusterResult) && previousClusterResult != Unknown;
if (!atChange)
continue;
// Compute the range indices of consecutive shaped or .notdef glyphs.
// Cluster information for RTL runs becomes reversed, e.g. character 0
// has cluster index 5 in a run of 6 characters.
unsigned numCharacters = 0;
unsigned numGlyphsToInsert = 0;
unsigned startIndex = 0;
if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))) {
startIndex = glyphInfo[lastChangePosition].cluster;
if (glyphIndex == numGlyphs) {
numCharacters = currentQueueItem.m_startIndex + currentQueueItem.m_numCharacters - glyphInfo[lastChangePosition].cluster;
numGlyphsToInsert = numGlyphs - lastChangePosition;
} else {
numCharacters = glyphInfo[glyphIndex].cluster - glyphInfo[lastChangePosition].cluster;
numGlyphsToInsert = glyphIndex - lastChangePosition;
}
} else {
// Direction Backwards
startIndex = glyphInfo[glyphIndex - 1].cluster;
if (lastChangePosition == 0) {
numCharacters = currentQueueItem.m_startIndex + currentQueueItem.m_numCharacters - glyphInfo[glyphIndex - 1].cluster;
} else {
numCharacters = glyphInfo[lastChangePosition - 1].cluster - glyphInfo[glyphIndex - 1].cluster;
}
numGlyphsToInsert = glyphIndex - lastChangePosition;
}
if (currentClusterResult == Shaped && !isLastResort) {
// Now it's clear that we need to continue processing.
if (!fontCycleQueued) {
appendToHolesQueue(HolesQueueNextFont, 0, 0);
fontCycleQueued = true;
}
// Here we need to put character positions.
ASSERT(numCharacters);
appendToHolesQueue(HolesQueueRange, startIndex, numCharacters);
}
// If numCharacters is 0, that means we hit a NotDef before shaping the
// whole grapheme. We do not append it here. For the next glyph we
// encounter, atChange will be true, and the characters corresponding to
// the grapheme will be added to the TODO queue again, attempting to
// shape the whole grapheme with the next font.
// When we're getting here with the last resort font, we have no other
// choice than adding boxes to the ShapeResult.
if ((currentClusterResult == NotDef && numCharacters) || isLastResort) {
hb_direction_t direction = TextDirectionToHBDirection(
m_textRun.direction(),
m_font->getFontDescription().orientation(), currentFont);
// Here we need to specify glyph positions.
ShapeResult::RunInfo* run = new ShapeResult::RunInfo(currentFont,
direction, ICUScriptToHBScript(currentRunScript),
startIndex, numGlyphsToInsert, numCharacters);
shapeResult->insertRun(wrapUnique(run), lastChangePosition,
numGlyphsToInsert,
harfBuzzBuffer);
}
lastChangePosition = glyphIndex;
}
return true;
}
static inline const SimpleFontData* fontDataAdjustedForOrientation(const SimpleFontData* originalFont,
FontOrientation runOrientation,
OrientationIterator::RenderOrientation renderOrientation)
{
if (!isVerticalBaseline(runOrientation))
return originalFont;
if (runOrientation == FontOrientation::VerticalRotated
|| (runOrientation == FontOrientation::VerticalMixed && renderOrientation == OrientationIterator::OrientationRotateSideways))
return originalFont->verticalRightOrientationFontData().get();
return originalFont;
}
bool HarfBuzzShaper::collectFallbackHintChars(Vector<UChar32>& hint, bool needsList)
{
if (!m_holesQueue.size())
return false;
hint.clear();
size_t numCharsAdded = 0;
for (auto it = m_holesQueue.begin(); it != m_holesQueue.end(); ++it) {
if (it->m_action == HolesQueueNextFont)
break;
UChar32 hintChar;
RELEASE_ASSERT(it->m_startIndex + it->m_numCharacters <= m_normalizedBufferLength);
UTF16TextIterator iterator(m_normalizedBuffer.get() + it->m_startIndex, it->m_numCharacters);
while (iterator.consume(hintChar)) {
hint.append(hintChar);
numCharsAdded++;
if (!needsList)
break;
iterator.advance();
}
}
return numCharsAdded > 0;
}
void HarfBuzzShaper::appendToHolesQueue(HolesQueueItemAction action,
unsigned startIndex,
unsigned numCharacters)
{
m_holesQueue.append(HolesQueueItem(action, startIndex, numCharacters));
}
void HarfBuzzShaper::prependHolesQueue(HolesQueueItemAction action,
unsigned startIndex,
unsigned numCharacters)
{
m_holesQueue.prepend(HolesQueueItem(action, startIndex, numCharacters));
}
void HarfBuzzShaper::splitUntilNextCaseChange(HolesQueueItem& currentQueueItem, SmallCapsIterator::SmallCapsBehavior& smallCapsBehavior)
{
unsigned numCharactersUntilCaseChange = 0;
SmallCapsIterator smallCapsIterator(
m_normalizedBuffer.get() + currentQueueItem.m_startIndex,
currentQueueItem.m_numCharacters);
smallCapsIterator.consume(&numCharactersUntilCaseChange, &smallCapsBehavior);
if (numCharactersUntilCaseChange > 0 && numCharactersUntilCaseChange < currentQueueItem.m_numCharacters) {
prependHolesQueue(HolesQueueRange,
currentQueueItem.m_startIndex + numCharactersUntilCaseChange,
currentQueueItem.m_numCharacters - numCharactersUntilCaseChange);
currentQueueItem.m_numCharacters = numCharactersUntilCaseChange;
}
}
PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult()
{
RefPtr<ShapeResult> result = ShapeResult::create(m_font,
m_normalizedBufferLength, m_textRun.direction());
HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy);
const FontDescription& fontDescription = m_font->getFontDescription();
const String& localeString = fontDescription.locale();
CString locale = localeString.latin1();
const hb_language_t language = hb_language_from_string(locale.data(), locale.length());
bool needsCapsHandling = fontDescription.variantCaps() != FontDescription::CapsNormal;
OpenTypeCapsSupport capsSupport;
RunSegmenter::RunSegmenterRange segmentRange = {
0,
0,
USCRIPT_INVALID_CODE,
OrientationIterator::OrientationInvalid,
FontFallbackPriority::Invalid };
RunSegmenter runSegmenter(
m_normalizedBuffer.get(),
m_normalizedBufferLength,
m_font->getFontDescription().orientation());
Vector<UChar32> fallbackCharsHint;
// TODO: Check whether this treatAsZerowidthspace from the previous script
// segmentation plays a role here, does the new scriptRuniterator handle that correctly?
while (runSegmenter.consume(&segmentRange)) {
RefPtr<FontFallbackIterator> fallbackIterator =
m_font->createFontFallbackIterator(
segmentRange.fontFallbackPriority);
appendToHolesQueue(HolesQueueNextFont, 0, 0);
appendToHolesQueue(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start);
RefPtr<FontDataForRangeSet> currentFontDataForRangeSet;
bool fontCycleQueued = false;
while (m_holesQueue.size()) {
HolesQueueItem currentQueueItem = m_holesQueue.takeFirst();
if (currentQueueItem.m_action == HolesQueueNextFont) {
// For now, we're building a character list with which we probe
// for needed fonts depending on the declared unicode-range of a
// segmented CSS font. Alternatively, we can build a fake font
// for the shaper and check whether any glyphs were found, or
// define a new API on the shaper which will give us coverage
// information?
if (!collectFallbackHintChars(fallbackCharsHint, fallbackIterator->needsHintList())) {
// Give up shaping since we cannot retrieve a font fallback
// font without a hintlist.
m_holesQueue.clear();
break;
}
currentFontDataForRangeSet = fallbackIterator->next(fallbackCharsHint);
if (!currentFontDataForRangeSet->fontData()) {
ASSERT(!m_holesQueue.size());
break;
}
fontCycleQueued = false;
continue;
}
SmallCapsIterator::SmallCapsBehavior smallCapsBehavior = SmallCapsIterator::SmallCapsSameCase;
if (needsCapsHandling) {
capsSupport = OpenTypeCapsSupport(
currentFontDataForRangeSet->fontData()->platformData().harfBuzzFace(),
fontDescription.variantCaps(),
ICUScriptToHBScript(segmentRange.script));
if (capsSupport.needsRunCaseSplitting())
splitUntilNextCaseChange(currentQueueItem, smallCapsBehavior);
}
ASSERT(currentQueueItem.m_numCharacters);
const SimpleFontData* smallcapsAdjustedFont = needsCapsHandling
&& capsSupport.needsSyntheticFont(smallCapsBehavior)
? currentFontDataForRangeSet->fontData()->smallCapsFontData(fontDescription).get()
: currentFontDataForRangeSet->fontData();
// Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction.
// TODO: crbug.com/506224 This should go away in favor of storing that information elsewhere, for example in
// ShapeResult.
const SimpleFontData* directionAndSmallCapsAdjustedFont = fontDataAdjustedForOrientation(smallcapsAdjustedFont,
m_font->getFontDescription().orientation(),
segmentRange.renderOrientation);
CaseMapIntend caseMapIntend = CaseMapIntend::KeepSameCase;
if (needsCapsHandling) {
caseMapIntend = capsSupport.needsCaseChange(smallCapsBehavior);
}
CaseMappingHarfBuzzBufferFiller(
caseMapIntend,
fontDescription.locale(),
harfBuzzBuffer.get(),
m_normalizedBuffer.get(),
m_normalizedBufferLength,
currentQueueItem.m_startIndex,
currentQueueItem.m_numCharacters);
CapsFeatureSettingsScopedOverlay capsOverlay(m_features, capsSupport.fontFeatureToUse(smallCapsBehavior));
if (!shapeRange(harfBuzzBuffer.get(),
currentQueueItem.m_startIndex,
currentQueueItem.m_numCharacters,
directionAndSmallCapsAdjustedFont,
currentFontDataForRangeSet->ranges(),
segmentRange.script,
language))
DLOG(ERROR) << "Shaping range failed.";
if (!extractShapeResults(harfBuzzBuffer.get(),
result.get(),
fontCycleQueued,
currentQueueItem,
directionAndSmallCapsAdjustedFont,
segmentRange.script,
!fallbackIterator->hasNext()))
DLOG(ERROR) << "Shape result extraction failed.";
hb_buffer_reset(harfBuzzBuffer.get());
}
}
return result.release();
}
PassRefPtr<ShapeResult> ShapeResult::createForTabulationCharacters(const Font* font,
const TextRun& textRun, float positionOffset, unsigned count)
{
const SimpleFontData* fontData = font->primaryFont();
std::unique_ptr<ShapeResult::RunInfo> run = wrapUnique(new ShapeResult::RunInfo(fontData,
// Tab characters are always LTR or RTL, not TTB, even when isVerticalAnyUpright().
textRun.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
HB_SCRIPT_COMMON, 0, count, count));
float position = textRun.xPos() + positionOffset;
float startPosition = position;
for (unsigned i = 0; i < count; i++) {
float advance = font->tabWidth(*fontData, textRun.getTabSize(), position);
run->m_glyphData[i].characterIndex = i;
run->setGlyphAndPositions(i, fontData->spaceGlyph(), advance, 0, 0);
position += advance;
}
run->m_width = position - startPosition;
RefPtr<ShapeResult> result = ShapeResult::create(font, count, textRun.direction());
result->m_width = run->m_width;
result->m_numGlyphs = count;
ASSERT(result->m_numGlyphs == count); // no overflow
result->m_hasVerticalOffsets = fontData->platformData().isVerticalAnyUpright();
result->m_runs.append(std::move(run));
return result.release();
}
} // namespace blink