/* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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 "config.h" #include "InlineTextBoxStyle.h" #include "FontCascade.h" #include "InlineTextBox.h" #include "RootInlineBox.h" namespace WebCore { int computeUnderlineOffset(TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, int textDecorationThickness) { // This represents the gap between the baseline and the closest edge of the underline. int gap = std::max(1, ceilf(textDecorationThickness / 2.0)); // FIXME: The code for visual overflow detection passes in a null inline text box. This means it is now // broken for the case where auto needs to behave like "under". // According to the specification TextUnderlinePositionAuto should avoid drawing through glyphs in // scripts where it would not be appropriate (e.g., ideographs). // Strictly speaking this can occur whenever the line contains ideographs // even if it is horizontal, but detecting this has performance implications. For now we only work with // vertical text, since we already determined the baseline type to be ideographic in that // case. TextUnderlinePosition resolvedUnderlinePosition = underlinePosition == TextUnderlinePositionAuto && inlineTextBox && inlineTextBox->root().baselineType() == IdeographicBaseline ? TextUnderlinePositionUnder : TextUnderlinePositionAlphabetic; switch (resolvedUnderlinePosition) { case TextUnderlinePositionAlphabetic: return fontMetrics.ascent() + gap; case TextUnderlinePositionUnder: { ASSERT(inlineTextBox); // Position underline relative to the bottom edge of the lowest element's content box. const RootInlineBox& rootBox = inlineTextBox->root(); const RenderElement* decorationRenderer = inlineTextBox->parent()->renderer().enclosingRendererWithTextDecoration(TextDecorationUnderline, inlineTextBox->isFirstLine()); float offset; if (inlineTextBox->renderer().style().isFlippedLinesWritingMode()) { offset = inlineTextBox->logicalTop(); rootBox.minLogicalTopForTextDecorationLine(offset, decorationRenderer, TextDecorationUnderline); offset = inlineTextBox->logicalTop() - offset; } else { offset = inlineTextBox->logicalBottom(); rootBox.maxLogicalBottomForTextDecorationLine(offset, decorationRenderer, TextDecorationUnderline); offset -= inlineTextBox->logicalBottom(); } return inlineTextBox->logicalHeight() + gap + std::max(offset, 0); } case TextUnderlinePositionAuto: ASSERT_NOT_REACHED(); } ASSERT_NOT_REACHED(); return fontMetrics.ascent() + gap; } void getWavyStrokeParameters(float strokeThickness, float& controlPointDistance, float& step) { // 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 strokeThickness increases to make the curve look better. controlPointDistance = 3 * std::max(2, strokeThickness); // 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 strokeThickness increases to make the curve look better. step = 2 * std::max(2, strokeThickness); } static inline void extendIntToFloat(int& extendMe, float extendTo) { extendMe = std::max(extendMe, static_cast(ceilf(extendTo))); } GlyphOverflow visualOverflowForDecorations(const RenderStyle& lineStyle, const InlineTextBox* inlineTextBox) { ASSERT(!inlineTextBox || inlineTextBox->lineStyle() == lineStyle); TextDecoration decoration = lineStyle.textDecorationsInEffect(); if (decoration == TextDecorationNone) return GlyphOverflow(); float strokeThickness = textDecorationStrokeThickness(lineStyle.fontSize()); float controlPointDistance; float step; float wavyOffset; TextDecorationStyle decorationStyle = lineStyle.textDecorationStyle(); float height = lineStyle.fontCascade().fontMetrics().floatHeight(); GlyphOverflow overflowResult; if (decorationStyle == TextDecorationStyleWavy) { getWavyStrokeParameters(strokeThickness, controlPointDistance, step); wavyOffset = wavyOffsetFromDecoration(); overflowResult.left = strokeThickness; overflowResult.right = strokeThickness; } // These metrics must match where underlines get drawn. if (decoration & TextDecorationUnderline) { // Compensate for the integral ceiling in GraphicsContext::computeLineBoundsAndAntialiasingModeForText() int underlineOffset = 1; underlineOffset += computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), inlineTextBox, strokeThickness); if (decorationStyle == TextDecorationStyleWavy) { extendIntToFloat(overflowResult.bottom, underlineOffset + wavyOffset + controlPointDistance + strokeThickness - height); extendIntToFloat(overflowResult.top, -(underlineOffset + wavyOffset - controlPointDistance - strokeThickness)); } else { extendIntToFloat(overflowResult.bottom, underlineOffset + strokeThickness - height); extendIntToFloat(overflowResult.top, -underlineOffset); } } if (decoration & TextDecorationOverline) { if (decorationStyle == TextDecorationStyleWavy) { extendIntToFloat(overflowResult.bottom, -wavyOffset + controlPointDistance + strokeThickness - height); extendIntToFloat(overflowResult.top, wavyOffset + controlPointDistance + strokeThickness); } else { extendIntToFloat(overflowResult.bottom, strokeThickness - height); // top is untouched } } if (decoration & TextDecorationLineThrough) { float baseline = lineStyle.fontMetrics().floatAscent(); if (decorationStyle == TextDecorationStyleWavy) { extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + controlPointDistance + strokeThickness - height); extendIntToFloat(overflowResult.top, -(2 * baseline / 3 - controlPointDistance - strokeThickness)); } else { extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + strokeThickness - height); extendIntToFloat(overflowResult.top, -(2 * baseline / 3)); } } return overflowResult; } }