diff options
Diffstat (limited to 'WebCore/svg/SVGFont.cpp')
-rw-r--r-- | WebCore/svg/SVGFont.cpp | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/WebCore/svg/SVGFont.cpp b/WebCore/svg/SVGFont.cpp new file mode 100644 index 0000000..ece667f --- /dev/null +++ b/WebCore/svg/SVGFont.cpp @@ -0,0 +1,594 @@ +/** + * Copyright (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> + * + * 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 "SimpleFontData.h" +#include "SVGAltGlyphElement.h" +#include "SVGFontData.h" +#include "SVGGlyphElement.h" +#include "SVGGlyphMap.h" +#include "SVGFontElement.h" +#include "SVGFontFaceElement.h" +#include "SVGMissingGlyphElement.h" +#include "SVGPaintServer.h" +#include "SVGPaintServerSolid.h" +#include "XMLNames.h" + +using namespace WTF::Unicode; + +namespace WebCore { + +static inline float convertEmUnitToPixel(float fontSize, float unitsPerEm, float value) +{ + if (unitsPerEm == 0.0f) + return 0.0f; + + return value * fontSize / unitsPerEm; +} + +static inline bool isVerticalWritingMode(const SVGRenderStyle* style) +{ + return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; +} + +// Helper functions to determine the arabic character forms (initial, medial, terminal, isolated) +enum ArabicCharShapingMode { + SNone = 0, + SRight = 1, + SDual = 2 +}; + +static const ArabicCharShapingMode s_arabicCharShapingMode[222] = { + SRight, SRight, SRight, SRight, SDual , SRight, SDual , SRight, SDual , SDual , SDual , SDual , SDual , SRight, /* 0x0622 - 0x062F */ + SRight, SRight, SRight, SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SNone , SNone , SNone , SNone , SNone , /* 0x0630 - 0x063F */ + SNone , SDual , SDual , SDual , SDual , SDual , SDual , SRight, SDual , SDual , SNone , SNone , SNone , SNone , SNone , SNone , /* 0x0640 - 0x064F */ + SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , /* 0x0650 - 0x065F */ + SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , /* 0x0660 - 0x066F */ + SNone , SRight, SRight, SRight, SNone , SRight, SRight, SRight, SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , /* 0x0670 - 0x067F */ + SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, /* 0x0680 - 0x068F */ + SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, SDual , SDual , SDual , SDual , SDual , SDual , /* 0x0690 - 0x069F */ + SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , /* 0x06A0 - 0x06AF */ + SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , SDual , /* 0x06B0 - 0x06BF */ + SRight, SDual , SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, SRight, SDual , SRight, SDual , SRight, /* 0x06C0 - 0x06CF */ + SDual , SDual , SRight, SRight, SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , /* 0x06D0 - 0x06DF */ + SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , /* 0x06E0 - 0x06EF */ + SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SNone , SDual , SDual , SDual , SNone , SNone , SNone /* 0x06F0 - 0x06FF */ +}; + +static inline SVGGlyphIdentifier::ArabicForm processArabicFormDetection(const UChar& curChar, bool& lastCharShapesRight, SVGGlyphIdentifier::ArabicForm* prevForm) +{ + SVGGlyphIdentifier::ArabicForm curForm; + + ArabicCharShapingMode shapingMode = SNone; + if (curChar >= 0x0622 && curChar <= 0x06FF) + shapingMode = s_arabicCharShapingMode[curChar - 0x0622]; + + // Use a simple state machine to identify the actual arabic form + // It depends on the order of the arabic form enum: + // enum ArabicForm { None = 0, Isolated, Terminal, Initial, Medial }; + + if (lastCharShapesRight && shapingMode == SDual) { + if (prevForm) { + int correctedForm = (int) *prevForm + 1; + ASSERT(correctedForm >= SVGGlyphIdentifier::None && correctedForm <= SVGGlyphIdentifier::Medial); + *prevForm = static_cast<SVGGlyphIdentifier::ArabicForm>(correctedForm); + } + + curForm = SVGGlyphIdentifier::Initial; + } else + curForm = shapingMode == SNone ? SVGGlyphIdentifier::None : SVGGlyphIdentifier::Isolated; + + lastCharShapesRight = shapingMode != SNone; + return curForm; +} + +static Vector<SVGGlyphIdentifier::ArabicForm> charactersWithArabicForm(const String& input, bool rtl) +{ + Vector<SVGGlyphIdentifier::ArabicForm> forms; + unsigned length = input.length(); + + bool containsArabic = false; + for (unsigned i = 0; i < length; ++i) { + if (isArabicChar(input[i])) { + containsArabic = true; + break; + } + } + + if (!containsArabic) + return forms; + + bool lastCharShapesRight = false; + + // Start identifying arabic forms + if (rtl) { + for (int i = length - 1; i >= 0; --i) + forms.prepend(processArabicFormDetection(input[i], lastCharShapesRight, forms.isEmpty() ? 0 : &forms.first())); + } else { + for (unsigned i = 0; i < length; ++i) + forms.append(processArabicFormDetection(input[i], lastCharShapesRight, forms.isEmpty() ? 0 : &forms.last())); + } + + return forms; +} + +static inline bool isCompatibleArabicForm(const SVGGlyphIdentifier& identifier, const Vector<SVGGlyphIdentifier::ArabicForm>& chars, unsigned startPosition, unsigned endPosition) +{ + if (chars.isEmpty()) + return true; + + Vector<SVGGlyphIdentifier::ArabicForm>::const_iterator it = chars.begin() + startPosition; + Vector<SVGGlyphIdentifier::ArabicForm>::const_iterator end = chars.begin() + endPosition; + + ASSERT(end <= chars.end()); + for (; it != end; ++it) { + if (*it != static_cast<SVGGlyphIdentifier::ArabicForm>(identifier.arabicForm) && *it != SVGGlyphIdentifier::None) + return false; + } + + return true; +} + +static inline bool isCompatibleGlyph(const SVGGlyphIdentifier& identifier, bool isVerticalText, const String& language, + const Vector<SVGGlyphIdentifier::ArabicForm>& chars, unsigned startPosition, unsigned endPosition) +{ + bool valid = true; + + // Check wheter orientation if glyph fits within the request + switch (identifier.orientation) { + case SVGGlyphIdentifier::Vertical: + valid = isVerticalText; + break; + case SVGGlyphIdentifier::Horizontal: + valid = !isVerticalText; + break; + case SVGGlyphIdentifier::Both: + break; + } + + if (!valid) + return false; + + // Check wheter languages are compatible + if (!identifier.languages.isEmpty()) { + // This glyph exists only in certain languages, if we're not specifying a + // language on the referencing element we're unable to use this glyph. + if (language.isEmpty()) + return false; + + // Split subcode from language, if existant. + String languagePrefix; + + int subCodeSeparator = language.find('-'); + if (subCodeSeparator != -1) + languagePrefix = language.left(subCodeSeparator); + + Vector<String>::const_iterator it = identifier.languages.begin(); + Vector<String>::const_iterator end = identifier.languages.end(); + + bool found = false; + for (; it != end; ++it) { + const String& cur = *it; + if (cur == language || cur == languagePrefix) { + found = true; + break; + } + } + + if (!found) + return false; + } + + // Check wheter arabic form is compatible + return isCompatibleArabicForm(identifier, chars, startPosition, endPosition); +} + +static inline const SVGFontData* svgFontAndFontFaceElementForFontData(const SimpleFontData* fontData, SVGFontFaceElement*& fontFace, SVGFontElement*& font) +{ + ASSERT(fontData->isCustomFont()); + ASSERT(fontData->isSVGFont()); + + const SVGFontData* svgFontData = static_cast<const SVGFontData*>(fontData->svgFontData()); + + fontFace = svgFontData->svgFontFaceElement(); + ASSERT(fontFace); + + font = fontFace->associatedFontElement(); + return svgFontData; +} + +// Helper class to walk a text run. Lookup a SVGGlyphIdentifier 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 SVGGlyphIdentifier&, 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) + { + // Should hold true for SVG text, otherwhise sth. is wrong + ASSERT(to - from == run.length()); + + Vector<SVGGlyphIdentifier::ArabicForm> chars(charactersWithArabicForm(String(run.data(from), run.length()), run.rtl())); + + SVGGlyphIdentifier identifier; + bool foundGlyph = false; + int characterLookupRange; + int endOfScanRange = to + m_walkerData.extraCharsAvailable; + + bool haveAltGlyph = false; + SVGGlyphIdentifier altGlyphIdentifier; + if (RenderObject* renderObject = run.referencingRenderObject()) { + if (renderObject->element() && renderObject->element()->hasTagName(SVGNames::altGlyphTag)) { + SVGGlyphElement* glyphElement = static_cast<SVGAltGlyphElement*>(renderObject->element())->glyphElement(); + if (glyphElement) { + haveAltGlyph = true; + altGlyphIdentifier = glyphElement->buildGlyphIdentifier(); + altGlyphIdentifier.isValid = true; + altGlyphIdentifier.nameLength = 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(run.data(i), characterLookupRange); + Vector<SVGGlyphIdentifier> glyphs; + if (haveAltGlyph) + glyphs.append(altGlyphIdentifier); + else + m_fontElement->getGlyphIdentifiersForString(lookupString, glyphs); + + Vector<SVGGlyphIdentifier>::iterator it = glyphs.begin(); + Vector<SVGGlyphIdentifier>::iterator end = glyphs.end(); + + for (; it != end; ++it) { + identifier = *it; + if (identifier.isValid && isCompatibleGlyph(identifier, isVerticalText, language, chars, i, i + identifier.nameLength)) { + ASSERT(characterLookupRange > 0); + i += identifier.nameLength - 1; + m_walkerData.charsConsumed += identifier.nameLength; + 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.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; +}; + +bool floatWidthUsingSVGFontCallback(const SVGGlyphIdentifier& 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; +} + +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()); + + data.length += font.floatWidth(run); +} + + +SVGFontElement* Font::svgFont() const +{ + if (!isSVGFont()) + return 0; + + SVGFontElement* fontElement = 0; + SVGFontFaceElement* fontFaceElement = 0; + if (svgFontAndFontFaceElementForFontData(primaryFont(), fontFaceElement, fontElement)) + return fontElement; + + return 0; +} + +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; + + String language; + bool isVerticalText = false; // Holds true for HTML text + + // TODO: language matching & svg glyphs should be possible for HTML text, too. + if (RenderObject* renderObject = run.referencingRenderObject()) { + isVerticalText = isVerticalWritingMode(renderObject->style()->svgStyle()); + + if (SVGElement* element = static_cast<SVGElement*>(renderObject->element())) + language = element->getAttribute(XMLNames::langAttr); + } + + SVGTextRunWalker<SVGTextRunWalkerMeasuredLengthData> runWalker(fontData, fontElement, data, floatWidthUsingSVGFontCallback, floatWidthMissingGlyphCallback); + runWalker.walk(run, isVerticalText, language, 0, run.length()); + charsConsumed = data.charsConsumed; + glyphName = data.glyphName; + return data.length; + } + + return 0.0f; +} + +float Font::floatWidthUsingSVGFont(const TextRun& run) const +{ + int charsConsumed; + String glyphName; + return floatWidthOfSubStringUsingSVGFont(this, run, 0, 0, run.length(), charsConsumed, glyphName); +} + +float Font::floatWidthUsingSVGFont(const TextRun& run, int extraCharsAvailable, int& charsConsumed, String& glyphName) const +{ + return floatWidthOfSubStringUsingSVGFont(this, 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<SVGGlyphIdentifier> glyphIdentifiers; + Vector<UChar> fallbackCharacters; +}; + +bool drawTextUsingSVGFontCallback(const SVGGlyphIdentifier& identifier, SVGTextRunWalkerDrawTextData& data) +{ + data.glyphIdentifiers.append(identifier); + return true; +} + +void drawTextMissingGlyphCallback(const TextRun& run, SVGTextRunWalkerDrawTextData& data) +{ + ASSERT(run.length() == 1); + data.glyphIdentifiers.append(SVGGlyphIdentifier()); + data.fallbackCharacters.append(run[0]); +} + +void Font::drawTextUsingSVGFont(GraphicsContext* context, const TextRun& run, + const FloatPoint& point, int from, int to) const +{ + SVGFontElement* fontElement = 0; + SVGFontFaceElement* fontFaceElement = 0; + + if (const SVGFontData* fontData = svgFontAndFontFaceElementForFontData(primaryFont(), fontFaceElement, fontElement)) { + if (!fontElement) + return; + + SVGTextRunWalkerDrawTextData data; + FloatPoint currentPoint = point; + float scale = convertEmUnitToPixel(size(), fontFaceElement->unitsPerEm(), 1.0f); + + SVGPaintServer* activePaintServer = run.activePaintServer(); + + // If renderObject is not set, we're dealing for HTML text rendered using SVG Fonts. + if (!run.referencingRenderObject()) { + ASSERT(!activePaintServer); + + // TODO: We're only supporting simple filled HTML text so far. + SVGPaintServerSolid* solidPaintServer = SVGPaintServer::sharedSolidPaintServer(); + solidPaintServer->setColor(context->fillColor()); + + activePaintServer = solidPaintServer; + } + + ASSERT(activePaintServer); + + int charsConsumed; + String glyphName; + bool isVerticalText = false; + float xStartOffset = floatWidthOfSubStringUsingSVGFont(this, run, 0, run.rtl() ? to : 0, run.rtl() ? run.length() : from, charsConsumed, glyphName); + FloatPoint glyphOrigin; + + String language; + + // TODO: language matching & svg glyphs should be possible for HTML text, too. + if (run.referencingRenderObject()) { + isVerticalText = isVerticalWritingMode(run.referencingRenderObject()->style()->svgStyle()); + + if (SVGElement* element = static_cast<SVGElement*>(run.referencingRenderObject()->element())) + language = element->getAttribute(XMLNames::langAttr); + } + + if (!isVerticalText) { + glyphOrigin.setX(fontData->horizontalOriginX() * scale); + glyphOrigin.setY(fontData->horizontalOriginY() * scale); + } + + data.extraCharsAvailable = 0; + + SVGTextRunWalker<SVGTextRunWalkerDrawTextData> runWalker(fontData, fontElement, data, drawTextUsingSVGFontCallback, drawTextMissingGlyphCallback); + runWalker.walk(run, isVerticalText, language, from, to); + + SVGPaintTargetType targetType = context->textDrawingMode() == cTextStroke ? ApplyToStrokeTargetType : ApplyToFillTargetType; + + unsigned numGlyphs = data.glyphIdentifiers.size(); + unsigned fallbackCharacterIndex = 0; + for (unsigned i = 0; i < numGlyphs; ++i) { + const SVGGlyphIdentifier& 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()) { + context->save(); + + if (isVerticalText) { + glyphOrigin.setX(identifier.verticalOriginX * scale); + glyphOrigin.setY(identifier.verticalOriginY * scale); + } + + context->translate(xStartOffset + currentPoint.x() + glyphOrigin.x(), currentPoint.y() + glyphOrigin.y()); + context->scale(FloatSize(scale, -scale)); + + context->beginPath(); + context->addPath(identifier.pathData); + + if (activePaintServer->setup(context, run.referencingRenderObject(), targetType)) { + // Spec: Any properties specified on a text elements which represents a length, such as the + // 'stroke-width' property, might produce surprising results since the length value will be + // processed in the coordinate system of the glyph. (TODO: What other lengths? miter-limit? dash-offset?) + if (targetType == ApplyToStrokeTargetType && scale != 0.0f) + context->setStrokeThickness(context->strokeThickness() / scale); + + activePaintServer->renderPath(context, run.referencingRenderObject(), targetType); + activePaintServer->teardown(context, run.referencingRenderObject(), targetType); + } + + context->restore(); + } + + if (isVerticalText) + currentPoint.move(0.0f, identifier.verticalAdvanceY * scale); + else + currentPoint.move(identifier.horizontalAdvanceX * scale, 0.0f); + } else { + // Handle system font fallback + FontDescription fontDescription(context->font().fontDescription()); + fontDescription.setFamily(FontFamily()); + Font font(fontDescription, 0, 0); // spacing handled by SVG text code. + font.update(context->font().fontSelector()); + + TextRun fallbackCharacterRun(run); + 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.floatWidth(fallbackCharacterRun)); + else + currentPoint.move(font.floatWidth(fallbackCharacterRun), 0.0f); + + fallbackCharacterIndex++; + } + } + } +} + +FloatRect Font::selectionRectForTextUsingSVGFont(const TextRun& run, const IntPoint& point, int height, int from, int to) const +{ + int charsConsumed; + String glyphName; + + return FloatRect(point.x() + floatWidthOfSubStringUsingSVGFont(this, run, 0, run.rtl() ? to : 0, run.rtl() ? run.length() : from, charsConsumed, glyphName), + point.y(), floatWidthOfSubStringUsingSVGFont(this, run, 0, from, to, charsConsumed, glyphName), height); +} + +int Font::offsetForPositionForTextUsingSVGFont(const TextRun&, int position, bool includePartialGlyphs) 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 |