Home | History | Annotate | Download | only in win
      1 /*
      2  * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "UniscribeController.h"
     28 #include "Font.h"
     29 #include "SimpleFontData.h"
     30 #include "TextRun.h"
     31 #include <wtf/MathExtras.h>
     32 
     33 using namespace std;
     34 
     35 namespace WebCore {
     36 
     37 // FIXME: Rearchitect this to be more like WidthIterator in Font.cpp.  Have an advance() method
     38 // that does stuff in that method instead of doing everything in the constructor.  Have advance()
     39 // take the GlyphBuffer as an arg so that we don't have to populate the glyph buffer when
     40 // measuring.
     41 UniscribeController::UniscribeController(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts)
     42     : m_font(*font)
     43     , m_run(run)
     44     , m_fallbackFonts(fallbackFonts)
     45     , m_minGlyphBoundingBoxX(numeric_limits<float>::max())
     46     , m_maxGlyphBoundingBoxX(numeric_limits<float>::min())
     47     , m_minGlyphBoundingBoxY(numeric_limits<float>::max())
     48     , m_maxGlyphBoundingBoxY(numeric_limits<float>::min())
     49     , m_end(run.length())
     50     , m_currentCharacter(0)
     51     , m_runWidthSoFar(0)
     52     , m_padding(run.expansion())
     53     , m_computingOffsetPosition(false)
     54     , m_includePartialGlyphs(false)
     55     , m_offsetX(0)
     56     , m_offsetPosition(0)
     57 {
     58     if (!m_padding)
     59         m_padPerSpace = 0;
     60     else {
     61         float numSpaces = 0;
     62         for (int s = 0; s < m_run.length(); s++) {
     63             if (Font::treatAsSpace(m_run[s]))
     64                 numSpaces++;
     65         }
     66 
     67         if (numSpaces == 0)
     68             m_padPerSpace = 0;
     69         else
     70             m_padPerSpace = m_padding / numSpaces;
     71     }
     72 
     73     // Null out our uniscribe structs
     74     resetControlAndState();
     75 }
     76 
     77 int UniscribeController::offsetForPosition(int x, bool includePartialGlyphs)
     78 {
     79     m_computingOffsetPosition = true;
     80     m_includePartialGlyphs = includePartialGlyphs;
     81     m_offsetX = x;
     82     m_offsetPosition = 0;
     83     advance(m_run.length());
     84     if (m_computingOffsetPosition) {
     85         // The point is to the left or to the right of the entire run.
     86         if (m_offsetX >= m_runWidthSoFar && m_run.ltr() || m_offsetX < 0 && m_run.rtl())
     87             m_offsetPosition = m_end;
     88     }
     89     m_computingOffsetPosition = false;
     90     return m_offsetPosition;
     91 }
     92 
     93 void UniscribeController::advance(unsigned offset, GlyphBuffer* glyphBuffer)
     94 {
     95     // FIXME: We really want to be using a newer version of Uniscribe that supports the new OpenType
     96     // functions.  Those functions would allow us to turn off kerning and ligatures.  Without being able
     97     // to do that, we will have buggy line breaking and metrics when simple and complex text are close
     98     // together (the complex code path will narrow the text because of kerning and ligatures and then
     99     // when bidi processing splits into multiple runs, the simple portions will get wider and cause us to
    100     // spill off the edge of a line).
    101     if (static_cast<int>(offset) > m_end)
    102         offset = m_end;
    103 
    104     // Itemize the string.
    105     const UChar* cp = m_run.data(m_currentCharacter);
    106     int length = offset - m_currentCharacter;
    107     if (length <= 0)
    108         return;
    109 
    110     unsigned baseCharacter = m_currentCharacter;
    111 
    112     // We break up itemization of the string by fontData and (if needed) the use of small caps.
    113 
    114     // FIXME: It's inconsistent that we use logical order when itemizing, since this
    115     // does not match normal RTL.
    116 
    117     // FIXME: This function should decode surrogate pairs. Currently it makes little difference that
    118     // it does not because the font cache on Windows does not support non-BMP characters.
    119     Vector<UChar, 256> smallCapsBuffer;
    120     if (m_font.isSmallCaps())
    121         smallCapsBuffer.resize(length);
    122 
    123     unsigned indexOfFontTransition = m_run.rtl() ? length - 1 : 0;
    124     const UChar* curr = m_run.rtl() ? cp + length  - 1 : cp;
    125     const UChar* end = m_run.rtl() ? cp - 1 : cp + length;
    126 
    127     const SimpleFontData* fontData;
    128     const SimpleFontData* nextFontData = m_font.glyphDataForCharacter(*curr, false).fontData;
    129 
    130     UChar newC = 0;
    131 
    132     bool isSmallCaps;
    133     bool nextIsSmallCaps = m_font.isSmallCaps() && !(U_GET_GC_MASK(*curr) & U_GC_M_MASK) && (newC = u_toupper(*curr)) != *curr;
    134 
    135     if (nextIsSmallCaps)
    136         smallCapsBuffer[curr - cp] = newC;
    137 
    138     while (true) {
    139         curr = m_run.rtl() ? curr - 1 : curr + 1;
    140         if (curr == end)
    141             break;
    142 
    143         fontData = nextFontData;
    144         isSmallCaps = nextIsSmallCaps;
    145         int index = curr - cp;
    146         UChar c = *curr;
    147 
    148         bool forceSmallCaps = isSmallCaps && (U_GET_GC_MASK(c) & U_GC_M_MASK);
    149         nextFontData = m_font.glyphDataForCharacter(*curr, false, forceSmallCaps ? SmallCapsVariant : AutoVariant).fontData;
    150         if (m_font.isSmallCaps()) {
    151             nextIsSmallCaps = forceSmallCaps || (newC = u_toupper(c)) != c;
    152             if (nextIsSmallCaps)
    153                 smallCapsBuffer[index] = forceSmallCaps ? c : newC;
    154         }
    155 
    156         if (m_fallbackFonts && nextFontData != fontData && fontData != m_font.primaryFont())
    157             m_fallbackFonts->add(fontData);
    158 
    159         if (nextFontData != fontData || nextIsSmallCaps != isSmallCaps) {
    160             int itemStart = m_run.rtl() ? index + 1 : indexOfFontTransition;
    161             int itemLength = m_run.rtl() ? indexOfFontTransition - index : index - indexOfFontTransition;
    162             m_currentCharacter = baseCharacter + itemStart;
    163             itemizeShapeAndPlace((isSmallCaps ? smallCapsBuffer.data() : cp) + itemStart, itemLength, fontData, glyphBuffer);
    164             indexOfFontTransition = index;
    165         }
    166     }
    167 
    168     int itemLength = m_run.rtl() ? indexOfFontTransition + 1 : length - indexOfFontTransition;
    169     if (itemLength) {
    170         if (m_fallbackFonts && nextFontData != m_font.primaryFont())
    171             m_fallbackFonts->add(nextFontData);
    172 
    173         int itemStart = m_run.rtl() ? 0 : indexOfFontTransition;
    174         m_currentCharacter = baseCharacter + itemStart;
    175         itemizeShapeAndPlace((nextIsSmallCaps ? smallCapsBuffer.data() : cp) + itemStart, itemLength, nextFontData, glyphBuffer);
    176     }
    177 
    178     m_currentCharacter = baseCharacter + length;
    179 }
    180 
    181 void UniscribeController::itemizeShapeAndPlace(const UChar* cp, unsigned length, const SimpleFontData* fontData, GlyphBuffer* glyphBuffer)
    182 {
    183     // ScriptItemize (in Windows XP versions prior to SP2) can overflow by 1.  This is why there is an extra empty item
    184     // hanging out at the end of the array
    185     m_items.resize(6);
    186     int numItems = 0;
    187     while (ScriptItemize(cp, length, m_items.size() - 1, &m_control, &m_state, m_items.data(), &numItems) == E_OUTOFMEMORY) {
    188         m_items.resize(m_items.size() * 2);
    189         resetControlAndState();
    190     }
    191     m_items.resize(numItems + 1);
    192 
    193     if (m_run.rtl()) {
    194         for (int i = m_items.size() - 2; i >= 0; i--) {
    195             if (!shapeAndPlaceItem(cp, i, fontData, glyphBuffer))
    196                 return;
    197         }
    198     } else {
    199         for (unsigned i = 0; i < m_items.size() - 1; i++) {
    200             if (!shapeAndPlaceItem(cp, i, fontData, glyphBuffer))
    201                 return;
    202         }
    203     }
    204 }
    205 
    206 void UniscribeController::resetControlAndState()
    207 {
    208     memset(&m_control, 0, sizeof(SCRIPT_CONTROL));
    209     memset(&m_state, 0, sizeof(SCRIPT_STATE));
    210 
    211     // Set up the correct direction for the run.
    212     m_state.uBidiLevel = m_run.rtl();
    213 
    214     // Lock the correct directional override.
    215     m_state.fOverrideDirection = m_run.directionalOverride();
    216 }
    217 
    218 bool UniscribeController::shapeAndPlaceItem(const UChar* cp, unsigned i, const SimpleFontData* fontData, GlyphBuffer* glyphBuffer)
    219 {
    220     // Determine the string for this item.
    221     const UChar* str = cp + m_items[i].iCharPos;
    222     int len = m_items[i+1].iCharPos - m_items[i].iCharPos;
    223     SCRIPT_ITEM item = m_items[i];
    224 
    225     // Set up buffers to hold the results of shaping the item.
    226     Vector<WORD> glyphs;
    227     Vector<WORD> clusters;
    228     Vector<SCRIPT_VISATTR> visualAttributes;
    229     clusters.resize(len);
    230 
    231     // Shape the item.
    232     // The recommended size for the glyph buffer is 1.5 * the character length + 16 in the uniscribe docs.
    233     // Apparently this is a good size to avoid having to make repeated calls to ScriptShape.
    234     glyphs.resize(1.5 * len + 16);
    235     visualAttributes.resize(glyphs.size());
    236 
    237     if (!shape(str, len, item, fontData, glyphs, clusters, visualAttributes))
    238         return true;
    239 
    240     // We now have a collection of glyphs.
    241     Vector<GOFFSET> offsets;
    242     Vector<int> advances;
    243     offsets.resize(glyphs.size());
    244     advances.resize(glyphs.size());
    245     int glyphCount = 0;
    246     HRESULT placeResult = ScriptPlace(0, fontData->scriptCache(), glyphs.data(), glyphs.size(), visualAttributes.data(),
    247                                       &item.a, advances.data(), offsets.data(), 0);
    248     if (placeResult == E_PENDING) {
    249         // The script cache isn't primed with enough info yet.  We need to select our HFONT into
    250         // a DC and pass the DC in to ScriptPlace.
    251         HDC hdc = GetDC(0);
    252         HFONT hfont = fontData->platformData().hfont();
    253         HFONT oldFont = (HFONT)SelectObject(hdc, hfont);
    254         placeResult = ScriptPlace(hdc, fontData->scriptCache(), glyphs.data(), glyphs.size(), visualAttributes.data(),
    255                                   &item.a, advances.data(), offsets.data(), 0);
    256         SelectObject(hdc, oldFont);
    257         ReleaseDC(0, hdc);
    258     }
    259 
    260     if (FAILED(placeResult) || glyphs.isEmpty())
    261         return true;
    262 
    263     // Convert all chars that should be treated as spaces to use the space glyph.
    264     // We also create a map that allows us to quickly go from space glyphs back to their corresponding characters.
    265     Vector<int> spaceCharacters(glyphs.size());
    266     spaceCharacters.fill(-1);
    267 
    268     const float cLogicalScale = fontData->platformData().useGDI() ? 1.0f : 32.0f;
    269     unsigned logicalSpaceWidth = fontData->spaceWidth() * cLogicalScale;
    270     float spaceWidth = fontData->spaceWidth();
    271 
    272     for (int k = 0; k < len; k++) {
    273         UChar ch = *(str + k);
    274         bool treatAsSpace = Font::treatAsSpace(ch);
    275         bool treatAsZeroWidthSpace = ch == zeroWidthSpace || Font::treatAsZeroWidthSpace(ch);
    276         if (treatAsSpace || treatAsZeroWidthSpace) {
    277             // Substitute in the space glyph at the appropriate place in the glyphs
    278             // array.
    279             glyphs[clusters[k]] = fontData->spaceGlyph();
    280             advances[clusters[k]] = treatAsSpace ? logicalSpaceWidth : 0;
    281             if (treatAsSpace)
    282                 spaceCharacters[clusters[k]] = m_currentCharacter + k + item.iCharPos;
    283         }
    284     }
    285 
    286     // Populate our glyph buffer with this information.
    287     bool hasExtraSpacing = m_font.letterSpacing() || m_font.wordSpacing() || m_padding;
    288 
    289     float leftEdge = m_runWidthSoFar;
    290 
    291     for (unsigned k = 0; k < glyphs.size(); k++) {
    292         Glyph glyph = glyphs[k];
    293         float advance = advances[k] / cLogicalScale;
    294         float offsetX = offsets[k].du / cLogicalScale;
    295         float offsetY = offsets[k].dv / cLogicalScale;
    296 
    297         // Match AppKit's rules for the integer vs. non-integer rendering modes.
    298         float roundedAdvance = roundf(advance);
    299         if (!m_font.isPrinterFont() && !fontData->isSystemFont()) {
    300             advance = roundedAdvance;
    301             offsetX = roundf(offsetX);
    302             offsetY = roundf(offsetY);
    303         }
    304 
    305         advance += fontData->syntheticBoldOffset();
    306 
    307         if (hasExtraSpacing) {
    308             // If we're a glyph with an advance, go ahead and add in letter-spacing.
    309             // That way we weed out zero width lurkers.  This behavior matches the fast text code path.
    310             if (advance && m_font.letterSpacing())
    311                 advance += m_font.letterSpacing();
    312 
    313             // Handle justification and word-spacing.
    314             int characterIndex = spaceCharacters[k];
    315             // characterIndex is left at the initial value of -1 for glyphs that do not map back to treated-as-space characters.
    316             if (characterIndex != -1) {
    317                 // Account for padding. WebCore uses space padding to justify text.
    318                 // We distribute the specified padding over the available spaces in the run.
    319                 if (m_padding) {
    320                     // Use leftover padding if not evenly divisible by number of spaces.
    321                     if (m_padding < m_padPerSpace) {
    322                         advance += m_padding;
    323                         m_padding = 0;
    324                     } else {
    325                         m_padding -= m_padPerSpace;
    326                         advance += m_padPerSpace;
    327                     }
    328                 }
    329 
    330                 // Account for word-spacing.
    331                 if (characterIndex > 0 && !Font::treatAsSpace(*m_run.data(characterIndex - 1)) && m_font.wordSpacing())
    332                     advance += m_font.wordSpacing();
    333             }
    334         }
    335 
    336         m_runWidthSoFar += advance;
    337 
    338         // FIXME: We need to take the GOFFSETS for combining glyphs and store them in the glyph buffer
    339         // as well, so that when the time comes to draw those glyphs, we can apply the appropriate
    340         // translation.
    341         if (glyphBuffer) {
    342             FloatSize size(offsetX, -offsetY);
    343             glyphBuffer->add(glyph, fontData, advance, &size);
    344         }
    345 
    346         FloatRect glyphBounds = fontData->boundsForGlyph(glyph);
    347         glyphBounds.move(m_glyphOrigin.x(), m_glyphOrigin.y());
    348         m_minGlyphBoundingBoxX = min(m_minGlyphBoundingBoxX, glyphBounds.x());
    349         m_maxGlyphBoundingBoxX = max(m_maxGlyphBoundingBoxX, glyphBounds.maxX());
    350         m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, glyphBounds.y());
    351         m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, glyphBounds.maxY());
    352         m_glyphOrigin.move(advance + offsetX, -offsetY);
    353 
    354         // Mutate the glyph array to contain our altered advances.
    355         if (m_computingOffsetPosition)
    356             advances[k] = advance;
    357     }
    358 
    359     while (m_computingOffsetPosition && m_offsetX >= leftEdge && m_offsetX < m_runWidthSoFar) {
    360         // The position is somewhere inside this run.
    361         int trailing = 0;
    362         ScriptXtoCP(m_offsetX - leftEdge, clusters.size(), glyphs.size(), clusters.data(), visualAttributes.data(),
    363                     advances.data(), &item.a, &m_offsetPosition, &trailing);
    364         if (trailing && m_includePartialGlyphs && m_offsetPosition < len - 1) {
    365             m_offsetPosition += m_currentCharacter + m_items[i].iCharPos;
    366             m_offsetX += m_run.rtl() ? -trailing : trailing;
    367         } else {
    368             m_computingOffsetPosition = false;
    369             m_offsetPosition += m_currentCharacter + m_items[i].iCharPos;
    370             if (trailing && m_includePartialGlyphs)
    371                m_offsetPosition++;
    372             return false;
    373         }
    374     }
    375 
    376     return true;
    377 }
    378 
    379 bool UniscribeController::shape(const UChar* str, int len, SCRIPT_ITEM item, const SimpleFontData* fontData,
    380                                 Vector<WORD>& glyphs, Vector<WORD>& clusters,
    381                                 Vector<SCRIPT_VISATTR>& visualAttributes)
    382 {
    383     HDC hdc = 0;
    384     HFONT oldFont = 0;
    385     HRESULT shapeResult = E_PENDING;
    386     int glyphCount = 0;
    387     do {
    388         shapeResult = ScriptShape(hdc, fontData->scriptCache(), str, len, glyphs.size(), &item.a,
    389                                   glyphs.data(), clusters.data(), visualAttributes.data(), &glyphCount);
    390         if (shapeResult == E_PENDING) {
    391             // The script cache isn't primed with enough info yet.  We need to select our HFONT into
    392             // a DC and pass the DC in to ScriptShape.
    393             ASSERT(!hdc);
    394             hdc = GetDC(0);
    395             HFONT hfont = fontData->platformData().hfont();
    396             oldFont = (HFONT)SelectObject(hdc, hfont);
    397         } else if (shapeResult == E_OUTOFMEMORY) {
    398             // Need to resize our buffers.
    399             glyphs.resize(glyphs.size() * 2);
    400             visualAttributes.resize(glyphs.size());
    401         }
    402     } while (shapeResult == E_PENDING || shapeResult == E_OUTOFMEMORY);
    403 
    404     if (hdc) {
    405         SelectObject(hdc, oldFont);
    406         ReleaseDC(0, hdc);
    407     }
    408 
    409     if (FAILED(shapeResult))
    410         return false;
    411 
    412     glyphs.shrink(glyphCount);
    413     visualAttributes.shrink(glyphCount);
    414 
    415     return true;
    416 }
    417 
    418 }
    419