Logo Search packages:      
Sourcecode: pango-graphite version File versions  Download package

pangographite.cpp

/* Pango
 * pangographite.cpp: SILGraphite support for pango module
 *
 * Copyright (C) 2004-2006 SIL International
 * Author: Daniel Glassey <wdg@debian.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; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "graphitecache.h"
#include "pangographite.h"
#include <math.h>

typedef std::pair<gr::GlyphIterator, gr::GlyphIterator> GlyphRange;
typedef std::pair<gr::GlyphSetIterator, gr::GlyphSetIterator> GrGlyphSet;

void graphite_PangoLogAttrs(const char      *text,
        int             length,
        PangoFcFont *xftfont,
        PangoLogAttr    *attrs,
        int             attrs_len,
        const char *language,
        int rtl)
{
    PangoTextSrc *pTextSrc = new PangoTextSrc(text, length, rtl, language);
    PangoGrFont *pgrFont = new PangoGrFont(xftfont);
    PangoLogAttr *cachedattr = graphite_GetLogAttr(pTextSrc, pgrFont);
    if (cachedattr)
    {
        std::copy(cachedattr, cachedattr + attrs_len, attrs);
        delete pTextSrc;
        delete pgrFont;
        return;
    }

    RangeSegment *grseg = NULL;
        grseg = graphite_GetSegment(pTextSrc, pgrFont);
    if (!grseg)
    {
        try
        {
            pgrFont->lockFace();
            grseg = new RangeSegment(pgrFont, pTextSrc, NULL, 0, length);
            pgrFont->unlockFace();
            graphite_CacheSegment(pTextSrc, pgrFont, grseg);
        }

        catch (...)
        {
            g_warning("Exception in gr::RangeSegment for PangoLogAttrs");
        }
    }
    // if there was an exception return now
    if (!grseg)
    {
        delete pTextSrc;
        delete pgrFont;
        return;
    }

    GlyphRange gRange = grseg->glyphs();
    gr::GlyphIterator gi = gRange.first;
    gr::GlyphIterator gp = gRange.second;
    size_t lastGlyph = 0;
    unsigned int nextChar = 0;
    
/* notes on linebreaking and breakweights.
    effective bw = max(prev_glyph.bw > 0 ? prev_glyph.bw : 0, next_glyph.bw < 0 ? -next_glyph.bw : 0);
*/
    for (int i=0; i < attrs_len; i++)
    {
        attrs[i].is_cursor_position = FALSE;
        attrs[i].is_white = FALSE;
        attrs[i].is_line_break = FALSE;
        attrs[i].is_char_break = FALSE;
        attrs[i].backspace_deletes_character = TRUE;
    }
    while (gi != gRange.second)
    {
        gr::GlyphInfo info = (*gi);
        int prevBW;
        int nextBW;
        int breakWeight;
        
        if (gp == gRange.second)
            prevBW = grseg->startBreakWeight();
        else
            prevBW = (*gp).breakweight();
        
        nextBW = info.breakweight();
        
        breakWeight = std::max(prevBW > 0 ? prevBW : 0, nextBW < 0 ? -nextBW : 0);
        
        if (info.lastChar() >= nextChar)
        {
            size_t glyphIndex = info.logicalIndex();
            if (glyphIndex > lastGlyph)
            {
                if (info.insertBefore())
                {
                    int attr_index = g_utf8_pointer_to_offset(text, text + nextChar);
                    attrs[attr_index].is_cursor_position = TRUE;
                    // only set breakweights if we have a new
                    // cluster otherwise you get a break
                    // mid cluster
                    if (breakWeight > 0 && breakWeight < 30)
                        attrs[attr_index].is_line_break = TRUE;
                    if (breakWeight > 0 && breakWeight < 50)
                        attrs[attr_index].is_char_break = TRUE;
                    if (info.isSpace())
                        attrs[attr_index].is_white = TRUE;
                }
                lastGlyph = glyphIndex;
            }
        }
    
        // Loop over all remaining characters associated with this glyph
        // Need to catch the situation where this glyph has jumped several characters from
        // the previous one. e.g.
        // a b c d
        // g1[a] g2[c] g3[b] g4[d]
        // If you are processing g[c], then you need to advance nextChar to d and
        // lastGlyph to g3. Next loop, the if statements will fail preventing
        // a break in the middle of reordering, which causes lots of problems.
        // The algorithm must cope with 1 char to many glyphs, many char to 1 glyph
        // and many char to many glyphs.
        Assert(info.lastChar() < static_cast<unsigned int>(length));
        while (info.lastChar() >= nextChar)
        {
            GrGlyphSet charGlyphs = grseg->charToGlyphs(nextChar);
            nextChar = g_utf8_next_char(text + nextChar) - text;
            gr::GlyphSetIterator gj = charGlyphs.first;
            while (gj != charGlyphs.second)
            {
                lastGlyph = std::max(lastGlyph, (*gj).logicalIndex());
                ++gj;
            }
        }
        
        gp = gi;
        ++gi;
    }
    graphite_CacheLogAttr(pTextSrc, pgrFont, attrs_len, attrs);
}


void graphite_PangoGlyphString(const char *text, 
        int length, 
        PangoFcFont *xftfont, 
        PangoGlyphString *glyphs,
        const char *language,
        int rtl)
{
    PangoTextSrc *pTextSrc = new PangoTextSrc(text, length, rtl, language);
    PangoGrFont *pgrFont = new PangoGrFont(xftfont);
    
    PangoGlyphString *cachedglyphs = graphite_GetGlyphString(pTextSrc, pgrFont);
    if (cachedglyphs)
    {
        pango_glyph_string_set_size(glyphs, cachedglyphs->num_glyphs);
        std::copy(cachedglyphs->glyphs, cachedglyphs->glyphs + cachedglyphs->space, glyphs->glyphs);
        std::copy(cachedglyphs->log_clusters, 
            cachedglyphs->log_clusters + cachedglyphs->space, glyphs->log_clusters);
        delete pTextSrc;
        delete pgrFont;
        return;
    }
    int n_glyphs = 0, n_chars = 0;

    RangeSegment *grseg;
    GlyphRange grIterators;
    try // Graphite does throw exceptions, so catch them
    {
        grseg = graphite_GetSegment(pTextSrc, pgrFont);
        if (!grseg)
        {
            pgrFont->lockFace();
            grseg = new RangeSegment(pgrFont, pTextSrc, NULL, 0, length);
            pgrFont->unlockFace();
            graphite_CacheSegment(pTextSrc, pgrFont, grseg);
        }
        grIterators = grseg->glyphs();
        n_chars = length;
        n_glyphs = grIterators.second - grIterators.first;
    }
    catch (...)
    {
        // does occur with control characters
        g_warning("Exception in gr::RangeSegment");
    }
    if (n_glyphs == 0)
    {
        PangoRectangle logical_rect;

        // Returning no glyphs causes pango to bail out, so
        // return a single null glyph instead
        pango_glyph_string_set_size(glyphs, 1);
//      glyphs->glyphs[0].glyph = 0; // null glyph
        glyphs->glyphs[0].glyph = PANGO_GET_UNKNOWN_GLYPH(g_utf8_get_char(text));
        glyphs->glyphs[0].geometry.y_offset = 0;
        glyphs->glyphs[0].geometry.x_offset = 0;
        glyphs->log_clusters[0] = 0;
        PangoFont *font = PANGO_FONT(xftfont);
        pango_font_get_glyph_extents(font, glyphs->glyphs[0].glyph, NULL, &logical_rect);
        glyphs->glyphs[0].geometry.width = logical_rect.width;
                //g_message("no glyphs so returning null glyph");
        delete pTextSrc;
        delete pgrFont;
        return;
    }

    gr::utf16 * prgchGlyphs;
    float * prgxd;
    float * prgyd;
    float * prgadv;
    prgchGlyphs = new gr::utf16[n_glyphs];
    prgxd = new float[n_glyphs+1];
    prgyd = new float[n_glyphs];
    prgadv = new float[n_glyphs];
    int k = 0;
    gr::GlyphIterator grIter = grIterators.first;
    float maxWidth = 0.0;
    while (grIter != grIterators.second)
    {
        prgchGlyphs[k] = (*grIter).glyphID();
        prgyd[k] = (*grIter).yOffset();
        prgadv[k] = (*grIter).advanceWidth();
        prgxd[k] = (*grIter).origin();
        maxWidth = std::max(maxWidth, prgxd[k] + (*grIter).advanceWidth());
        ++k;
        ++grIter;
    }
    Assert(k==n_glyphs);

    int * sorted;
    sorted = new int[n_glyphs];
    
    int * prg1stCharInClust;
    unsigned int first_char_in_cluster = 0, first_glyph_in_cluster = 0;
    
// What I really want here is
// for each glyph, 
//  byte index of the starting character for the cluster. 
//  The indices are relative to the start of the text corresponding to the PangoGlyphString. 

    // for each glyph the first char that is in that cluster
    prg1stCharInClust = new int[n_glyphs];

    // iterate glyph wise
    GlyphRange gRange = grseg->glyphs();
    gr::GlyphIterator gi = gRange.first;
    size_t lastGlyph = 0;
    int nextChar = 0;
    // current glyph number
    int currGlyph = 0;

    // loop over the glyphs determining which characters are linked to them
    while (gi != gRange.second)
    {
        Assert(currGlyph < n_glyphs);
        gr::GlyphInfo info = (*gi);
        // the last character associated with this glyph is after
        // our current cluster buffer position
        if ((signed)info.lastChar() >= nextChar)
        {
            size_t glyphIndex = info.logicalIndex();
            if (glyphIndex > lastGlyph)
            {
                // this glyph is after the previous one left->right
                // if insertion is allowed before it then we are in a
                // new cluster
//              if (info.insertBefore() && (!info.isAttached() || info.logicalIndex() == (*(info.attachedClusterBase())).logicalIndex())) 
                if (info.insertBefore())
                {
                    first_char_in_cluster = info.firstChar();
                    first_glyph_in_cluster = currGlyph;
                }
                lastGlyph = glyphIndex;
            }
            // loop over chacters associated with this glyph and characters
            // between nextChar and the last character associated with this glyph
            // giving them the current cluster id.  This allows for character /glyph
            // order reversal.
            // For each character we do a reverse glyph id look up
            // and store the glyph id with the highest logical index in lastGlyph
            while ((signed)info.lastChar() >= nextChar)
            {
                GrGlyphSet charGlyphs = grseg->charToGlyphs(nextChar);
                nextChar = g_utf8_next_char(text + nextChar) - text;
                gr::GlyphSetIterator gj = charGlyphs.first;
                while (gj != charGlyphs.second)
                {
                    lastGlyph = max(lastGlyph, (*gj).logicalIndex());
                    ++gj;
                }
            }
        }

        // it is possible for the lastChar to be after nextChar and
        // firstChar to be before the first_char_in_cluster in rare
        // circumstances e.g. Myanmar word for cemetery
        if (info.firstChar() < first_char_in_cluster)
        {
            first_char_in_cluster = info.firstChar();
            size_t prevGlyph = currGlyph - 1;
            while (prevGlyph >= first_glyph_in_cluster)
            {
                prg1stCharInClust[prevGlyph] = first_char_in_cluster;
                prevGlyph--;
            }
        }
        prg1stCharInClust[currGlyph] = first_char_in_cluster;
        ++gi;
        ++currGlyph;
    }

   // sorted contains glyph indexes such that the if prg1stCharInClust[x] is
    // the first then sorted[x] is the left most glyph. Likewise if prg1stCharInClust[y]
    // is the last, then sorted[y] has a rhs furthest to the right. This makes
    // the width of the cluster the rhs of the last glyph - origin of the first
    // glyph in the cluster.
    nextChar = -1;      // first char in current cluster
    currGlyph = -1;         // index of current glyph of interest
    for (gi = gRange.first; gi != gRange.second; gi += 1)
    {
        currGlyph++;        // advance to 0 and beyond
        Assert(currGlyph < n_glyphs);
        if (prg1stCharInClust[currGlyph] != nextChar)   // new cluster
        {
            first_glyph_in_cluster = currGlyph;
            nextChar = prg1stCharInClust[currGlyph];
            if (rtl)
            {
                if (currGlyph > 0)
                    std::memmove(sorted + 1, sorted, sizeof(int) * currGlyph);
                sorted[0] = currGlyph;
            }
            else
                sorted[currGlyph] = currGlyph;
        }
        else
        {
            int i;
            if (rtl)
            {
                for (i = 0; i < currGlyph; i++)
                {
                    if (prgxd[sorted[i]] <= prgxd[currGlyph])
                        break;
                }
            }
            else
            {
                for (i = currGlyph - 1; i >= 0; i--)
                {
                    if (prgxd[sorted[i]] <= prgxd[currGlyph])
                        break;
                }
                i++;
            }
            if (i < currGlyph)
            {
                std::memmove(sorted + i + 1, sorted + i, sizeof(int) * (currGlyph - i));
            }
            sorted[i] = currGlyph;
        }
    }

    pango_glyph_string_set_size(glyphs, n_glyphs);

    int i;
    int next_shift = 0;

    for (i = 0; i < n_glyphs; i++)
    {
        currGlyph = sorted[i];
        glyphs->glyphs[i].geometry.y_offset = static_cast<int>(-prgyd[currGlyph]*PANGO_SCALE);
        glyphs->log_clusters[i] = prg1stCharInClust[currGlyph];
        glyphs->glyphs[i].geometry.x_offset = next_shift;
        if (i == n_glyphs - 1)
            glyphs->glyphs[i].geometry.width = static_cast<int>((maxWidth - prgxd[currGlyph]) * PANGO_SCALE);
        else
            glyphs->glyphs[i].geometry.width = static_cast<int>((prgxd[sorted[i+1]] - prgxd[currGlyph]) * PANGO_SCALE);
        glyphs->glyphs[i].geometry.width += next_shift;
        next_shift = 0;
        if (glyphs->glyphs[i].geometry.width < 0)
        {
            next_shift = glyphs->glyphs[i].geometry.width;
            glyphs->glyphs[i].geometry.width = 0;
        }
        if (prgchGlyphs[currGlyph])
            glyphs->glyphs[i].glyph = prgchGlyphs[currGlyph];
        else
        {
            PangoRectangle logical_rect;

            glyphs->glyphs[i].glyph = PANGO_GET_UNKNOWN_GLYPH(g_utf8_get_char(text + prg1stCharInClust[currGlyph]));
            PangoFont *font = PANGO_FONT(xftfont);
            pango_font_get_glyph_extents(font, glyphs->glyphs[i].glyph, NULL, &logical_rect);
            glyphs->glyphs[i].geometry.width = logical_rect.width;
        }
    }

    glyphs->num_glyphs = n_glyphs;

    delete[] sorted;
    delete[] prgchGlyphs;
    delete[] prgxd;
    delete[] prgyd;
    delete[] prgadv;
    delete[] prg1stCharInClust;
    graphite_CacheGlyphString(pTextSrc, pgrFont, glyphs);
}


Generated by  Doxygen 1.6.0   Back to index