9

text_decoration_info.cc

 2 years ago
source link: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/text_decoration_info.cc;l=358;drc=253d7c7b54ba8ed1b45cd91e314fb95962d134be
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
text_decoration_info.cc - Chromium Code Search
Code panel - press c to focus line 1.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/text_decoration_info.h"
#include "third_party/blink/renderer/core/paint/text_paint_style.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
namespace blink {
namespace {
static int kUndefinedDecorationIndex = -1;
static ResolvedUnderlinePosition ResolveUnderlinePosition(
const ComputedStyle& style,
FontBaseline baseline_type) {
// |auto| should resolve to |under| to avoid drawing through glyphs in
// scripts where it would not be appropriate (e.g., ideographs.)
// However, this has performance implications. For now, we only work with
// vertical text.
if (baseline_type != kCentralBaseline) {
if (style.TextUnderlinePosition() & kTextUnderlinePositionUnder)
return ResolvedUnderlinePosition::kUnder;
if (style.TextUnderlinePosition() & kTextUnderlinePositionFromFont)
return ResolvedUnderlinePosition::kNearAlphabeticBaselineFromFont;
return ResolvedUnderlinePosition::kNearAlphabeticBaselineAuto;
// Compute language-appropriate default underline position.
UScriptCode script = style.GetFontDescription().GetScript();
if (script == USCRIPT_KATAKANA_OR_HIRAGANA || script == USCRIPT_HANGUL) {
if (style.TextUnderlinePosition() & kTextUnderlinePositionLeft)
return ResolvedUnderlinePosition::kUnder;
return ResolvedUnderlinePosition::kOver;
if (style.TextUnderlinePosition() & kTextUnderlinePositionRight)
return ResolvedUnderlinePosition::kOver;
return ResolvedUnderlinePosition::kUnder;
static bool ShouldSetDecorationAntialias(const ComputedStyle& style) {
for (const auto& decoration : style.AppliedTextDecorations()) {
ETextDecorationStyle decoration_style = decoration.Style();
if (decoration_style == ETextDecorationStyle::kDotted ||
decoration_style == ETextDecorationStyle::kDashed)
return true;
return false;
static float ComputeDecorationThickness(
const TextDecorationThickness text_decoration_thickness,
float computed_font_size,
const SimpleFontData* font_data) {
float auto_underline_thickness = std::max(1.f, computed_font_size / 10.f);
if (text_decoration_thickness.IsAuto())
return auto_underline_thickness;
// In principle we would not need to test for font_data if
// |text_decoration_thickness.Thickness()| is fixed, but a null font_data here
// would be a rare / error situation anyway, so practically, we can
// early out here.
if (!font_data)
return auto_underline_thickness;
if (text_decoration_thickness.IsFromFont()) {
absl::optional<float> font_underline_thickness =
font_data->GetFontMetrics().UnderlineThickness();
if (!font_underline_thickness)
return auto_underline_thickness;
return std::max(1.f, font_underline_thickness.value());
DCHECK(!text_decoration_thickness.IsFromFont());
const Length& thickness_length = text_decoration_thickness.Thickness();
float font_size = font_data->PlatformData().size();
float text_decoration_thickness_pixels =
FloatValueForLength(thickness_length, font_size);
return std::max(1.f, text_decoration_thickness_pixels);
static void AdjustStepToDecorationLength(float& step,
float& control_point_distance,
float length) {
DCHECK_GT(step, 0);
if (length <= 0)
return;
unsigned step_count = static_cast<unsigned>(length / step);
// Each Bezier curve starts at the same pixel that the previous one
// ended. We need to subtract (stepCount - 1) pixels when calculating the
// length covered to account for that.
float uncovered_length = length - (step_count * step - (step_count - 1));
float adjustment = uncovered_length / step_count;
step += adjustment;
control_point_distance += adjustment;
static enum StrokeStyle TextDecorationStyleToStrokeStyle(
ETextDecorationStyle decoration_style) {
enum StrokeStyle stroke_style = kSolidStroke;
switch (decoration_style) {
case ETextDecorationStyle::kSolid:
stroke_style = kSolidStroke;
break;
case ETextDecorationStyle::kDouble:
stroke_style = kDoubleStroke;
break;
case ETextDecorationStyle::kDotted:
stroke_style = kDottedStroke;
break;
case ETextDecorationStyle::kDashed:
stroke_style = kDashedStroke;
break;
case ETextDecorationStyle::kWavy:
stroke_style = kWavyStroke;
break;
return stroke_style;
static int TextDecorationToLineDataIndex(TextDecoration line) {
switch (line) {
case TextDecoration::kUnderline:
return 0;
case TextDecoration::kOverline:
return 1;
case TextDecoration::kLineThrough:
return 2;
default:
NOTREACHED();
return 0;
} // anonymous namespace
TextDecorationInfo::TextDecorationInfo(
PhysicalOffset local_origin,
LayoutUnit width,
FontBaseline baseline_type,
const ComputedStyle& style,
const Font& scaled_font,
const absl::optional<AppliedTextDecoration> selection_text_decoration,
const ComputedStyle* decorating_box_style,
float scaling_factor)
: style_(style),
selection_text_decoration_(selection_text_decoration),
baseline_type_(baseline_type),
width_(width),
font_data_(scaled_font.PrimaryFont()),
baseline_(font_data_ ? font_data_->GetFontMetrics().FloatAscent() : 0),
computed_font_size_(scaled_font.GetFontDescription().ComputedSize()),
scaling_factor_(scaling_factor),
underline_position_(ResolveUnderlinePosition(style_, baseline_type_)),
local_origin_(FloatPoint(local_origin)),
antialias_(ShouldSetDecorationAntialias(style)),
decoration_index_(kUndefinedDecorationIndex) {
DCHECK(font_data_);
for (const AppliedTextDecoration& decoration :
style_.AppliedTextDecorations()) {
applied_decorations_thickness_.push_back(ComputeUnderlineThickness(
decoration.Thickness(), decorating_box_style));
DCHECK_EQ(style_.AppliedTextDecorations().size(),
applied_decorations_thickness_.size());
void TextDecorationInfo::SetDecorationIndex(int decoration_index) {
DCHECK_LT(decoration_index,
static_cast<int>(applied_decorations_thickness_.size()));
decoration_index_ = decoration_index;
void TextDecorationInfo::SetPerLineData(TextDecoration line,
float line_offset) {
int index = TextDecorationToLineDataIndex(line);
line_data_[index].line_offset = line_offset;
const float double_offset_from_thickness = ResolvedThickness() + 1.0f;
float double_offset;
int wavy_offset_factor;
switch (line) {
case TextDecoration::kUnderline:
double_offset = double_offset_from_thickness;
wavy_offset_factor = 1;
break;
case TextDecoration::kOverline:
double_offset = -double_offset_from_thickness;
wavy_offset_factor = 1;
break;
case TextDecoration::kLineThrough:
// Floor double_offset in order to avoid double-line gap to appear
// of different size depending on position where the double line
// is drawn because of rounding downstream in
// GraphicsContext::DrawLineForText.
double_offset = floorf(double_offset_from_thickness);
wavy_offset_factor = 0;
break;
default:
double_offset = 0.0f;
wavy_offset_factor = 0;
NOTREACHED();
line_data_[index].double_offset = double_offset;
line_data_[index].wavy_offset_factor = wavy_offset_factor;
line_data_[index].stroke_path.reset();
ETextDecorationStyle TextDecorationInfo::DecorationStyle() const {
return style_.AppliedTextDecorations()[decoration_index_].Style();
Color TextDecorationInfo::LineColor() const {
// Find the matched normal and selection |AppliedTextDecoration|
// and use the text-decoration-color from selection when it is.
if (selection_text_decoration_ &&
style_.AppliedTextDecorations()[decoration_index_].Lines() ==
selection_text_decoration_.value().Lines()) {
return selection_text_decoration_.value().GetColor();
return style_.AppliedTextDecorations()[decoration_index_].GetColor();
FloatPoint TextDecorationInfo::StartPoint(TextDecoration line) const {
return local_origin_ +
FloatPoint(
0, line_data_[TextDecorationToLineDataIndex(line)].line_offset);
float TextDecorationInfo::DoubleOffset(TextDecoration line) const {
return line_data_[TextDecorationToLineDataIndex(line)].double_offset;
enum StrokeStyle TextDecorationInfo::StrokeStyle() const {
return TextDecorationStyleToStrokeStyle(DecorationStyle());
float TextDecorationInfo::ComputeUnderlineThickness(
const TextDecorationThickness& applied_decoration_thickness,
const ComputedStyle* decorating_box_style) {
float thickness = 0;
if ((underline_position_ ==
ResolvedUnderlinePosition::kNearAlphabeticBaselineAuto) ||
underline_position_ ==
ResolvedUnderlinePosition::kNearAlphabeticBaselineFromFont) {
thickness = ComputeDecorationThickness(applied_decoration_thickness,
computed_font_size_, font_data_);
} else {
// Compute decorating box. Position and thickness are computed from the
// decorating box.
// Only for non-Roman for now for the performance implications.
// https:// drafts.csswg.org/css-text-decor-3/#decorating-box
if (decorating_box_style) {
thickness = ComputeDecorationThickness(
applied_decoration_thickness,
decorating_box_style->ComputedFontSize(),
decorating_box_style->GetFont().PrimaryFont());
} else {
thickness = ComputeDecorationThickness(applied_decoration_thickness,
computed_font_size_, font_data_);
return thickness;
FloatRect TextDecorationInfo::BoundsForLine(TextDecoration line) const {
FloatPoint start_point = StartPoint(line);
switch (DecorationStyle()) {
case ETextDecorationStyle::kDotted:
case ETextDecorationStyle::kDashed:
return BoundsForDottedOrDashed(line);
case ETextDecorationStyle::kWavy:
return BoundsForWavy(line);
case ETextDecorationStyle::kDouble:
if (DoubleOffset(line) > 0) {
return FloatRect(start_point.x(), start_point.y(), width_,
DoubleOffset(line) + ResolvedThickness());
return FloatRect(start_point.x(), start_point.y() + DoubleOffset(line),
width_, -DoubleOffset(line) + ResolvedThickness());
case ETextDecorationStyle::kSolid:
return FloatRect(start_point.x(), start_point.y(), width_,
ResolvedThickness());
default:
break;
NOTREACHED();
return FloatRect();
FloatRect TextDecorationInfo::BoundsForDottedOrDashed(
TextDecoration line) const {
int line_data_index = TextDecorationToLineDataIndex(line);
if (!line_data_[line_data_index].stroke_path) {
// These coordinate transforms need to match what's happening in
// GraphicsContext's drawLineForText and drawLine.
FloatPoint start_point = StartPoint(line);
line_data_[TextDecorationToLineDataIndex(line)].stroke_path =
GraphicsContext::GetPathForTextLine(
start_point, width_, ResolvedThickness(),
TextDecorationStyleToStrokeStyle(DecorationStyle()));
StrokeData stroke_data;
stroke_data.SetThickness(roundf(ResolvedThickness()));
stroke_data.SetStyle(TextDecorationStyleToStrokeStyle(DecorationStyle()));
return line_data_[line_data_index].stroke_path.value().StrokeBoundingRect(
stroke_data);
FloatRect TextDecorationInfo::BoundsForWavy(TextDecoration line) const {
StrokeData stroke_data;
stroke_data.SetThickness(ResolvedThickness());
return PrepareWavyStrokePath(line)->StrokeBoundingRect(stroke_data);
* Prepare a path for a cubic Bezier curve and repeat the same pattern long the
* the decoration's axis. The start point (p1), controlPoint1, controlPoint2
* and end point (p2) of the Bezier curve form a diamond shape:
* step
* |-----------|
* controlPoint1
* (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis
* . . | controlPointDistance
* controlPoint2
* |-----------|
* step
absl::optional<Path> TextDecorationInfo::PrepareWavyStrokePath(
TextDecoration line) const {
int line_data_index = TextDecorationToLineDataIndex(line);
if (line_data_[line_data_index].stroke_path)
return line_data_[line_data_index].stroke_path;
FloatPoint start_point = StartPoint(line);
float wave_offset =
DoubleOffset(line) *
line_data_[TextDecorationToLineDataIndex(line)].wavy_offset_factor;
FloatPoint p1(start_point + FloatPoint(0, wave_offset));
FloatPoint p2(start_point + FloatPoint(width_, wave_offset));
GraphicsContext::AdjustLineToPixelBoundaries(p1, p2, ResolvedThickness());
Path& path = line_data_[line_data_index].stroke_path.emplace();
path.MoveTo(ToGfxPointF(p1));
// Distance between decoration's axis and Bezier curve's control points.
// The height of the curve is based on this distance. Use a minimum of 6
// pixels distance since
// the actual curve passes approximately at half of that distance, that is 3
// pixels.
// The minimum height of the curve is also approximately 3 pixels. Increases
// the curve's height
// as strockThickness increases to make the curve looks better.
float control_point_distance = 3 * std::max<float>(2, ResolvedThickness());
// Increment used to form the diamond shape between start point (p1),
// control points and end point (p2) along the axis of the decoration. Makes
// the curve wider as strockThickness increases to make the curve looks
// better.
float step = 2 * std::max<float>(2, ResolvedThickness());
bool is_vertical_line = (p1.x() == p2.x());
if (is_vertical_line) {
DCHECK(p1.x() == p2.x());
float x_axis = p1.x();
float y1;
float y2;
if (p1.y() < p2.y()) {
y1 = p1.y();
y2 = p2.y();
} else {
y1 = p2.y();
y2 = p1.y();
AdjustStepToDecorationLength(step, control_point_distance, y2 - y1);
gfx::PointF control_point1(x_axis + control_point_distance, 0);
gfx::PointF control_point2(x_axis - control_point_distance, 0);
for (float y = y1; y + 2 * step <= y2;) {
control_point1.set_y(y + step);
control_point2.set_y(y + step);
y += 2 * step;
path.AddBezierCurveTo(control_point1, control_point2,
gfx::PointF(x_axis, y));
} else {
DCHECK(p1.y() == p2.y());
float y_axis = p1.y();
float x1;
float x2;
if (p1.x() < p2.x()) {
x1 = p1.x();
x2 = p2.x();
} else {
x1 = p2.x();
x2 = p1.x();
AdjustStepToDecorationLength(step, control_point_distance, x2 - x1);
gfx::PointF control_point1(0, y_axis + control_point_distance);
gfx::PointF control_point2(0, y_axis - control_point_distance);
for (float x = x1; x + 2 * step <= x2;) {
control_point1.set_x(x + step);
control_point2.set_x(x + step);
x += 2 * step;
path.AddBezierCurveTo(control_point1, control_point2,
gfx::PointF(x, y_axis));
return line_data_[line_data_index].stroke_path;
} // namespace blink

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK