blob: e30fba0db66c2191f862bb465c381d47f1cbd703 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2003, 2006, 2010, 2011 Apple Inc. All rights reserved.
* Copyright (c) 2007, 2008, 2010 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h"
#include "third_party/blink/renderer/platform/fonts/character_range.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/font_fallback_list.h"
#include "third_party/blink/renderer/platform/fonts/font_fallback_map.h"
#include "third_party/blink/renderer/platform/fonts/ng_text_fragment_paint_info.h"
#include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
#include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
#include "third_party/blink/renderer/platform/text/bidi_resolver.h"
#include "third_party/blink/renderer/platform/text/character.h"
#include "third_party/blink/renderer/platform/text/text_run.h"
#include "third_party/blink/renderer/platform/text/text_run_iterator.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
#include "third_party/blink/renderer/platform/wtf/text/unicode.h"
#include "third_party/skia/include/core/SkTextBlob.h"
namespace blink {
namespace {
FontFallbackMap& GetFontFallbackMap(FontSelector* font_selector) {
if (font_selector)
return font_selector->GetFontFallbackMap();
return FontCache::GetFontCache()->GetFontFallbackMap();
}
scoped_refptr<FontFallbackList> GetOrCreateFontFallbackList(
const FontDescription& font_description,
FontSelector* font_selector) {
return GetFontFallbackMap(font_selector).Get(font_description);
}
} // namespace
Font::Font() = default;
Font::Font(const FontDescription& fd) : font_description_(fd) {}
Font::Font(const FontDescription& font_description, FontSelector* font_selector)
: font_description_(font_description),
font_fallback_list_(
font_selector
? GetOrCreateFontFallbackList(font_description, font_selector)
: nullptr) {}
Font::Font(const Font& other) = default;
Font& Font::operator=(const Font& other) {
if (this == &other || *this == other)
return *this;
ReleaseFontFallbackListRef();
font_description_ = other.font_description_;
font_fallback_list_ = other.font_fallback_list_;
return *this;
}
Font::~Font() {
ReleaseFontFallbackListRef();
}
// Ensures that FontFallbackMap only keeps FontFallbackLists that are still in
// use by at least one Font object. If the last Font releases its reference, we
// should clear the entry from FontFallbackMap.
// Note that we must not persist a FontFallbackList reference outside Font.
void Font::ReleaseFontFallbackListRef() const {
if (!font_fallback_list_ || !font_fallback_list_->IsValid() ||
!font_fallback_list_->HasFontFallbackMap()) {
font_fallback_list_.reset();
return;
}
FontFallbackList& list_ref = *font_fallback_list_;
// Failing this CHECK causes use-after-free below.
CHECK(!list_ref.HasOneRef());
font_fallback_list_.reset();
if (list_ref.HasOneRef())
list_ref.GetFontFallbackMap().Remove(font_description_);
}
void Font::RevalidateFontFallbackList() const {
DCHECK(font_fallback_list_);
font_fallback_list_ =
font_fallback_list_->GetFontFallbackMap().Get(font_description_);
}
FontFallbackList* Font::EnsureFontFallbackList() const {
if (!font_fallback_list_) {
font_fallback_list_ =
GetOrCreateFontFallbackList(font_description_, nullptr);
}
if (!font_fallback_list_->IsValid())
RevalidateFontFallbackList();
return font_fallback_list_.get();
}
bool Font::operator==(const Font& other) const {
// Two Font objects with the same FontDescription and FontSelector should
// always hold reference to the same FontFallbackList object, unless
// invalidated.
if (font_fallback_list_ && font_fallback_list_->IsValid() &&
other.font_fallback_list_ && other.font_fallback_list_->IsValid()) {
return font_fallback_list_ == other.font_fallback_list_;
}
FontSelector* first =
font_fallback_list_ ? font_fallback_list_->GetFontSelector() : nullptr;
FontSelector* second = other.font_fallback_list_
? other.font_fallback_list_->GetFontSelector()
: nullptr;
return first == second && font_description_ == other.font_description_;
}
namespace {
void DrawBlobs(cc::PaintCanvas* canvas,
const cc::PaintFlags& flags,
const ShapeResultBloberizer::BlobBuffer& blobs,
const FloatPoint& point,
cc::NodeId node_id = cc::kInvalidNodeId) {
for (const auto& blob_info : blobs) {
DCHECK(blob_info.blob);
cc::PaintCanvasAutoRestore auto_restore(canvas, false);
switch (blob_info.rotation) {
case CanvasRotationInVertical::kRegular:
break;
case CanvasRotationInVertical::kRotateCanvasUpright: {
canvas->save();
SkMatrix m;
m.setSinCos(-1, 0, point.X(), point.Y());
canvas->concat(m);
break;
}
case CanvasRotationInVertical::kRotateCanvasUprightOblique: {
canvas->save();
SkMatrix m;
m.setSinCos(-1, 0, point.X(), point.Y());
// TODO(yosin): We should use angle specified in CSS instead of
// constant value -15deg.
// Note: We draw glyph in right-top corner upper.
// See CSS "transform: skew(0, -15deg)"
SkMatrix skewY;
constexpr SkScalar kSkewY = -0.2679491924311227; // tan(-15deg)
skewY.setSkew(0, kSkewY, point.X(), point.Y());
m.preConcat(skewY);
canvas->concat(m);
break;
}
case CanvasRotationInVertical::kOblique: {
// TODO(yosin): We should use angle specified in CSS instead of
// constant value 15deg.
// Note: We draw glyph in right-top corner upper.
// See CSS "transform: skew(0, -15deg)"
canvas->save();
SkMatrix skewX;
constexpr SkScalar kSkewX = 0.2679491924311227; // tan(15deg)
skewX.setSkew(kSkewX, 0, point.X(), point.Y());
canvas->concat(skewX);
break;
}
}
if (node_id != cc::kInvalidNodeId) {
canvas->drawTextBlob(blob_info.blob, point.X(), point.Y(), node_id,
flags);
} else {
canvas->drawTextBlob(blob_info.blob, point.X(), point.Y(), flags);
}
}
}
} // anonymous ns
void Font::DrawText(cc::PaintCanvas* canvas,
const TextRunPaintInfo& run_info,
const FloatPoint& point,
float device_scale_factor,
const cc::PaintFlags& flags) const {
DrawText(canvas, run_info, point, device_scale_factor, cc::kInvalidNodeId,
flags);
}
void Font::DrawText(cc::PaintCanvas* canvas,
const TextRunPaintInfo& run_info,
const FloatPoint& point,
float device_scale_factor,
cc::NodeId node_id,
const cc::PaintFlags& flags) const {
// Don't draw anything while we are using custom fonts that are in the process
// of loading.
if (ShouldSkipDrawing())
return;
ShapeResultBloberizer bloberizer(*this, device_scale_factor);
CachingWordShaper word_shaper(*this);
ShapeResultBuffer buffer;
word_shaper.FillResultBuffer(run_info, &buffer);
bloberizer.FillGlyphs(run_info, buffer);
DrawBlobs(canvas, flags, bloberizer.Blobs(), point, node_id);
}
void Font::DrawText(cc::PaintCanvas* canvas,
const NGTextFragmentPaintInfo& text_info,
const FloatPoint& point,
float device_scale_factor,
cc::NodeId node_id,
const cc::PaintFlags& flags) const {
// Don't draw anything while we are using custom fonts that are in the process
// of loading.
if (ShouldSkipDrawing())
return;
ShapeResultBloberizer bloberizer(*this, device_scale_factor);
bloberizer.FillGlyphs(text_info.text, text_info.from, text_info.to,
text_info.shape_result);
DrawBlobs(canvas, flags, bloberizer.Blobs(), point, node_id);
}
bool Font::DrawBidiText(cc::PaintCanvas* canvas,
const TextRunPaintInfo& run_info,
const FloatPoint& point,
CustomFontNotReadyAction custom_font_not_ready_action,
float device_scale_factor,
const cc::PaintFlags& flags) const {
// Don't draw anything while we are using custom fonts that are in the process
// of loading, except if the 'force' argument is set to true (in which case it
// will use a fallback font).
if (ShouldSkipDrawing() &&
custom_font_not_ready_action == kDoNotPaintIfFontNotReady)
return false;
// sub-run painting is not supported for Bidi text.
const TextRun& run = run_info.run;
DCHECK_EQ(run_info.from, 0u);
DCHECK_EQ(run_info.to, run.length());
BidiResolver<TextRunIterator, BidiCharacterRun> bidi_resolver;
bidi_resolver.SetStatus(
BidiStatus(run.Direction(), run.DirectionalOverride()));
bidi_resolver.SetPositionIgnoringNestedIsolates(TextRunIterator(&run, 0));
// FIXME: This ownership should be reversed. We should pass BidiRunList
// to BidiResolver in createBidiRunsForLine.
BidiRunList<BidiCharacterRun>& bidi_runs = bidi_resolver.Runs();
bidi_resolver.CreateBidiRunsForLine(TextRunIterator(&run, run.length()));
if (!bidi_runs.RunCount())
return true;
FloatPoint curr_point = point;
BidiCharacterRun* bidi_run = bidi_runs.FirstRun();
CachingWordShaper word_shaper(*this);
while (bidi_run) {
TextRun subrun =
run.SubRun(bidi_run->Start(), bidi_run->Stop() - bidi_run->Start());
bool is_rtl = bidi_run->Level() % 2;
subrun.SetDirection(is_rtl ? TextDirection::kRtl : TextDirection::kLtr);
subrun.SetDirectionalOverride(bidi_run->DirOverride(false));
TextRunPaintInfo subrun_info(subrun);
// Fix regression with -ftrivial-auto-var-init=pattern. See
// crbug.com/1055652.
STACK_UNINITIALIZED ShapeResultBloberizer bloberizer(*this,
device_scale_factor);
ShapeResultBuffer buffer;
word_shaper.FillResultBuffer(subrun_info, &buffer);
float run_width = bloberizer.FillGlyphs(subrun_info, buffer);
DrawBlobs(canvas, flags, bloberizer.Blobs(), curr_point);
bidi_run = bidi_run->Next();
curr_point.Move(run_width, 0);
}
bidi_runs.DeleteRuns();
return true;
}
void Font::DrawEmphasisMarks(cc::PaintCanvas* canvas,
const TextRunPaintInfo& run_info,
const AtomicString& mark,
const FloatPoint& point,
float device_scale_factor,
const cc::PaintFlags& flags) const {
if (ShouldSkipDrawing())
return;
FontCachePurgePreventer purge_preventer;
const auto emphasis_glyph_data = GetEmphasisMarkGlyphData(mark);
if (!emphasis_glyph_data.font_data)
return;
ShapeResultBloberizer bloberizer(*this, device_scale_factor);
CachingWordShaper word_shaper(*this);
ShapeResultBuffer buffer;
word_shaper.FillResultBuffer(run_info, &buffer);
bloberizer.FillTextEmphasisGlyphs(run_info, emphasis_glyph_data, buffer);
DrawBlobs(canvas, flags, bloberizer.Blobs(), point);
}
void Font::DrawEmphasisMarks(cc::PaintCanvas* canvas,
const NGTextFragmentPaintInfo& text_info,
const AtomicString& mark,
const FloatPoint& point,
float device_scale_factor,
const cc::PaintFlags& flags) const {
if (ShouldSkipDrawing())
return;
FontCachePurgePreventer purge_preventer;
const auto emphasis_glyph_data = GetEmphasisMarkGlyphData(mark);
if (!emphasis_glyph_data.font_data)
return;
ShapeResultBloberizer bloberizer(*this, device_scale_factor);
bloberizer.FillTextEmphasisGlyphs(text_info.text, text_info.from,
text_info.to, emphasis_glyph_data,
text_info.shape_result);
DrawBlobs(canvas, flags, bloberizer.Blobs(), point);
}
FloatRect Font::TextInkBounds(const NGTextFragmentPaintInfo& text_info) const {
// No need to compute bounds if using custom fonts that are in the process
// of loading as it won't be painted.
if (ShouldSkipDrawing())
return FloatRect();
// NOTE(eae): We could use the SkTextBlob::bounds API [1] however by default
// it returns conservative bounds (rather than tight bounds) which are
// unsuitable for our needs. If we could get the tight bounds from Skia that
// would be quite a bit faster than the two-stage approach employed by the
// ShapeResultView::ComputeInkBounds method.
// 1: https://skia.org/user/api/SkTextBlob_Reference#SkTextBlob_bounds
return text_info.shape_result->ComputeInkBounds();
}
float Font::Width(const TextRun& run,
HashSet<const SimpleFontData*>* fallback_fonts,
FloatRect* glyph_bounds) const {
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
return shaper.Width(run, fallback_fonts, glyph_bounds);
}
namespace { // anonymous namespace
unsigned InterceptsFromBlobs(const ShapeResultBloberizer::BlobBuffer& blobs,
const SkPaint& paint,
const std::tuple<float, float>& bounds,
SkScalar* intercepts_buffer) {
SkScalar bounds_array[2] = {std::get<0>(bounds), std::get<1>(bounds)};
unsigned num_intervals = 0;
for (const auto& blob_info : blobs) {
DCHECK(blob_info.blob);
// ShapeResultBloberizer splits for a new blob rotation, but does not split
// for a change in font. A TextBlob can contain runs with differing fonts
// and the getTextBlobIntercepts method handles multiple fonts for us. For
// upright in vertical blobs we currently have to bail, see crbug.com/655154
if (IsCanvasRotationInVerticalUpright(blob_info.rotation))
continue;
SkScalar* offset_intercepts_buffer = nullptr;
if (intercepts_buffer)
offset_intercepts_buffer = &intercepts_buffer[num_intervals];
num_intervals += blob_info.blob->getIntercepts(
bounds_array, offset_intercepts_buffer, &paint);
}
return num_intervals;
}
void GetTextInterceptsInternal(const ShapeResultBloberizer::BlobBuffer& blobs,
const cc::PaintFlags& flags,
const std::tuple<float, float>& bounds,
Vector<Font::TextIntercept>& intercepts) {
// Get the number of intervals, without copying the actual values by
// specifying nullptr for the buffer, following the Skia allocation model for
// retrieving text intercepts.
SkPaint paint = flags.ToSkPaint();
unsigned num_intervals = InterceptsFromBlobs(blobs, paint, bounds, nullptr);
if (!num_intervals)
return;
DCHECK_EQ(num_intervals % 2, 0u);
intercepts.resize(num_intervals / 2u);
InterceptsFromBlobs(blobs, paint, bounds,
reinterpret_cast<SkScalar*>(intercepts.data()));
}
} // anonymous namespace
void Font::GetTextIntercepts(const TextRunPaintInfo& run_info,
float device_scale_factor,
const cc::PaintFlags& flags,
const std::tuple<float, float>& bounds,
Vector<TextIntercept>& intercepts) const {
if (ShouldSkipDrawing())
return;
ShapeResultBloberizer bloberizer(
*this, device_scale_factor, ShapeResultBloberizer::Type::kTextIntercepts);
CachingWordShaper word_shaper(*this);
ShapeResultBuffer buffer;
word_shaper.FillResultBuffer(run_info, &buffer);
bloberizer.FillGlyphs(run_info, buffer);
GetTextInterceptsInternal(bloberizer.Blobs(), flags, bounds, intercepts);
}
void Font::GetTextIntercepts(const NGTextFragmentPaintInfo& text_info,
float device_scale_factor,
const cc::PaintFlags& flags,
const std::tuple<float, float>& bounds,
Vector<TextIntercept>& intercepts) const {
if (ShouldSkipDrawing())
return;
ShapeResultBloberizer bloberizer(
*this, device_scale_factor, ShapeResultBloberizer::Type::kTextIntercepts);
bloberizer.FillGlyphs(text_info.text, text_info.from, text_info.to,
text_info.shape_result);
GetTextInterceptsInternal(bloberizer.Blobs(), flags, bounds, intercepts);
}
static inline FloatRect PixelSnappedSelectionRect(FloatRect rect) {
// Using roundf() rather than ceilf() for the right edge as a compromise to
// ensure correct caret positioning.
float rounded_x = roundf(rect.X());
return FloatRect(rounded_x, rect.Y(), roundf(rect.MaxX() - rounded_x),
rect.Height());
}
FloatRect Font::SelectionRectForText(const TextRun& run,
const FloatPoint& point,
float height,
int from,
int to) const {
to = (to == -1 ? run.length() : to);
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
CharacterRange range = shaper.GetCharacterRange(run, from, to);
return PixelSnappedSelectionRect(
FloatRect(point.X() + range.start, point.Y(), range.Width(), height));
}
int Font::OffsetForPosition(const TextRun& run,
float x_float,
IncludePartialGlyphsOption partial_glyphs,
BreakGlyphsOption break_glyphs) const {
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
return shaper.OffsetForPosition(run, x_float, partial_glyphs, break_glyphs);
}
ShapeCache* Font::GetShapeCache() const {
return EnsureFontFallbackList()->GetShapeCache(font_description_);
}
bool Font::CanShapeWordByWord() const {
return EnsureFontFallbackList()->CanShapeWordByWord(GetFontDescription());
}
void Font::ReportNotDefGlyph() const {
FontSelector* fontSelector = EnsureFontFallbackList()->GetFontSelector();
// We have a few non-DOM usages of Font code, for example in DragImage::Create
// and in EmbeddedObjectPainter::paintReplaced. In those cases, we can't
// retrieve a font selector as our connection to a Document object to report
// UseCounter metrics, and thus we cannot report notdef glyphs.
if (fontSelector)
fontSelector->ReportNotDefGlyph();
}
void Font::WillUseFontData(const String& text) const {
const FontFamily& family = GetFontDescription().Family();
if (font_fallback_list_ && font_fallback_list_->GetFontSelector() &&
!family.FamilyIsEmpty())
font_fallback_list_->GetFontSelector()->WillUseFontData(
GetFontDescription(), family.Family(), text);
}
GlyphData Font::GetEmphasisMarkGlyphData(const AtomicString& mark) const {
if (mark.IsEmpty())
return GlyphData();
TextRun emphasis_mark_run(mark, mark.length());
return CachingWordShaper(*this).EmphasisMarkGlyphData(emphasis_mark_run);
}
int Font::EmphasisMarkAscent(const AtomicString& mark) const {
FontCachePurgePreventer purge_preventer;
const auto mark_glyph_data = GetEmphasisMarkGlyphData(mark);
const SimpleFontData* mark_font_data = mark_glyph_data.font_data;
if (!mark_font_data)
return 0;
return mark_font_data->GetFontMetrics().Ascent();
}
int Font::EmphasisMarkDescent(const AtomicString& mark) const {
FontCachePurgePreventer purge_preventer;
const auto mark_glyph_data = GetEmphasisMarkGlyphData(mark);
const SimpleFontData* mark_font_data = mark_glyph_data.font_data;
if (!mark_font_data)
return 0;
return mark_font_data->GetFontMetrics().Descent();
}
int Font::EmphasisMarkHeight(const AtomicString& mark) const {
FontCachePurgePreventer purge_preventer;
const auto mark_glyph_data = GetEmphasisMarkGlyphData(mark);
const SimpleFontData* mark_font_data = mark_glyph_data.font_data;
if (!mark_font_data)
return 0;
return mark_font_data->GetFontMetrics().Height();
}
CharacterRange Font::GetCharacterRange(const TextRun& run,
unsigned from,
unsigned to) const {
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
return shaper.GetCharacterRange(run, from, to);
}
Vector<CharacterRange> Font::IndividualCharacterRanges(
const TextRun& run) const {
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
auto ranges = shaper.IndividualCharacterRanges(run);
// The shaper should return ranges.size == run.length but on some platforms
// (OSX10.9.5) we are seeing cases in the upper end of the unicode range
// where this is not true (see: crbug.com/620952). To catch these cases on
// more popular platforms, and to protect users, we are using a CHECK here.
CHECK_EQ(ranges.size(), run.length());
return ranges;
}
Vector<double> Font::IndividualCharacterAdvances(const TextRun& run) const {
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
return shaper.IndividualCharacterAdvances(run);
}
void Font::ExpandRangeToIncludePartialGlyphs(const TextRun& text_run,
int* from,
int* to) const {
TextRunPaintInfo run_info(text_run);
run_info.from = *from;
run_info.to = *to;
CachingWordShaper word_shaper(*this);
ShapeResultBuffer buffer;
word_shaper.FillResultBuffer(run_info, &buffer);
buffer.ExpandRangeToIncludePartialGlyphs(from, to);
}
float Font::TabWidth(const SimpleFontData* font_data,
const TabSize& tab_size,
float position) const {
float base_tab_width = TabWidth(font_data, tab_size);
if (!base_tab_width)
return GetFontDescription().LetterSpacing();
float distance_to_tab_stop = base_tab_width - fmodf(position, base_tab_width);
// Let the minimum width be the half of the space width so that it's always
// recognizable. if the distance to the next tab stop is less than that,
// advance an additional tab stop.
if (distance_to_tab_stop < font_data->SpaceWidth() / 2)
distance_to_tab_stop += base_tab_width;
return distance_to_tab_stop;
}
LayoutUnit Font::TabWidth(const TabSize& tab_size, LayoutUnit position) const {
const SimpleFontData* font_data = PrimaryFont();
if (!font_data)
return LayoutUnit::FromFloatCeil(GetFontDescription().LetterSpacing());
float base_tab_width = tab_size.GetPixelSize(font_data->SpaceWidth());
if (!base_tab_width)
return LayoutUnit::FromFloatCeil(GetFontDescription().LetterSpacing());
LayoutUnit distance_to_tab_stop = LayoutUnit::FromFloatFloor(
base_tab_width - fmodf(position, base_tab_width));
// Let the minimum width be the half of the space width so that it's always
// recognizable. if the distance to the next tab stop is less than that,
// advance an additional tab stop.
if (distance_to_tab_stop < font_data->SpaceWidth() / 2)
distance_to_tab_stop += base_tab_width;
return distance_to_tab_stop;
}
bool Font::IsFallbackValid() const {
return !font_fallback_list_ || font_fallback_list_->IsValid();
}
} // namespace blink