blob: 85b4448ddbf1b86c8e915817b0f9e12985ed6690 [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) Research In Motion Limited 2010-2011. 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 "config.h"
#if ENABLE(SVG_FONTS)
#include "Font.h"
#include "CSSFontSelector.h"
#include "GraphicsContext.h"
#include "RenderObject.h"
#include "RenderSVGInlineText.h"
#include "RenderSVGResourceSolidColor.h"
#include "SVGAltGlyphElement.h"
#include "SVGFontData.h"
#include "SVGFontElement.h"
#include "SVGFontFaceElement.h"
#include "SVGGlyphElement.h"
#include "SVGGlyphMap.h"
#include "SVGMissingGlyphElement.h"
#include "SVGNames.h"
#include "SVGTextRunRenderingContext.h"
#include "SimpleFontData.h"
#include "XMLNames.h"
using namespace WTF::Unicode;
namespace WebCore {
static inline float convertEmUnitToPixel(float fontSize, float unitsPerEm, float value)
{
if (!unitsPerEm)
return 0.0f;
return value * fontSize / unitsPerEm;
}
static inline bool isVerticalWritingMode(const SVGRenderStyle* style)
{
return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB;
}
static inline const SVGFontData* svgFontAndFontFaceElementForFontData(const SimpleFontData* fontData, SVGFontFaceElement*& fontFace, SVGFontElement*& font)
{
ASSERT(fontData);
ASSERT(fontData->isCustomFont());
ASSERT(fontData->isSVGFont());
const SVGFontData* svgFontData = static_cast<const SVGFontData*>(fontData->fontData());
fontFace = svgFontData->svgFontFaceElement();
ASSERT(fontFace);
font = fontFace->associatedFontElement();
return svgFontData;
}
static inline RenderObject* firstParentRendererForNonTextNode(RenderObject* renderer)
{
ASSERT(renderer);
RenderObject* newRenderer = renderer->isText() ? renderer->parent() : renderer;
return newRenderer;
}
static inline RenderObject* referencingRenderObjectFromRun(const TextRun& run)
{
if (TextRun::RenderingContext* renderingContext = run.renderingContext())
return static_cast<SVGTextRunRenderingContext*>(renderingContext)->renderer();
return 0;
}
static inline RenderSVGResource* activePaintingResourceFromRun(const TextRun& run)
{
if (TextRun::RenderingContext* renderingContext = run.renderingContext())
return static_cast<SVGTextRunRenderingContext*>(renderingContext)->activePaintingResource();
return 0;
}
// Helper class to walk a text run. Lookup a SVGGlyph for each character
// - also respecting possibly defined ligatures - and invoke a callback for each found glyph.
template<typename SVGTextRunData>
struct SVGTextRunWalker {
typedef bool (*SVGTextRunWalkerCallback)(const SVGGlyph&, SVGTextRunData&);
typedef void (*SVGTextRunWalkerMissingGlyphCallback)(const TextRun&, SVGTextRunData&);
SVGTextRunWalker(const SVGFontData* fontData, SVGFontElement* fontElement, SVGTextRunData& data,
SVGTextRunWalkerCallback callback, SVGTextRunWalkerMissingGlyphCallback missingGlyphCallback)
: m_fontData(fontData)
, m_fontElement(fontElement)
, m_walkerData(data)
, m_walkerCallback(callback)
, m_walkerMissingGlyphCallback(missingGlyphCallback)
{
}
void walk(const TextRun& run, bool isVerticalText, const String& language, int from, int to)
{
if (from < 0 || to < 0 || from > to || from >= run.length() || to > run.length())
return;
const String text = Font::normalizeSpaces(run.data(from), to - from);
Vector<SVGGlyph::ArabicForm> chars(charactersWithArabicForm(text, run.rtl()));
SVGGlyph identifier;
bool foundGlyph = false;
int characterLookupRange;
int endOfScanRange = to + m_walkerData.extraCharsAvailable;
RenderObject* renderObject = referencingRenderObjectFromRun(run);
RenderObject* parentRenderObject = firstParentRendererForNonTextNode(renderObject);
bool haveAltGlyph = false;
SVGGlyph altGlyphIdentifier;
Node* node = parentRenderObject->node();
if (node && node->hasTagName(SVGNames::altGlyphTag)) {
if (SVGGlyphElement* glyphElement = static_cast<SVGAltGlyphElement*>(node)->glyphElement()) {
haveAltGlyph = true;
altGlyphIdentifier = glyphElement->buildGlyphIdentifier();
altGlyphIdentifier.isValid = true;
altGlyphIdentifier.unicodeStringLength = to - from;
}
}
for (int i = from; i < to; ++i) {
// If characterLookupRange is > 0, then the font defined ligatures (length of unicode property value > 1).
// We have to check wheter the current character & the next character define a ligature. This needs to be
// extended to the n-th next character (where n is 'characterLookupRange'), to check for any possible ligature.
characterLookupRange = endOfScanRange - i;
String lookupString = Font::normalizeSpaces(run.data(i), characterLookupRange);
Vector<SVGGlyph> glyphs;
if (haveAltGlyph)
glyphs.append(altGlyphIdentifier);
else
m_fontElement->collectGlyphsForString(lookupString, glyphs);
Vector<SVGGlyph>::iterator it = glyphs.begin();
Vector<SVGGlyph>::iterator end = glyphs.end();
for (; it != end; ++it) {
identifier = *it;
if (identifier.isValid && isCompatibleGlyph(identifier, isVerticalText, language, chars, i, i + identifier.unicodeStringLength)) {
ASSERT(characterLookupRange > 0);
i += identifier.unicodeStringLength - 1;
m_walkerData.charsConsumed += identifier.unicodeStringLength;
m_walkerData.glyphName = identifier.glyphName;
foundGlyph = true;
SVGGlyphElement::inheritUnspecifiedAttributes(identifier, m_fontData);
break;
}
}
if (!foundGlyph) {
++m_walkerData.charsConsumed;
if (SVGMissingGlyphElement* element = m_fontElement->firstMissingGlyphElement()) {
// <missing-glyph> element support
identifier = SVGGlyphElement::buildGenericGlyphIdentifier(element);
SVGGlyphElement::inheritUnspecifiedAttributes(identifier, m_fontData);
identifier.isValid = true;
} else {
// Fallback to system font fallback
TextRun subRun(run);
subRun.setRenderingContext(0);
subRun.setText(subRun.data(i), 1);
(*m_walkerMissingGlyphCallback)(subRun, m_walkerData);
continue;
}
}
if (!(*m_walkerCallback)(identifier, m_walkerData))
break;
foundGlyph = false;
}
}
private:
const SVGFontData* m_fontData;
SVGFontElement* m_fontElement;
SVGTextRunData& m_walkerData;
SVGTextRunWalkerCallback m_walkerCallback;
SVGTextRunWalkerMissingGlyphCallback m_walkerMissingGlyphCallback;
};
// Callback & data structures to compute the width of text using SVG Fonts
struct SVGTextRunWalkerMeasuredLengthData {
int at;
int from;
int to;
int extraCharsAvailable;
int charsConsumed;
String glyphName;
float scale;
float length;
const Font* font;
};
static bool floatWidthUsingSVGFontCallback(const SVGGlyph& identifier, SVGTextRunWalkerMeasuredLengthData& data)
{
if (data.at >= data.from && data.at < data.to)
data.length += identifier.horizontalAdvanceX * data.scale;
data.at++;
return data.at < data.to;
}
static void floatWidthMissingGlyphCallback(const TextRun& run, SVGTextRunWalkerMeasuredLengthData& data)
{
// Handle system font fallback
FontDescription fontDescription(data.font->fontDescription());
fontDescription.setFamily(FontFamily());
Font font(fontDescription, 0, 0); // spacing handled by SVG text code.
font.update(data.font->fontSelector());
TextRun fallbackRun(run);
fallbackRun.setRenderingContext(0);
data.length += font.width(fallbackRun);
}
static float floatWidthOfSubStringUsingSVGFont(const Font& font, const TextRun& run, int extraCharsAvailable, int from, int to, int& charsConsumed, String& glyphName)
{
int newFrom = to > from ? from : to;
int newTo = to > from ? to : from;
from = newFrom;
to = newTo;
SVGFontElement* fontElement = 0;
SVGFontFaceElement* fontFaceElement = 0;
if (const SVGFontData* fontData = svgFontAndFontFaceElementForFontData(font.primaryFont(), fontFaceElement, fontElement)) {
if (!fontElement)
return 0.0f;
SVGTextRunWalkerMeasuredLengthData data;
data.font = &font;
data.at = from;
data.from = from;
data.to = to;
data.extraCharsAvailable = extraCharsAvailable;
data.charsConsumed = 0;
data.scale = convertEmUnitToPixel(font.size(), fontFaceElement->unitsPerEm(), 1.0f);
data.length = 0.0f;
RenderObject* renderObject = referencingRenderObjectFromRun(run);
RenderObject* parentRenderObject = firstParentRendererForNonTextNode(renderObject);
String language;
if (SVGElement* element = static_cast<SVGElement*>(parentRenderObject->node()))
language = element->getAttribute(XMLNames::langAttr);
bool isVerticalText = isVerticalWritingMode(parentRenderObject->style()->svgStyle());
SVGTextRunWalker<SVGTextRunWalkerMeasuredLengthData> runWalker(fontData, fontElement, data, floatWidthUsingSVGFontCallback, floatWidthMissingGlyphCallback);
runWalker.walk(run, isVerticalText, language, from, to);
charsConsumed = data.charsConsumed;
glyphName = data.glyphName;
return data.length;
}
return 0.0f;
}
float SVGTextRunRenderingContext::floatWidthUsingSVGFont(const Font& font, const TextRun& run) const
{
int charsConsumed;
String glyphName;
return floatWidthOfSubStringUsingSVGFont(font, run, 0, 0, run.length(), charsConsumed, glyphName);
}
float SVGTextRunRenderingContext::floatWidthUsingSVGFont(const Font& font, const TextRun& run, int extraCharsAvailable, int& charsConsumed, String& glyphName) const
{
return floatWidthOfSubStringUsingSVGFont(font, run, extraCharsAvailable, 0, run.length(), charsConsumed, glyphName);
}
// Callback & data structures to draw text using SVG Fonts
struct SVGTextRunWalkerDrawTextData {
int extraCharsAvailable;
int charsConsumed;
String glyphName;
Vector<SVGGlyph> glyphIdentifiers;
Vector<UChar> fallbackCharacters;
};
static bool drawTextUsingSVGFontCallback(const SVGGlyph& identifier, SVGTextRunWalkerDrawTextData& data)
{
data.glyphIdentifiers.append(identifier);
return true;
}
static void drawTextMissingGlyphCallback(const TextRun& run, SVGTextRunWalkerDrawTextData& data)
{
ASSERT(run.length() == 1);
data.glyphIdentifiers.append(SVGGlyph());
data.fallbackCharacters.append(run[0]);
}
void SVGTextRunRenderingContext::drawTextUsingSVGFont(const Font& font, GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
{
SVGFontElement* fontElement = 0;
SVGFontFaceElement* fontFaceElement = 0;
if (const SVGFontData* fontData = svgFontAndFontFaceElementForFontData(font.primaryFont(), fontFaceElement, fontElement)) {
if (!fontElement)
return;
SVGTextRunWalkerDrawTextData data;
FloatPoint currentPoint = point;
float scale = convertEmUnitToPixel(font.size(), fontFaceElement->unitsPerEm(), 1.0f);
// We can only paint SVGFonts if a context is available.
RenderObject* renderObject = referencingRenderObjectFromRun(run);
RenderObject* parentRenderObject = firstParentRendererForNonTextNode(renderObject);
// If activePaintingResource is not set, we're dealing for HTML text rendered using SVG Fonts.
RenderSVGResource* activePaintingResource = activePaintingResourceFromRun(run);
if (!activePaintingResource) {
// TODO: We're only supporting simple filled HTML text so far.
RenderSVGResourceSolidColor* solidPaintingResource = RenderSVGResource::sharedSolidPaintingResource();
solidPaintingResource->setColor(context->fillColor());
activePaintingResource = solidPaintingResource;
}
int charsConsumed;
String glyphName;
float xStartOffset = floatWidthOfSubStringUsingSVGFont(font, run, 0, run.rtl() ? to : 0, run.rtl() ? run.length() : from, charsConsumed, glyphName);
FloatPoint glyphOrigin;
String language;
if (SVGElement* element = static_cast<SVGElement*>(parentRenderObject->node()))
language = element->getAttribute(XMLNames::langAttr);
RenderStyle* parentRenderObjectStyle = parentRenderObject->style();
bool isVerticalText = isVerticalWritingMode(parentRenderObjectStyle->svgStyle());
if (!isVerticalText) {
glyphOrigin.setX(fontData->horizontalOriginX() * scale);
glyphOrigin.setY(fontData->horizontalOriginY() * scale);
}
data.extraCharsAvailable = 0;
data.charsConsumed = 0;
SVGTextRunWalker<SVGTextRunWalkerDrawTextData> runWalker(fontData, fontElement, data, drawTextUsingSVGFontCallback, drawTextMissingGlyphCallback);
runWalker.walk(run, isVerticalText, language, from, to);
RenderSVGResourceMode resourceMode = context->textDrawingMode() == TextModeStroke ? ApplyToStrokeMode : ApplyToFillMode;
unsigned numGlyphs = data.glyphIdentifiers.size();
unsigned fallbackCharacterIndex = 0;
for (unsigned i = 0; i < numGlyphs; ++i) {
const SVGGlyph& identifier = data.glyphIdentifiers[run.rtl() ? numGlyphs - i - 1 : i];
if (identifier.isValid) {
// FIXME: Support arbitary SVG content as glyph (currently limited to <glyph d="..."> situations).
if (!identifier.pathData.isEmpty()) {
GraphicsContextStateSaver stateSaver(*context);
if (isVerticalText) {
glyphOrigin.setX(identifier.verticalOriginX * scale);
glyphOrigin.setY(identifier.verticalOriginY * scale);
}
AffineTransform glyphPathTransform;
glyphPathTransform.translate(xStartOffset + currentPoint.x() + glyphOrigin.x(), currentPoint.y() + glyphOrigin.y());
glyphPathTransform.scale(scale, -scale);
Path glyphPath = identifier.pathData;
glyphPath.transform(glyphPathTransform);
if (activePaintingResource->applyResource(parentRenderObject, parentRenderObjectStyle, context, resourceMode)) {
if (renderObject && renderObject->isSVGInlineText()) {
const RenderSVGInlineText* textRenderer = toRenderSVGInlineText(renderObject);
context->setStrokeThickness(context->strokeThickness() * textRenderer->scalingFactor());
}
activePaintingResource->postApplyResource(parentRenderObject, context, resourceMode, &glyphPath);
}
}
if (isVerticalText)
currentPoint.move(0.0f, identifier.verticalAdvanceY * scale);
else
currentPoint.move(identifier.horizontalAdvanceX * scale, 0.0f);
} else {
// Handle system font fallback
FontDescription fontDescription(font.fontDescription());
fontDescription.setFamily(FontFamily());
Font font(fontDescription, 0, 0); // spacing handled by SVG text code.
font.update(font.fontSelector());
TextRun fallbackCharacterRun(run);
fallbackCharacterRun.setRenderingContext(0);
fallbackCharacterRun.setText(&data.fallbackCharacters[run.rtl() ? data.fallbackCharacters.size() - fallbackCharacterIndex - 1 : fallbackCharacterIndex], 1);
font.drawText(context, fallbackCharacterRun, currentPoint);
if (isVerticalText)
currentPoint.move(0.0f, font.width(fallbackCharacterRun));
else
currentPoint.move(font.width(fallbackCharacterRun), 0.0f);
fallbackCharacterIndex++;
}
}
}
}
FloatRect SVGTextRunRenderingContext::selectionRectForTextUsingSVGFont(const Font& font, const TextRun& run, const FloatPoint& point, int height, int from, int to) const
{
int charsConsumed;
String glyphName;
return FloatRect(point.x() + floatWidthOfSubStringUsingSVGFont(font, run, 0, run.rtl() ? to : 0, run.rtl() ? run.length() : from, charsConsumed, glyphName),
point.y(), floatWidthOfSubStringUsingSVGFont(font, run, 0, from, to, charsConsumed, glyphName), height);
}
int SVGTextRunRenderingContext::offsetForPositionForTextUsingSVGFont(const Font&, const TextRun&, float, bool) const
{
// TODO: Fix text selection when HTML text is drawn using a SVG Font
// We need to integrate the SVG text selection code in the offsetForPosition() framework.
// This will also fix a major issue, that SVG Text code can't select arabic strings properly.
return 0;
}
}
#endif