WebCore/rendering/SVGTextLayoutUtilities.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2     Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
       
     3     Copyright (C) Research In Motion Limited 2010. All rights reserved.
       
     4 
       
     5     This library is free software; you can redistribute it and/or
       
     6     modify it under the terms of the GNU Library General Public
       
     7     License as published by the Free Software Foundation; either
       
     8     version 2 of the License, or (at your option) any later version.
       
     9 
       
    10     This library is distributed in the hope that it will be useful,
       
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    13     Library General Public License for more details.
       
    14 
       
    15     You should have received a copy of the GNU Library General Public License
       
    16     along with this library; see the file COPYING.LIB.  If not, write to
       
    17     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
       
    18     Boston, MA 02110-1301, USA.
       
    19 */
       
    20 
       
    21 #include "config.h"
       
    22 #include "SVGTextLayoutUtilities.h"
       
    23 
       
    24 #if ENABLE(SVG)
       
    25 #include "FloatPoint.h"
       
    26 #include "InlineTextBox.h"
       
    27 #include "RenderObject.h"
       
    28 #include "SVGCharacterData.h"
       
    29 #include "SVGCharacterLayoutInfo.h"
       
    30 #include "SVGFontElement.h"
       
    31 #include "SVGRenderStyle.h"
       
    32 #include "SVGTextChunkLayoutInfo.h"
       
    33 #include "TextRun.h"
       
    34 #include "UnicodeRange.h"
       
    35 
       
    36 #include <float.h>
       
    37 
       
    38 namespace WebCore {
       
    39 
       
    40 bool isVerticalWritingMode(const SVGRenderStyle* style)
       
    41 {
       
    42     return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; 
       
    43 }
       
    44 
       
    45 static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font)
       
    46 {
       
    47     ASSERT(text);
       
    48 
       
    49     const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0;
       
    50     ASSERT(style);
       
    51 
       
    52     const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0;
       
    53 
       
    54     EDominantBaseline baseline = style->dominantBaseline();
       
    55     if (baseline == DB_AUTO) {
       
    56         if (isVerticalText)
       
    57             baseline = DB_CENTRAL;
       
    58         else
       
    59             baseline = DB_ALPHABETIC;
       
    60     }
       
    61 
       
    62     switch (baseline) {
       
    63     case DB_USE_SCRIPT:
       
    64         // TODO: The dominant-baseline and the baseline-table components are set by
       
    65         //       determining the predominant script of the character data content.
       
    66         return AB_ALPHABETIC;
       
    67     case DB_NO_CHANGE:
       
    68         {
       
    69             if (parentStyle)
       
    70                 return dominantBaselineToShift(isVerticalText, text->parent(), font);
       
    71 
       
    72             ASSERT_NOT_REACHED();
       
    73             return AB_AUTO;
       
    74         }
       
    75     case DB_RESET_SIZE:
       
    76         {
       
    77             if (parentStyle)
       
    78                 return dominantBaselineToShift(isVerticalText, text->parent(), font);
       
    79 
       
    80             ASSERT_NOT_REACHED();
       
    81             return AB_AUTO;    
       
    82         }
       
    83     case DB_IDEOGRAPHIC:
       
    84         return AB_IDEOGRAPHIC;
       
    85     case DB_ALPHABETIC:
       
    86         return AB_ALPHABETIC;
       
    87     case DB_HANGING:
       
    88         return AB_HANGING;
       
    89     case DB_MATHEMATICAL:
       
    90         return AB_MATHEMATICAL;
       
    91     case DB_CENTRAL:
       
    92         return AB_CENTRAL;
       
    93     case DB_MIDDLE:
       
    94         return AB_MIDDLE;
       
    95     case DB_TEXT_AFTER_EDGE:
       
    96         return AB_TEXT_AFTER_EDGE;
       
    97     case DB_TEXT_BEFORE_EDGE:
       
    98         return AB_TEXT_BEFORE_EDGE;
       
    99     default:
       
   100         ASSERT_NOT_REACHED();
       
   101         return AB_AUTO;
       
   102     }
       
   103 }
       
   104 
       
   105 float alignmentBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font)
       
   106 {
       
   107     ASSERT(text);
       
   108 
       
   109     const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0;
       
   110     ASSERT(style);
       
   111 
       
   112     const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0;
       
   113 
       
   114     EAlignmentBaseline baseline = style->alignmentBaseline();
       
   115     if (baseline == AB_AUTO) {
       
   116         if (parentStyle && style->dominantBaseline() == DB_AUTO)
       
   117             baseline = dominantBaselineToShift(isVerticalText, text->parent(), font);
       
   118         else
       
   119             baseline = dominantBaselineToShift(isVerticalText, text, font);
       
   120 
       
   121         ASSERT(baseline != AB_AUTO);    
       
   122     }
       
   123 
       
   124     // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
       
   125     switch (baseline) {
       
   126     case AB_BASELINE:
       
   127         {
       
   128             if (parentStyle)
       
   129                 return dominantBaselineToShift(isVerticalText, text->parent(), font);
       
   130 
       
   131             return 0.0f;
       
   132         }
       
   133     case AB_BEFORE_EDGE:
       
   134     case AB_TEXT_BEFORE_EDGE:
       
   135         return font.ascent();
       
   136     case AB_MIDDLE:
       
   137         return font.xHeight() / 2.0f;
       
   138     case AB_CENTRAL:
       
   139         // Not needed, we're taking this into account already for vertical text!
       
   140         // return (font.ascent() - font.descent()) / 2.0f;
       
   141         return 0.0f;
       
   142     case AB_AFTER_EDGE:
       
   143     case AB_TEXT_AFTER_EDGE:
       
   144     case AB_IDEOGRAPHIC:
       
   145         return font.descent();
       
   146     case AB_ALPHABETIC:
       
   147         return 0.0f;
       
   148     case AB_HANGING:
       
   149         return font.ascent() * 8.0f / 10.0f;
       
   150     case AB_MATHEMATICAL:
       
   151         return font.ascent() / 2.0f;
       
   152     default:
       
   153         ASSERT_NOT_REACHED();
       
   154         return 0.0f;
       
   155     }
       
   156 }
       
   157 
       
   158 float glyphOrientationToAngle(const SVGRenderStyle* svgStyle, bool isVerticalText, const UChar& character)
       
   159 {
       
   160     switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) {
       
   161     case GO_AUTO:
       
   162         {
       
   163             // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
       
   164             // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
       
   165             unsigned int unicodeRange = findCharUnicodeRange(character);
       
   166             if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
       
   167                 return 90.0f;
       
   168 
       
   169             return 0.0f;
       
   170         }
       
   171     case GO_90DEG:
       
   172         return 90.0f;
       
   173     case GO_180DEG:
       
   174         return 180.0f;
       
   175     case GO_270DEG:
       
   176         return 270.0f;
       
   177     case GO_0DEG:
       
   178     default:
       
   179         return 0.0f;
       
   180     }
       
   181 }
       
   182 
       
   183 static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
       
   184 {
       
   185     return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f;
       
   186 }
       
   187 
       
   188 float applyGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font& font, SVGChar& svgChar, float& xOrientationShift, float& yOrientationShift)
       
   189 {
       
   190     bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle);
       
   191 
       
   192     // The function is based on spec requirements:
       
   193     //
       
   194     // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
       
   195     // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
       
   196     //
       
   197     // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
       
   198     // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph.
       
   199 
       
   200     // vertical orientation handling
       
   201     if (isVerticalText) {
       
   202         if (orientationAngle == 0.0f) {
       
   203             xOrientationShift = -glyphWidth / 2.0f;
       
   204             yOrientationShift = font.ascent();
       
   205         } else if (orientationAngle == 90.0f) {
       
   206             xOrientationShift = -glyphHeight;
       
   207             yOrientationShift = font.descent();
       
   208             svgChar.orientationShiftY = -font.ascent();
       
   209         } else if (orientationAngle == 270.0f) {
       
   210             xOrientationShift = glyphHeight;
       
   211             yOrientationShift = font.descent();
       
   212             svgChar.orientationShiftX = -glyphWidth;
       
   213             svgChar.orientationShiftY = -font.ascent();
       
   214         } else if (orientationAngle == 180.0f) {
       
   215             yOrientationShift = font.ascent();
       
   216             svgChar.orientationShiftX = -glyphWidth / 2.0f;
       
   217             svgChar.orientationShiftY = font.ascent() - font.descent();
       
   218         }
       
   219 
       
   220         // vertical advance calculation
       
   221         if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees)
       
   222             return glyphWidth;
       
   223 
       
   224         return glyphHeight; 
       
   225     }
       
   226 
       
   227     // horizontal orientation handling
       
   228     if (orientationAngle == 90.0f) {
       
   229         xOrientationShift = glyphWidth / 2.0f;
       
   230         yOrientationShift = -font.descent();
       
   231         svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent(); 
       
   232         svgChar.orientationShiftY = font.descent();
       
   233     } else if (orientationAngle == 270.0f) {
       
   234         xOrientationShift = -glyphWidth / 2.0f;
       
   235         yOrientationShift = -font.descent();
       
   236         svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent();
       
   237         svgChar.orientationShiftY = glyphHeight;
       
   238     } else if (orientationAngle == 180.0f) {
       
   239         xOrientationShift = glyphWidth / 2.0f;
       
   240         svgChar.orientationShiftX = -glyphWidth / 2.0f;
       
   241         svgChar.orientationShiftY = font.ascent() - font.descent();
       
   242     }
       
   243 
       
   244     // horizontal advance calculation
       
   245     if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees)
       
   246         return glyphHeight;
       
   247 
       
   248     return glyphWidth;
       
   249 }
       
   250 
       
   251 FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::iterator end)
       
   252 {
       
   253     float lowX = FLT_MAX, lowY = FLT_MAX;
       
   254     for (; it != end; ++it) {
       
   255         if (it->isHidden())
       
   256             continue;
       
   257 
       
   258         float x = (*it).x;
       
   259         float y = (*it).y;
       
   260 
       
   261         if (x < lowX)
       
   262             lowX = x;
       
   263 
       
   264         if (y < lowY)
       
   265             lowY = y;
       
   266     }
       
   267 
       
   268     return FloatPoint(lowX, lowY);
       
   269 }
       
   270 
       
   271 float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range)
       
   272 {
       
   273     ASSERT(!range.isOpen());
       
   274     ASSERT(range.isClosed());
       
   275     ASSERT(range.box->isSVGInlineTextBox());
       
   276 
       
   277     InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box);
       
   278     RenderText* text = textBox->textRenderer();
       
   279     RenderStyle* style = text->style();
       
   280     return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox));
       
   281 }
       
   282 
       
   283 float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range)
       
   284 {
       
   285     ASSERT(!range.isOpen());
       
   286     ASSERT(range.isClosed());
       
   287     ASSERT(range.box->isSVGInlineTextBox());
       
   288 
       
   289     InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box);
       
   290     return (range.endOffset - range.startOffset) * textBox->textRenderer()->style()->font().height();
       
   291 }
       
   292 
       
   293 TextRun svgTextRunForInlineTextBox(const UChar* characters, int length, const RenderStyle* style, const InlineTextBox* textBox)
       
   294 {
       
   295     ASSERT(textBox);
       
   296     ASSERT(style);
       
   297 
       
   298     TextRun run(characters
       
   299                 , length
       
   300                 , false /* allowTabs */
       
   301                 , 0 /* xPos, only relevant with allowTabs=true */
       
   302                 , 0 /* padding, only relevant for justified text, not relevant for SVG */
       
   303                 , textBox->direction() == RTL
       
   304                 , textBox->m_dirOverride || style->visuallyOrdered() /* directionalOverride */);
       
   305 
       
   306 #if ENABLE(SVG_FONTS)
       
   307     run.setReferencingRenderObject(textBox->textRenderer()->parent());
       
   308 #endif
       
   309 
       
   310     // Disable any word/character rounding.
       
   311     run.disableRoundingHacks();
       
   312 
       
   313     // We handle letter & word spacing ourselves.
       
   314     run.disableSpacing();
       
   315     return run;
       
   316 }
       
   317 
       
   318 float calculateCSSKerning(const RenderStyle* style)
       
   319 {
       
   320     const Font& font = style->font();
       
   321     const SVGRenderStyle* svgStyle = style->svgStyle();
       
   322 
       
   323     float kerning = 0.0f;
       
   324     if (CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->kerning())) {
       
   325         kerning = primitive->getFloatValue();
       
   326 
       
   327         if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize())
       
   328             kerning = kerning / 100.0f * font.pixelSize();
       
   329     }
       
   330 
       
   331     return kerning;
       
   332 }
       
   333 
       
   334 bool applySVGKerning(SVGCharacterLayoutInfo& info, const RenderStyle* style, SVGLastGlyphInfo& lastGlyph, const String& unicodeString, const String& glyphName, bool isVerticalText)
       
   335 {
       
   336 #if ENABLE(SVG_FONTS)
       
   337     float kerning = 0.0f;
       
   338 
       
   339     const Font& font = style->font();
       
   340     if (!font.isSVGFont()) {
       
   341         lastGlyph.isValid = false;
       
   342         return false;
       
   343     }
       
   344 
       
   345     SVGFontElement* svgFont = font.svgFont();
       
   346     ASSERT(svgFont);
       
   347 
       
   348     if (lastGlyph.isValid) {
       
   349         if (isVerticalText)
       
   350             kerning = svgFont->verticalKerningForPairOfStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeString, glyphName);
       
   351         else
       
   352             kerning = svgFont->horizontalKerningForPairOfStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeString, glyphName);
       
   353     }
       
   354 
       
   355     lastGlyph.unicode = unicodeString;
       
   356     lastGlyph.glyphName = glyphName;
       
   357     lastGlyph.isValid = true;
       
   358     kerning *= style->font().size() / style->font().primaryFont()->unitsPerEm();
       
   359 
       
   360     if (kerning != 0.0f) {
       
   361         if (isVerticalText)
       
   362             info.cury -= kerning;
       
   363         else
       
   364             info.curx -= kerning;
       
   365         return true;
       
   366     }
       
   367 #else
       
   368     UNUSED_PARAM(info);
       
   369     UNUSED_PARAM(item);
       
   370     UNUSED_PARAM(lastGlyph);
       
   371     UNUSED_PARAM(unicodeString);
       
   372     UNUSED_PARAM(glyphName);
       
   373 #endif
       
   374     return false;
       
   375 }
       
   376 
       
   377 }
       
   378 
       
   379 #endif