2009-08-17 14:12:14 +00:00
|
|
|
/*************************************************************************
|
|
|
|
*
|
|
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
|
|
*
|
2010-02-12 15:01:35 +01:00
|
|
|
* Copyright 2000, 2010 Oracle and/or its affiliates.
|
2009-08-17 14:12:14 +00:00
|
|
|
*
|
|
|
|
* OpenOffice.org - a multi-platform office productivity suite
|
|
|
|
*
|
|
|
|
* This file is part of OpenOffice.org.
|
|
|
|
*
|
|
|
|
* OpenOffice.org is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Lesser General Public License version 3
|
|
|
|
* only, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* OpenOffice.org 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 Lesser General Public License version 3 for more details
|
|
|
|
* (a copy is included in the LICENSE file that accompanied this code).
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
|
|
* version 3 along with OpenOffice.org. If not, see
|
|
|
|
* <http://www.openoffice.org/license.html>
|
|
|
|
* for a copy of the LGPLv3 License.
|
|
|
|
*
|
|
|
|
************************************************************************/
|
|
|
|
|
|
|
|
// Description: An implementation of the SalLayout interface that uses the
|
|
|
|
// Graphite engine.
|
|
|
|
|
|
|
|
// MARKER(update_precomp.py): autogen include statement, do not remove
|
|
|
|
#include "precompiled_vcl.hxx"
|
|
|
|
|
|
|
|
// We need this to enable namespace support in libgrengine headers.
|
|
|
|
#define GR_NAMESPACE
|
|
|
|
|
|
|
|
// Enable lots of debug info
|
|
|
|
#ifdef DEBUG
|
|
|
|
//#define GRLAYOUT_DEBUG 1
|
|
|
|
//#undef NDEBUG
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Header files
|
|
|
|
//
|
|
|
|
// Standard Library
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cassert>
|
|
|
|
#include <functional>
|
|
|
|
#include <limits>
|
|
|
|
#include <numeric>
|
|
|
|
#include <deque>
|
|
|
|
|
|
|
|
// Platform
|
2009-09-10 22:38:33 +00:00
|
|
|
#ifdef WNT
|
2009-08-17 14:12:14 +00:00
|
|
|
#include <tools/svwin.h>
|
|
|
|
#include <svsys.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <vcl/salgdi.hxx>
|
|
|
|
|
|
|
|
#include <unicode/uchar.h>
|
|
|
|
#include <unicode/ubidi.h>
|
|
|
|
#include <unicode/uscript.h>
|
|
|
|
|
|
|
|
// Graphite Libraries (must be after vcl headers on windows)
|
2010-03-12 09:15:29 +00:00
|
|
|
#include <tools/preextstl.h>
|
2009-08-17 14:12:14 +00:00
|
|
|
#include <graphite/GrClient.h>
|
|
|
|
#include <graphite/Font.h>
|
|
|
|
#include <graphite/ITextSource.h>
|
|
|
|
#include <graphite/Segment.h>
|
|
|
|
#include <graphite/SegmentPainter.h>
|
2010-03-12 09:15:29 +00:00
|
|
|
#include <tools/postextstl.h>
|
2009-08-17 14:12:14 +00:00
|
|
|
|
|
|
|
#include <vcl/graphite_layout.hxx>
|
|
|
|
#include <vcl/graphite_features.hxx>
|
|
|
|
#include "graphite_textsrc.hxx"
|
|
|
|
|
|
|
|
|
|
|
|
// Module private type definitions and forward declarations.
|
|
|
|
//
|
|
|
|
// Module private names.
|
|
|
|
//
|
|
|
|
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
FILE * grLogFile = NULL;
|
|
|
|
FILE * grLog()
|
|
|
|
{
|
2009-09-10 22:38:33 +00:00
|
|
|
#ifdef WNT
|
2009-08-17 14:12:14 +00:00
|
|
|
std::string logFileName(getenv("TEMP"));
|
|
|
|
logFileName.append("\\graphitelayout.log");
|
|
|
|
if (grLogFile == NULL) grLogFile = fopen(logFileName.c_str(),"w");
|
|
|
|
else fflush(grLogFile);
|
|
|
|
return grLogFile;
|
|
|
|
#else
|
|
|
|
return stdout;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef GRCACHE
|
|
|
|
#include <vcl/graphite_cache.hxx>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2010-03-12 09:15:29 +00:00
|
|
|
typedef ext_std::pair<gr::GlyphIterator, gr::GlyphIterator> glyph_range_t;
|
|
|
|
typedef ext_std::pair<gr::GlyphSetIterator, gr::GlyphSetIterator> glyph_set_range_t;
|
2009-08-17 14:12:14 +00:00
|
|
|
|
|
|
|
inline long round(const float n) {
|
|
|
|
return long(n + (n < 0 ? -0.5 : 0.5));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
inline bool in_range(const T i, const T b, const T e) {
|
|
|
|
return !(b > i) && i < e;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
inline bool is_subrange(const T sb, const T se, const T b, const T e) {
|
|
|
|
return !(b > sb || se > e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
inline bool is_subrange(const std::pair<T, T> &s, const T b, const T e) {
|
|
|
|
return is_subrange(s.first, s.second, b, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
int findSameDirLimit(const xub_Unicode* buffer, int charCount, bool rtl)
|
|
|
|
{
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
UBiDi *ubidi = ubidi_openSized(charCount, 0, &status);
|
|
|
|
int limit = 0;
|
2009-09-10 22:38:33 +00:00
|
|
|
ubidi_setPara(ubidi, reinterpret_cast<const UChar *>(buffer), charCount,
|
2009-08-17 14:12:14 +00:00
|
|
|
(rtl)?UBIDI_DEFAULT_RTL:UBIDI_DEFAULT_LTR, NULL, &status);
|
|
|
|
UBiDiLevel level = 0;
|
|
|
|
ubidi_getLogicalRun(ubidi, 0, &limit, &level);
|
|
|
|
ubidi_close(ubidi);
|
|
|
|
if ((rtl && !(level & 1)) || (!rtl && (level & 1)))
|
|
|
|
{
|
|
|
|
limit = 0;
|
|
|
|
}
|
|
|
|
return limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Impementation of the GraphiteLayout::Glyphs container class.
|
|
|
|
// This is an extended vector class with methods added to enable
|
|
|
|
// o Correctly filling with glyphs.
|
|
|
|
// o Querying clustering relationships.
|
|
|
|
// o manipulations that affect neighouring glyphs.
|
|
|
|
|
|
|
|
const int GraphiteLayout::EXTRA_CONTEXT_LENGTH = 10;
|
|
|
|
#ifdef GRCACHE
|
|
|
|
GraphiteCacheHandler GraphiteCacheHandler::instance;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// The Graphite glyph stream is really a sequence of glyph attachment trees
|
|
|
|
// each rooted at a non-attached base glyph. fill_from walks the glyph stream
|
|
|
|
// find each non-attached base glyph and calls append to record them as a
|
|
|
|
// sequence of clusters.
|
|
|
|
void
|
|
|
|
GraphiteLayout::Glyphs::fill_from(gr::Segment & rSegment, ImplLayoutArgs &rArgs,
|
|
|
|
bool bRtl, long &rWidth, float fScaling, std::vector<int> & rChar2Base, std::vector<int> & rGlyph2Char, std::vector<int> & rCharDxs)
|
|
|
|
{
|
|
|
|
// Create a glyph item for each of the glyph and append it to the base class glyph list.
|
2010-03-12 09:15:29 +00:00
|
|
|
typedef ext_std::pair< gr::GlyphSetIterator, gr::GlyphSetIterator > GrGlyphSet;
|
2009-08-17 14:12:14 +00:00
|
|
|
int nChar = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
|
|
|
|
glyph_range_t iGlyphs = rSegment.glyphs();
|
|
|
|
int nGlyphs = iGlyphs.second - iGlyphs.first;
|
|
|
|
gr::GlyphIterator prevBase = iGlyphs.second;
|
|
|
|
float fMinX = rSegment.advanceWidth();
|
|
|
|
float fMaxX = 0.0f;
|
|
|
|
rGlyph2Char.assign(nGlyphs, -1);
|
|
|
|
long nDxOffset = 0;
|
|
|
|
int nGlyphIndex = (bRtl)? (nGlyphs - 1) : 0;
|
|
|
|
// OOo always expects the glyphs in ltr order
|
|
|
|
int nDelta = (bRtl)? -1 : 1;
|
|
|
|
|
|
|
|
int nLastGlyph = (bRtl)? nGlyphs - 1: 0;
|
|
|
|
int nNextChar = (bRtl)? (rSegment.stopCharacter() - 1) : rSegment.startCharacter();//rArgs.mnMinCharPos;
|
|
|
|
// current glyph number (Graphite glyphs)
|
|
|
|
//int currGlyph = 0;
|
|
|
|
int nFirstCharInCluster = nNextChar;
|
|
|
|
int nFirstGlyphInCluster = nLastGlyph;
|
|
|
|
|
|
|
|
// ltr first char in cluster is lowest, same is true for rtl
|
|
|
|
// ltr first glyph in cluster is lowest, rtl first glyph is highest
|
|
|
|
|
|
|
|
// loop over the glyphs determining which characters are linked to them
|
|
|
|
gr::GlyphIterator gi;
|
|
|
|
for (gi = iGlyphs.first + nGlyphIndex;
|
|
|
|
nGlyphIndex >= 0 && nGlyphIndex < nGlyphs;
|
|
|
|
nGlyphIndex+= nDelta, gi = iGlyphs.first + nGlyphIndex)
|
|
|
|
{
|
|
|
|
gr::GlyphInfo info = (*gi);
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Glyph %d %f,%f\n", (int)info.logicalIndex(), info.origin(), info.yOffset());
|
|
|
|
#endif
|
|
|
|
// the last character associated with this glyph is after
|
|
|
|
// our current cluster buffer position
|
|
|
|
if ((bRtl && ((signed)info.firstChar() <= nNextChar)) ||
|
|
|
|
(!bRtl && ((signed)info.lastChar() >= nNextChar)))
|
|
|
|
{
|
|
|
|
if ((bRtl && nGlyphIndex < nLastGlyph) ||
|
|
|
|
(!bRtl && nGlyphIndex > nLastGlyph))
|
|
|
|
{
|
|
|
|
// this glyph is after the previous one left->right
|
|
|
|
// if insertion is allowed before it then we are in a
|
|
|
|
// new cluster
|
|
|
|
int nAttachedBase = (*(info.attachedClusterBase())).logicalIndex();
|
|
|
|
if (!info.isAttached() ||
|
|
|
|
!in_range(nAttachedBase, nFirstGlyphInCluster, nGlyphIndex))
|
|
|
|
{
|
|
|
|
if (in_range(nFirstCharInCluster, rArgs.mnMinCharPos, rArgs.mnEndCharPos) &&
|
|
|
|
nFirstGlyphInCluster != nGlyphIndex)
|
|
|
|
{
|
|
|
|
std::pair <float,float> aBounds =
|
|
|
|
appendCluster(rSegment, rArgs, bRtl, nFirstCharInCluster,
|
|
|
|
nNextChar, nFirstGlyphInCluster, nGlyphIndex, fScaling,
|
|
|
|
rChar2Base, rGlyph2Char, rCharDxs, nDxOffset);
|
|
|
|
fMinX = std::min(aBounds.first, fMinX);
|
|
|
|
fMaxX = std::max(aBounds.second, fMaxX);
|
|
|
|
}
|
|
|
|
nFirstCharInCluster = (bRtl)? info.lastChar() : info.firstChar();
|
|
|
|
nFirstGlyphInCluster = nGlyphIndex;
|
|
|
|
}
|
|
|
|
nLastGlyph = (bRtl)? std::min(nGlyphIndex, nAttachedBase) :
|
|
|
|
std::max(nGlyphIndex, nAttachedBase);
|
|
|
|
}
|
|
|
|
// 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 nLastGlyph
|
|
|
|
while ((bRtl && ((signed)info.firstChar() <= nNextChar)) ||
|
|
|
|
(!bRtl && (signed)info.lastChar() >= nNextChar))
|
|
|
|
{
|
|
|
|
GrGlyphSet charGlyphs = rSegment.charToGlyphs(nNextChar);
|
|
|
|
nNextChar += nDelta;
|
|
|
|
gr::GlyphSetIterator gj = charGlyphs.first;
|
|
|
|
while (gj != charGlyphs.second)
|
|
|
|
{
|
|
|
|
nLastGlyph = (bRtl)? min(nLastGlyph, (signed)(*gj).logicalIndex()) : max(nLastGlyph, (signed)(*gj).logicalIndex());
|
|
|
|
++gj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Loop over attached glyphs and make sure they are all in the cluster since you
|
|
|
|
// can have glyphs attached with another base glyph in between
|
|
|
|
glyph_set_range_t iAttached = info.attachedClusterGlyphs();
|
|
|
|
for (gr::GlyphSetIterator agi = iAttached.first; agi != iAttached.second; ++agi)
|
|
|
|
{
|
|
|
|
nLastGlyph = (bRtl)? min(nLastGlyph, (signed)(*agi).logicalIndex()) : max(nLastGlyph, (signed)(*agi).logicalIndex());
|
|
|
|
}
|
|
|
|
|
|
|
|
// if this is a rtl attached glyph, then we need to include its
|
|
|
|
// base in the cluster, which will have a lower graphite index
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
if ((signed)info.attachedClusterBase()->logicalIndex() < nLastGlyph)
|
|
|
|
{
|
|
|
|
nLastGlyph = info.attachedClusterBase()->logicalIndex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// it is possible for the lastChar to be after nextChar and
|
|
|
|
// firstChar to be before the nFirstCharInCluster in rare
|
|
|
|
// circumstances e.g. Myanmar word for cemetery
|
|
|
|
if ((bRtl && ((signed)info.lastChar() > nFirstCharInCluster)) ||
|
|
|
|
(!bRtl && ((signed)info.firstChar() < nFirstCharInCluster)))
|
|
|
|
{
|
|
|
|
nFirstCharInCluster = info.firstChar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// process last cluster
|
|
|
|
if (in_range(nFirstCharInCluster, rArgs.mnMinCharPos, rArgs.mnEndCharPos) &&
|
|
|
|
nFirstGlyphInCluster != nGlyphIndex)
|
|
|
|
{
|
|
|
|
std::pair <float,float> aBounds =
|
|
|
|
appendCluster(rSegment, rArgs, bRtl, nFirstCharInCluster, nNextChar,
|
|
|
|
nFirstGlyphInCluster, nGlyphIndex, fScaling,
|
|
|
|
rChar2Base, rGlyph2Char, rCharDxs, nDxOffset);
|
|
|
|
fMinX = std::min(aBounds.first, fMinX);
|
|
|
|
fMaxX = std::max(aBounds.second, fMaxX);
|
|
|
|
}
|
|
|
|
long nXOffset = round(fMinX * fScaling);
|
|
|
|
rWidth = round(fMaxX * fScaling) - nXOffset + nDxOffset;
|
|
|
|
if (rWidth < 0)
|
|
|
|
{
|
|
|
|
// This can happen when there was no base inside the range
|
|
|
|
rWidth = 0;
|
|
|
|
}
|
|
|
|
// fill up non-base char dx with cluster widths from previous base glyph
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
if (rCharDxs[nChar-1] == -1)
|
|
|
|
rCharDxs[nChar-1] = 0;
|
|
|
|
else
|
|
|
|
rCharDxs[nChar-1] -= nXOffset;
|
|
|
|
for (int i = nChar - 2; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if (rCharDxs[i] == -1) rCharDxs[i] = rCharDxs[i+1];
|
|
|
|
else rCharDxs[i] -= nXOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (rCharDxs[0] == -1)
|
|
|
|
rCharDxs[0] = 0;
|
|
|
|
else
|
|
|
|
rCharDxs[0] -= nXOffset;
|
|
|
|
for (int i = 1; i < nChar; i++)
|
|
|
|
{
|
|
|
|
if (rCharDxs[i] == -1) rCharDxs[i] = rCharDxs[i-1];
|
|
|
|
else rCharDxs[i] -= nXOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Glyphs xOff%ld dropDx%ld w%ld\n", nXOffset, nDxOffset, rWidth);
|
|
|
|
#endif
|
|
|
|
// remove offset due to context if there is one
|
|
|
|
if (nXOffset != 0)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < size(); i++)
|
|
|
|
(*this)[i].maLinearPos.X() -= nXOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<float,float> GraphiteLayout::Glyphs::appendCluster(gr::Segment & rSeg,
|
|
|
|
ImplLayoutArgs & rArgs, bool bRtl, int nFirstCharInCluster, int nNextChar,
|
|
|
|
int nFirstGlyphInCluster, int nNextGlyph, float fScaling,
|
|
|
|
std::vector<int> & rChar2Base, std::vector<int> & rGlyph2Char,
|
|
|
|
std::vector<int> & rCharDxs, long & rDXOffset)
|
|
|
|
{
|
|
|
|
glyph_range_t iGlyphs = rSeg.glyphs();
|
|
|
|
int nGlyphs = iGlyphs.second - iGlyphs.first;
|
|
|
|
int nDelta = (bRtl)? -1 : 1;
|
|
|
|
gr::GlyphInfo aFirstGlyph = *(iGlyphs.first + nFirstGlyphInCluster);
|
|
|
|
std::pair <float, float> aBounds;
|
|
|
|
aBounds.first = aFirstGlyph.origin();
|
|
|
|
aBounds.second = aFirstGlyph.origin();
|
|
|
|
// before we add the glyphs to this vector, we record the
|
|
|
|
// glyph's index in the vector (which is not the same as
|
|
|
|
// the Segment's glyph index!)
|
|
|
|
assert(size() < rGlyph2Char.size());
|
|
|
|
rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] = size();
|
|
|
|
rGlyph2Char[size()] = nFirstCharInCluster;
|
|
|
|
bool bBaseGlyph = true;
|
|
|
|
for (int j = nFirstGlyphInCluster;
|
|
|
|
j != nNextGlyph; j += nDelta)
|
|
|
|
{
|
|
|
|
long nNextOrigin;
|
|
|
|
float fNextOrigin;
|
|
|
|
gr::GlyphInfo aGlyph = *(iGlyphs.first + j);
|
|
|
|
if (j + nDelta >= nGlyphs || j + nDelta < 0) // at rhs ltr,rtl
|
|
|
|
{
|
|
|
|
fNextOrigin = rSeg.advanceWidth();
|
|
|
|
nNextOrigin = round(rSeg.advanceWidth() * fScaling + rDXOffset);
|
|
|
|
aBounds.second = std::max(rSeg.advanceWidth(), aBounds.second);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gr::GlyphInfo aNextGlyph = *(iGlyphs.first + j + nDelta);
|
|
|
|
fNextOrigin = std::max(aNextGlyph.attachedClusterBase()->origin(), aNextGlyph.origin());
|
|
|
|
aBounds.second = std::max(fNextOrigin, aBounds.second);
|
|
|
|
nNextOrigin = round(fNextOrigin * fScaling + rDXOffset);
|
|
|
|
}
|
|
|
|
aBounds.first = std::min(aGlyph.origin(), aBounds.first);
|
|
|
|
if ((signed)aGlyph.firstChar() < rArgs.mnEndCharPos &&
|
|
|
|
(signed)aGlyph.firstChar() >= rArgs.mnMinCharPos)
|
|
|
|
{
|
|
|
|
rCharDxs[aGlyph.firstChar()-rArgs.mnMinCharPos] = nNextOrigin;
|
|
|
|
}
|
|
|
|
if ((signed)aGlyph.attachedClusterBase()->logicalIndex() == j)
|
|
|
|
{
|
|
|
|
append(rSeg, rArgs, aGlyph, fNextOrigin, fScaling, rChar2Base, rGlyph2Char, rCharDxs, rDXOffset, bBaseGlyph);
|
|
|
|
bBaseGlyph = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// from the point of view of the dx array, the xpos is
|
|
|
|
// the origin of the first glyph of the next cluster ltr
|
|
|
|
// rtl it is the origin of the 1st glyph of the cluster
|
|
|
|
long nXPos = (bRtl)?
|
|
|
|
round(aFirstGlyph.attachedClusterBase()->origin() * fScaling) + rDXOffset :
|
|
|
|
round(aBounds.second * fScaling) + rDXOffset;
|
|
|
|
// force the last char in range to have the width of the cluster
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
for (int n = nNextChar + 1; n <= nFirstCharInCluster; n++)
|
|
|
|
{
|
|
|
|
if ((n < rArgs.mnEndCharPos) && (n >= rArgs.mnMinCharPos))
|
|
|
|
rCharDxs[n-rArgs.mnMinCharPos] = nXPos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int n = nNextChar - 1; n >= nFirstCharInCluster; n--)
|
|
|
|
{
|
|
|
|
if (n < rArgs.mnEndCharPos && n >= rArgs.mnMinCharPos)
|
|
|
|
rCharDxs[n-rArgs.mnMinCharPos] = nXPos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Cluster g[%d-%d) c[%d-%d)%x x%ld y%f\n", nFirstGlyphInCluster, nNextGlyph, nFirstCharInCluster, nNextChar, rArgs.mpStr[nFirstCharInCluster], nXPos, aFirstGlyph.yOffset());
|
|
|
|
#endif
|
|
|
|
return aBounds;
|
|
|
|
}
|
|
|
|
|
|
|
|
// append walks an attachment tree, flattening it, and converting it into a
|
|
|
|
// sequence of GlyphItem objects which we can later manipulate.
|
|
|
|
void
|
|
|
|
GraphiteLayout::Glyphs::append(gr::Segment &segment, ImplLayoutArgs &args, gr::GlyphInfo & gi, float nextGlyphOrigin, float scaling, std::vector<int> & rChar2Base, std::vector<int> & rGlyph2Char, std::vector<int> & rCharDxs, long & rDXOffset, bool bIsBase)
|
|
|
|
{
|
|
|
|
float nextOrigin = nextGlyphOrigin;
|
|
|
|
int firstChar = std::min(gi.firstChar(), gi.lastChar());
|
|
|
|
assert(size() < rGlyph2Char.size());
|
|
|
|
if (!bIsBase) rGlyph2Char[size()] = firstChar;
|
|
|
|
// is the next glyph attached or in the next cluster?
|
|
|
|
glyph_set_range_t iAttached = gi.attachedClusterGlyphs();
|
|
|
|
if (iAttached.first != iAttached.second)
|
|
|
|
{
|
|
|
|
nextOrigin = iAttached.first->origin();
|
|
|
|
}
|
|
|
|
long glyphId = gi.glyphID();
|
|
|
|
long deltaOffset = 0;
|
|
|
|
int glyphWidth = round(nextOrigin * scaling) - round(gi.origin() * scaling);
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"c%d g%d gWidth%d x%f ", firstChar, (int)gi.logicalIndex(), glyphWidth, nextOrigin);
|
|
|
|
#endif
|
|
|
|
if (glyphId == 0)
|
|
|
|
{
|
|
|
|
args.NeedFallback(
|
|
|
|
firstChar,
|
|
|
|
gr::RightToLeftDir(gr::DirCode(gi.directionality())));
|
|
|
|
if( (SAL_LAYOUT_FOR_FALLBACK & args.mnFlags ))
|
|
|
|
{
|
|
|
|
glyphId = GF_DROPPED;
|
|
|
|
deltaOffset -= glyphWidth;
|
|
|
|
glyphWidth = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(args.mnFlags & SAL_LAYOUT_FOR_FALLBACK)
|
|
|
|
{
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"fallback c%d %x in run %d\n", firstChar, args.mpStr[firstChar],
|
|
|
|
args.maRuns.PosIsInAnyRun(firstChar));
|
|
|
|
#endif
|
|
|
|
// glyphs that aren't requested for fallback will be taken from base
|
|
|
|
// layout, so mark them as dropped (should this wait until Simplify(false) is called?)
|
|
|
|
if (!args.maRuns.PosIsInAnyRun(firstChar) &&
|
|
|
|
in_range(firstChar, args.mnMinCharPos, args.mnEndCharPos))
|
|
|
|
{
|
|
|
|
glyphId = GF_DROPPED;
|
|
|
|
deltaOffset -= glyphWidth;
|
|
|
|
glyphWidth = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// append this glyph.
|
|
|
|
long nGlyphFlags = bIsBase ? 0 : GlyphItem::IS_IN_CLUSTER;
|
|
|
|
// directionality seems to be unreliable
|
|
|
|
//nGlyphFlags |= gr::RightToLeftDir(gr::DirCode(gi.attachedClusterBase()->directionality())) ? GlyphItem::IS_RTL_GLYPH : 0;
|
|
|
|
nGlyphFlags |= (gi.directionLevel() & 0x1)? GlyphItem::IS_RTL_GLYPH : 0;
|
|
|
|
GlyphItem aGlyphItem(size(),//gi.logicalIndex(),
|
|
|
|
glyphId,
|
|
|
|
Point(round(gi.origin() * scaling + rDXOffset),
|
|
|
|
round((-gi.yOffset() * scaling) - segment.AscentOffset()* scaling)),
|
|
|
|
nGlyphFlags,
|
|
|
|
glyphWidth);
|
|
|
|
aGlyphItem.mnOrigWidth = round(gi.advanceWidth() * scaling);
|
|
|
|
push_back(aGlyphItem);
|
|
|
|
|
|
|
|
// update the offset if this glyph was dropped
|
|
|
|
rDXOffset += deltaOffset;
|
|
|
|
|
|
|
|
// Recursively apply append all the attached glyphs.
|
|
|
|
for (gr::GlyphSetIterator agi = iAttached.first; agi != iAttached.second; ++agi)
|
|
|
|
{
|
|
|
|
if (agi + 1 == iAttached.second)
|
|
|
|
append(segment, args, *agi, nextGlyphOrigin, scaling, rChar2Base, rGlyph2Char,rCharDxs, rDXOffset, false);
|
|
|
|
else
|
|
|
|
append(segment, args, *agi, (agi + 1)->origin(), scaling, rChar2Base, rGlyph2Char, rCharDxs, rDXOffset, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// An implementation of the SalLayout interface to enable Graphite enabled fonts to be used.
|
|
|
|
//
|
|
|
|
GraphiteLayout::GraphiteLayout(const gr::Font & font, const grutils::GrFeatureParser * pFeatures) throw()
|
|
|
|
: mpTextSrc(0),
|
|
|
|
mrFont(font),
|
|
|
|
mnWidth(0),
|
|
|
|
mfScaling(1.0),
|
|
|
|
mpFeatures(pFeatures)
|
|
|
|
{
|
|
|
|
// Line settings can have subtle affects on space handling
|
|
|
|
// since we don't really know whether it is the end of a line or just a run
|
|
|
|
// in the middle, it is hard to know what to set them to.
|
|
|
|
// If true, it can cause end of line spaces to be hidden e.g. Doulos SIL
|
|
|
|
maLayout.setStartOfLine(false);
|
|
|
|
maLayout.setEndOfLine(false);
|
|
|
|
// maLayout.setDumbFallback(false);
|
|
|
|
// trailing ws doesn't seem to always take affect if end of line is true
|
|
|
|
maLayout.setTrailingWs(gr::ktwshAll);
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
gr::ScriptDirCode aDirCode = font.getSupportedScriptDirections();
|
|
|
|
fprintf(grLog(),"GraphiteLayout scripts %x %lx\n", aDirCode, long(this));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GraphiteLayout::~GraphiteLayout() throw()
|
|
|
|
{
|
|
|
|
clear();
|
|
|
|
// the features are owned by the platform layers
|
|
|
|
mpFeatures = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphiteLayout::clear()
|
|
|
|
{
|
|
|
|
// Destroy the segment and text source from any previous invocation of
|
|
|
|
// LayoutText
|
|
|
|
mvGlyphs.clear();
|
|
|
|
mvCharDxs.clear();
|
|
|
|
mvChar2BaseGlyph.clear();
|
|
|
|
mvGlyph2Char.clear();
|
|
|
|
|
|
|
|
#ifndef GRCACHE
|
|
|
|
delete mpTextSrc;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Reset the state to the empty state.
|
|
|
|
mpTextSrc=0;
|
|
|
|
mnWidth = 0;
|
|
|
|
// Don't reset the scaling, because it is set before LayoutText
|
|
|
|
}
|
|
|
|
|
|
|
|
// This method shouldn't be called on windows, since it needs the dc reset
|
|
|
|
bool GraphiteLayout::LayoutText(ImplLayoutArgs & rArgs)
|
|
|
|
{
|
|
|
|
#ifdef GRCACHE
|
|
|
|
GrSegRecord * pSegRecord = NULL;
|
|
|
|
gr::Segment * pSegment = CreateSegment(rArgs, &pSegRecord);
|
|
|
|
if (!pSegment)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// layout the glyphs as required by OpenOffice
|
|
|
|
bool success = LayoutGlyphs(rArgs, pSegment, pSegRecord);
|
|
|
|
|
|
|
|
if (pSegRecord) pSegRecord->unlock();
|
|
|
|
else delete pSegment;
|
|
|
|
#else
|
|
|
|
gr::Segment * pSegment = CreateSegment(rArgs);
|
|
|
|
bool success = LayoutGlyphs(rArgs, pSegment);
|
|
|
|
delete pSegment;
|
|
|
|
#endif
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef GRCACHE
|
|
|
|
class GrFontHasher : public gr::Font
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
GrFontHasher(const gr::Font & aFont) : gr::Font(aFont), mrRealFont(const_cast<gr::Font&>(aFont)) {};
|
|
|
|
~GrFontHasher(){};
|
|
|
|
virtual bool bold() { return mrRealFont.bold(); };
|
|
|
|
virtual bool italic() { return mrRealFont.italic(); };
|
|
|
|
virtual float ascent() { return mrRealFont.ascent(); };
|
|
|
|
virtual float descent() { return mrRealFont.descent(); };
|
|
|
|
virtual float height() { return mrRealFont.height(); };
|
|
|
|
virtual gr::Font* copyThis() { return mrRealFont.copyThis(); };
|
|
|
|
virtual unsigned int getDPIx() { return mrRealFont.getDPIx(); };
|
|
|
|
virtual unsigned int getDPIy() { return mrRealFont.getDPIy(); };
|
|
|
|
virtual const void* getTable(gr::fontTableId32 nId, size_t* nSize)
|
|
|
|
{ return mrRealFont.getTable(nId,nSize); }
|
|
|
|
virtual void getFontMetrics(float*pA, float*pB, float*pC) { mrRealFont.getFontMetrics(pA,pB,pC); };
|
|
|
|
|
|
|
|
sal_Int32 hashCode(const grutils::GrFeatureParser * mpFeatures)
|
|
|
|
{
|
|
|
|
// is this sufficient?
|
2010-03-12 09:15:29 +00:00
|
|
|
ext_std::wstring aFace;
|
2009-08-17 14:12:14 +00:00
|
|
|
bool bBold;
|
|
|
|
bool bItalic;
|
|
|
|
UniqueCacheInfo(aFace, bBold, bItalic);
|
|
|
|
sal_Unicode uName[32]; // max length used in gr::Font
|
|
|
|
// Note: graphite stores font names as UTF-16 even if wchar_t is 32bit
|
|
|
|
// this conversion should be OK.
|
|
|
|
for (size_t i = 0; i < aFace.size() && i < 32; i++)
|
|
|
|
{
|
|
|
|
uName[i] = aFace[i];
|
|
|
|
}
|
|
|
|
size_t iSize = aFace.size();
|
|
|
|
if (0 == iSize) return 0;
|
|
|
|
sal_Int32 hash = rtl_ustr_hashCode_WithLength(uName, iSize);
|
|
|
|
hash ^= static_cast<sal_Int32>(height());
|
|
|
|
hash |= (bBold)? 0x1000000 : 0;
|
|
|
|
hash |= (bItalic)? 0x2000000 : 0;
|
|
|
|
if (mpFeatures)
|
|
|
|
hash ^= mpFeatures->hashCode();
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(), "font hash %x size %f\n", (int)hash, height());
|
|
|
|
#endif
|
|
|
|
return hash;
|
|
|
|
};
|
|
|
|
|
|
|
|
private:
|
|
|
|
gr::Font & mrRealFont;
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef GRCACHE
|
|
|
|
gr::Segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs, GrSegRecord ** pSegRecord)
|
|
|
|
#else
|
|
|
|
gr::Segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
assert(rArgs.mnLength >= 0);
|
|
|
|
|
|
|
|
gr::Segment * pSegment = NULL;
|
|
|
|
|
|
|
|
// Set the SalLayouts values to be the inital ones.
|
|
|
|
SalLayout::AdjustLayout(rArgs);
|
|
|
|
// TODO check if this is needed
|
|
|
|
if (mnUnitsPerPixel > 1)
|
|
|
|
mfScaling = 1.0f / mnUnitsPerPixel;
|
|
|
|
|
|
|
|
// Clear out any previous buffers
|
|
|
|
clear();
|
|
|
|
bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Don't set RTL if font doesn't support it otherwise it forces rtl on
|
|
|
|
// everything
|
|
|
|
if (bRtl && (mrFont.getSupportedScriptDirections() & gr::kfsdcHorizRtl))
|
|
|
|
maLayout.setRightToLeft(bRtl);
|
|
|
|
|
|
|
|
#ifdef GRCACHE
|
|
|
|
GrFontHasher hasher(mrFont);
|
|
|
|
sal_Int32 aFontHash = hasher.hashCode(mpFeatures);
|
|
|
|
GraphiteSegmentCache * pCache =
|
|
|
|
(GraphiteCacheHandler::instance).getCache(aFontHash);
|
|
|
|
if (pCache)
|
|
|
|
{
|
|
|
|
*pSegRecord = pCache->getSegment(rArgs, bRtl);
|
|
|
|
if (*pSegRecord)
|
|
|
|
{
|
|
|
|
pSegment = (*pSegRecord)->getSegment();
|
|
|
|
mpTextSrc = (*pSegRecord)->getTextSrc();
|
|
|
|
maLayout.setRightToLeft((*pSegRecord)->isRtl());
|
|
|
|
if (rArgs.mpStr != mpTextSrc->getLayoutArgs().mpStr ||
|
|
|
|
rArgs.mnMinCharPos != mpTextSrc->getLayoutArgs().mnMinCharPos ||
|
|
|
|
rArgs.mnEndCharPos != mpTextSrc->getLayoutArgs().mnEndCharPos ||
|
|
|
|
(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) )
|
|
|
|
{
|
|
|
|
(*pSegRecord)->clearVectors();
|
|
|
|
}
|
|
|
|
mpTextSrc->switchLayoutArgs(rArgs);
|
|
|
|
return pSegment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Context is often needed beyond the specified end, however, we don't
|
|
|
|
// want it if there has been a direction change, since it is hard
|
|
|
|
// to tell between reordering within one direction and multi-directional
|
|
|
|
// text.
|
|
|
|
const int segCharLimit = min(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH);
|
|
|
|
int limit = rArgs.mnEndCharPos;
|
|
|
|
if (segCharLimit > limit)
|
|
|
|
{
|
|
|
|
limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos,
|
|
|
|
segCharLimit - rArgs.mnEndCharPos, bRtl);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new TextSource object for the engine.
|
|
|
|
mpTextSrc = new TextSourceAdaptor(rArgs, limit);
|
|
|
|
if (mpFeatures) mpTextSrc->setFeatures(mpFeatures);
|
|
|
|
|
|
|
|
pSegment = new gr::RangeSegment((gr::Font *)&mrFont, mpTextSrc, &maLayout, mnMinCharPos, limit);
|
|
|
|
if (pSegment != NULL)
|
|
|
|
{
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Gr::LayoutText %d-%d, context %d,len%d rtl%d/%d scaling %f\n", rArgs.mnMinCharPos,
|
|
|
|
rArgs.mnEndCharPos, limit, rArgs.mnLength, maLayout.rightToLeft(), pSegment->rightToLeft(), mfScaling);
|
|
|
|
#endif
|
|
|
|
#ifdef GRCACHE
|
|
|
|
// on a new segment rightToLeft should be correct
|
|
|
|
*pSegRecord = pCache->cacheSegment(mpTextSrc, pSegment, pSegment->rightToLeft());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
clear();
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
clear(); // destroy the text source and any partially built segments.
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return pSegment;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef GRCACHE
|
|
|
|
bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr::Segment * pSegment, GrSegRecord * pSegRecord)
|
|
|
|
#else
|
|
|
|
bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr::Segment * pSegment)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
#ifdef GRCACHE
|
|
|
|
#ifdef GRCACHE_REUSE_VECTORS
|
|
|
|
// if we have an exact match, then we can reuse the glyph vectors from before
|
|
|
|
if (pSegRecord && (pSegRecord->glyphs().size() > 0) &&
|
2009-12-07 09:43:27 +01:00
|
|
|
(pSegRecord->fontScale() == mfScaling) &&
|
2009-08-17 14:12:14 +00:00
|
|
|
!(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) )
|
|
|
|
{
|
|
|
|
mnWidth = pSegRecord->width();
|
|
|
|
mvGlyphs = pSegRecord->glyphs();
|
|
|
|
mvCharDxs = pSegRecord->charDxs();
|
|
|
|
mvChar2BaseGlyph = pSegRecord->char2BaseGlyph();
|
|
|
|
mvGlyph2Char = pSegRecord->glyph2Char();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
// Calculate the initial character dxs.
|
|
|
|
mvCharDxs.assign(mnEndCharPos - mnMinCharPos, -1);
|
|
|
|
mvChar2BaseGlyph.assign(mnEndCharPos - mnMinCharPos, -1);
|
|
|
|
mnWidth = 0;
|
|
|
|
if (mvCharDxs.size() > 0)
|
|
|
|
{
|
|
|
|
// Discover all the clusters.
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Note: we use the layout rightToLeft() because in cached segments
|
|
|
|
// rightToLeft() may no longer be valid if the engine has been run
|
|
|
|
// ltr since the segment was created.
|
|
|
|
#ifdef GRCACHE
|
|
|
|
bool bRtl = pSegRecord? pSegRecord->isRtl() : pSegment->rightToLeft();
|
|
|
|
#else
|
|
|
|
bool bRtl = pSegment->rightToLeft();
|
|
|
|
#endif
|
|
|
|
mvGlyphs.fill_from(*pSegment, rArgs, bRtl,
|
|
|
|
mnWidth, mfScaling, mvChar2BaseGlyph, mvGlyph2Char, mvCharDxs);
|
|
|
|
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
// not needed for adjacent differences, but for mouse clicks to char
|
|
|
|
std::transform(mvCharDxs.begin(), mvCharDxs.end(), mvCharDxs.begin(),
|
|
|
|
std::bind1st(std::minus<long>(), mnWidth));
|
|
|
|
// fixup last dx to ensure it always equals the width
|
|
|
|
mvCharDxs[mvCharDxs.size() - 1] = mnWidth;
|
|
|
|
}
|
|
|
|
#ifdef GRCACHE
|
|
|
|
#ifdef GRCACHE_REUSE_VECTORS
|
|
|
|
if (pSegRecord && rArgs.maReruns.IsEmpty() &&
|
|
|
|
!(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags))
|
|
|
|
{
|
|
|
|
pSegRecord->setGlyphVectors(mnWidth, mvGlyphs, mvCharDxs,
|
2009-12-07 09:43:27 +01:00
|
|
|
mvChar2BaseGlyph, mvGlyph2Char,
|
|
|
|
mfScaling);
|
2009-08-17 14:12:14 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
catch (std::exception e)
|
|
|
|
{
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"LayoutGlyphs failed %s\n", e.what());
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"LayoutGlyphs failed with exception");
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mnWidth = 0;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GraphiteLayout::GetTextBreak(long maxmnWidth, long char_extra, int factor) const
|
|
|
|
{
|
|
|
|
// Adjust maxmnWidth so FindNextBreakPoint returns a sensible answer.
|
|
|
|
maxmnWidth -= (mnEndCharPos-mnMinCharPos-1)*char_extra; // extra character spacing.
|
|
|
|
maxmnWidth /= factor; // scaling factor.
|
|
|
|
|
|
|
|
// Ask the segment for the nearest whole letter break for the width.
|
|
|
|
//float width;
|
|
|
|
float targetWidth = maxmnWidth/mfScaling;
|
|
|
|
// return quickly if this segment is narrower than the target width
|
|
|
|
// (sometimes graphite doesn't seem to realise this!)
|
|
|
|
if (targetWidth > mnWidth)
|
|
|
|
return STRING_LEN;
|
|
|
|
//int nBreak = mpSegment->findNextBreakPoint(mnMinCharPos,
|
|
|
|
// gr::klbWordBreak, gr::klbLetterBreak, targetWidth, &width);
|
|
|
|
|
|
|
|
// LineFillSegment seems to give better results that findNextBreakPoint
|
|
|
|
// though it may be slower
|
|
|
|
gr::LayoutEnvironment aLE;
|
|
|
|
gr::LineFillSegment lineSeg(const_cast<gr::Font *>(&mrFont), mpTextSrc, &aLE,
|
|
|
|
mnMinCharPos, mpTextSrc->getContextLength(),
|
|
|
|
targetWidth);
|
|
|
|
int nBreak = lineSeg.stopCharacter();
|
|
|
|
|
|
|
|
if (nBreak > mnEndCharPos) nBreak = STRING_LEN;
|
|
|
|
else if (nBreak < mnMinCharPos) nBreak = mnMinCharPos;
|
|
|
|
return nBreak;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
long GraphiteLayout::FillDXArray( sal_Int32* pDXArray ) const
|
|
|
|
{
|
|
|
|
if (mnEndCharPos == mnMinCharPos)
|
|
|
|
// Then we must be zero width!
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (pDXArray)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < mvCharDxs.size(); i++)
|
|
|
|
{
|
|
|
|
assert((mvChar2BaseGlyph[i] >= -1) && (mvChar2BaseGlyph[i] < (signed)mvGlyphs.size()));
|
|
|
|
if (mvChar2BaseGlyph[i] != -1 &&
|
|
|
|
mvGlyphs[mvChar2BaseGlyph[i]].mnGlyphIndex == GF_DROPPED)
|
|
|
|
{
|
|
|
|
// when used in MultiSalLayout::GetTextBreak dropped glyphs
|
|
|
|
// must have zero width
|
|
|
|
pDXArray[i] = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pDXArray[i] = mvCharDxs[i];
|
|
|
|
if (i > 0) pDXArray[i] -= mvCharDxs[i-1];
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"%d,%d,%ld ", (int)i, (int)mvCharDxs[i], pDXArray[i]);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//std::adjacent_difference(mvCharDxs.begin(), mvCharDxs.end(), pDXArray);
|
|
|
|
//for (size_t i = 0; i < mvCharDxs.size(); i++)
|
|
|
|
// fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]);
|
|
|
|
//fprintf(grLog(),"FillDX %ld,%d\n", mnWidth, std::accumulate(pDXArray, pDXArray + mvCharDxs.size(), 0));
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"FillDXArray %d-%d,%d=%ld\n", mnMinCharPos, mnEndCharPos, (int)mpTextSrc->getLength(), mnWidth);
|
|
|
|
#endif
|
|
|
|
return mnWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GraphiteLayout::AdjustLayout(ImplLayoutArgs& rArgs)
|
|
|
|
{
|
|
|
|
SalLayout::AdjustLayout(rArgs);
|
|
|
|
|
|
|
|
if(rArgs.mpDXArray)
|
|
|
|
{
|
|
|
|
std::vector<int> vDeltaWidths(mvGlyphs.size(), 0);
|
|
|
|
ApplyDXArray(rArgs, vDeltaWidths);
|
|
|
|
|
|
|
|
if( (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL) &&
|
|
|
|
!(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) )
|
|
|
|
{
|
|
|
|
// check if this is a kashida script
|
|
|
|
bool bKashidaScript = false;
|
|
|
|
for (int i = rArgs.mnMinCharPos; i < rArgs.mnEndCharPos; i++)
|
|
|
|
{
|
|
|
|
UErrorCode aStatus = U_ZERO_ERROR;
|
|
|
|
UScriptCode scriptCode = uscript_getScript(rArgs.mpStr[i], &aStatus);
|
|
|
|
if (scriptCode == USCRIPT_ARABIC || scriptCode == USCRIPT_SYRIAC)
|
|
|
|
{
|
|
|
|
bKashidaScript = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int nKashidaWidth = 0;
|
|
|
|
int nKashidaIndex = getKashidaGlyph(nKashidaWidth);
|
|
|
|
if( nKashidaIndex != 0 && bKashidaScript)
|
|
|
|
{
|
|
|
|
kashidaJustify( vDeltaWidths, nKashidaIndex, nKashidaWidth );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GraphiteLayout::ApplyDXArray(ImplLayoutArgs &args, std::vector<int> & rDeltaWidth)
|
|
|
|
{
|
|
|
|
const size_t nChars = args.mnEndCharPos - args.mnMinCharPos;
|
|
|
|
if (nChars == 0) return;
|
|
|
|
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
for (size_t iDx = 0; iDx < mvCharDxs.size(); iDx++)
|
|
|
|
fprintf(grLog(),"%d,%d,%ld ", (int)iDx, (int)mvCharDxs[iDx], args.mpDXArray[iDx]);
|
|
|
|
fprintf(grLog(),"ApplyDx\n");
|
|
|
|
#endif
|
|
|
|
bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL;
|
|
|
|
int nXOffset = 0;
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
nXOffset = args.mpDXArray[nChars - 1] - mvCharDxs[nChars - 1];
|
|
|
|
}
|
|
|
|
int nPrevClusterGlyph = (bRtl)? mvGlyphs.size() : -1;
|
|
|
|
int nPrevClusterLastChar = -1;
|
|
|
|
for (size_t i = 0; i < nChars; i++)
|
|
|
|
{
|
|
|
|
if (mvChar2BaseGlyph[i] > -1 && mvChar2BaseGlyph[i] != nPrevClusterGlyph)
|
|
|
|
{
|
|
|
|
assert((mvChar2BaseGlyph[i] > -1) && (mvChar2BaseGlyph[i] < (signed)mvGlyphs.size()));
|
|
|
|
GlyphItem & gi = mvGlyphs[mvChar2BaseGlyph[i]];
|
|
|
|
if (!gi.IsClusterStart())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// find last glyph of this cluster
|
|
|
|
size_t j = i + 1;
|
|
|
|
int nLastChar = i;
|
|
|
|
int nLastGlyph = mvChar2BaseGlyph[i];
|
|
|
|
for (; j < nChars; j++)
|
|
|
|
{
|
|
|
|
assert((mvChar2BaseGlyph[j] >= -1) && (mvChar2BaseGlyph[j] < (signed)mvGlyphs.size()));
|
|
|
|
if (mvChar2BaseGlyph[j] != -1 && mvGlyphs[mvChar2BaseGlyph[j]].IsClusterStart())
|
|
|
|
{
|
|
|
|
nLastGlyph = mvChar2BaseGlyph[j] + ((bRtl)? 1 : -1);
|
|
|
|
nLastChar = j - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nLastGlyph < 0)
|
|
|
|
{
|
|
|
|
nLastGlyph = mvChar2BaseGlyph[i];
|
|
|
|
}
|
|
|
|
// Its harder to find the last glyph rtl, since the first of
|
|
|
|
// cluster is still on the left so we need to search towards
|
|
|
|
// the previous cluster to the right
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
nLastGlyph = mvChar2BaseGlyph[i];
|
|
|
|
while (nLastGlyph + 1 < (signed)mvGlyphs.size() &&
|
|
|
|
!mvGlyphs[nLastGlyph+1].IsClusterStart())
|
|
|
|
{
|
|
|
|
++nLastGlyph;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (j == nChars)
|
|
|
|
{
|
|
|
|
nLastChar = nChars - 1;
|
|
|
|
if (!bRtl) nLastGlyph = mvGlyphs.size() - 1;
|
|
|
|
}
|
|
|
|
assert((nLastChar > -1) && (nLastChar < (signed)nChars));
|
|
|
|
long nNewClusterWidth = args.mpDXArray[nLastChar];
|
|
|
|
long nOrigClusterWidth = mvCharDxs[nLastChar];
|
|
|
|
long nDGlyphOrigin = 0;
|
|
|
|
if (nPrevClusterLastChar > - 1)
|
|
|
|
{
|
|
|
|
assert(nPrevClusterLastChar < (signed)nChars);
|
|
|
|
nNewClusterWidth -= args.mpDXArray[nPrevClusterLastChar];
|
|
|
|
nOrigClusterWidth -= mvCharDxs[nPrevClusterLastChar];
|
|
|
|
nDGlyphOrigin = args.mpDXArray[nPrevClusterLastChar] - mvCharDxs[nPrevClusterLastChar];
|
|
|
|
}
|
|
|
|
long nDWidth = nNewClusterWidth - nOrigClusterWidth;
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(), "c%d last glyph %d/%d\n", i, nLastGlyph, mvGlyphs.size());
|
|
|
|
#endif
|
|
|
|
assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size()));
|
|
|
|
mvGlyphs[nLastGlyph].mnNewWidth += nDWidth;
|
|
|
|
if (gi.mnGlyphIndex != GF_DROPPED)
|
|
|
|
mvGlyphs[nLastGlyph].mnNewWidth += nDWidth;
|
|
|
|
else
|
|
|
|
nDGlyphOrigin += nDWidth;
|
|
|
|
// update glyph positions
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
for (int n = mvChar2BaseGlyph[i]; n <= nLastGlyph; n++)
|
|
|
|
{
|
|
|
|
assert((n > - 1) && (n < (signed)mvGlyphs.size()));
|
|
|
|
mvGlyphs[n].maLinearPos.X() += -nDGlyphOrigin + nXOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int n = mvChar2BaseGlyph[i]; n <= nLastGlyph; n++)
|
|
|
|
{
|
|
|
|
assert((n > - 1) && (n < (signed)mvGlyphs.size()));
|
|
|
|
mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + nXOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rDeltaWidth[mvChar2BaseGlyph[i]] = nDWidth;
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"c%d g%d-%d dW%ld-%ld=%ld dX%ld x%ld\t", (int)i, mvChar2BaseGlyph[i], nLastGlyph, nNewClusterWidth, nOrigClusterWidth, nDWidth, nDGlyphOrigin, mvGlyphs[mvChar2BaseGlyph[i]].maLinearPos.X());
|
|
|
|
#endif
|
|
|
|
nPrevClusterGlyph = mvChar2BaseGlyph[i];
|
|
|
|
nPrevClusterLastChar = nLastChar;
|
|
|
|
i = nLastChar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Update the dx vector with the new values.
|
|
|
|
std::copy(args.mpDXArray, args.mpDXArray + nChars,
|
|
|
|
mvCharDxs.begin() + (args.mnMinCharPos - mnMinCharPos));
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"ApplyDx %ld(%ld)\n", args.mpDXArray[nChars - 1], mnWidth);
|
|
|
|
#endif
|
|
|
|
mnWidth = args.mpDXArray[nChars - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphiteLayout::kashidaJustify(std::vector<int>& rDeltaWidths, sal_GlyphId nKashidaIndex, int nKashidaWidth)
|
|
|
|
{
|
|
|
|
// skip if the kashida glyph in the font looks suspicious
|
|
|
|
if( nKashidaWidth <= 0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// calculate max number of needed kashidas
|
|
|
|
Glyphs::iterator i = mvGlyphs.begin();
|
|
|
|
int nKashidaCount = 0;
|
|
|
|
int nOrigGlyphIndex = -1;
|
|
|
|
int nGlyphIndex = -1;
|
|
|
|
while (i != mvGlyphs.end())
|
|
|
|
{
|
|
|
|
nOrigGlyphIndex++;
|
|
|
|
nGlyphIndex++;
|
|
|
|
// only inject kashidas in RTL contexts
|
|
|
|
if( !(*i).IsRTLGlyph() )
|
|
|
|
{
|
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// no kashida-injection for blank justified expansion either
|
|
|
|
if( IsSpacingGlyph( (*i).mnGlyphIndex ) )
|
|
|
|
{
|
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// calculate gap, ignore if too small
|
|
|
|
int nGapWidth = rDeltaWidths[nOrigGlyphIndex];;
|
|
|
|
// worst case is one kashida even for mini-gaps
|
|
|
|
if( 3 * nGapWidth < nKashidaWidth )
|
|
|
|
{
|
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
nKashidaCount = 1 + (nGapWidth / nKashidaWidth);
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
printf("inserting %d kashidas at %ld\n", nKashidaCount, (*i).mnGlyphIndex);
|
|
|
|
#endif
|
|
|
|
GlyphItem glyphItem = *i;
|
|
|
|
Point aPos(0, 0);
|
|
|
|
aPos.X() = (*i).maLinearPos.X();
|
|
|
|
GlyphItem newGi(glyphItem.mnCharPos, nKashidaIndex, aPos,
|
|
|
|
GlyphItem::IS_IN_CLUSTER|GlyphItem::IS_RTL_GLYPH, nKashidaWidth);
|
|
|
|
mvGlyphs.reserve(mvGlyphs.size() + nKashidaCount);
|
|
|
|
i = mvGlyphs.begin() + nGlyphIndex;
|
|
|
|
mvGlyphs.insert(i, nKashidaCount, newGi);
|
|
|
|
i = mvGlyphs.begin() + nGlyphIndex;
|
|
|
|
nGlyphIndex += nKashidaCount;
|
|
|
|
// now fix up the kashida positions
|
|
|
|
for (int j = 0; j < nKashidaCount; j++)
|
|
|
|
{
|
|
|
|
(*(i)).maLinearPos.X() -= nGapWidth;
|
|
|
|
nGapWidth -= nKashidaWidth;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fixup rightmost kashida for gap remainder
|
|
|
|
if( nGapWidth < 0 )
|
|
|
|
{
|
|
|
|
if( nKashidaCount <= 1 )
|
|
|
|
nGapWidth /= 2; // for small gap move kashida to middle
|
|
|
|
(*(i-1)).mnNewWidth += nGapWidth; // adjust kashida width to gap width
|
|
|
|
(*(i-1)).maLinearPos.X() += nGapWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
(*i).mnNewWidth = (*i).mnOrigWidth;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphiteLayout::GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const
|
|
|
|
{
|
|
|
|
// For each character except the last discover the caret positions
|
|
|
|
// immediatly before and after that character.
|
|
|
|
// This is used for underlines in the GUI amongst other things.
|
|
|
|
// It may be used from MultiSalLayout, in which case it must take into account
|
|
|
|
// glyphs that have been moved.
|
|
|
|
std::fill(pCaretXArray, pCaretXArray + nArraySize, -1);
|
|
|
|
// the layout method doesn't modify the layout even though it isn't
|
|
|
|
// const in the interface
|
|
|
|
bool bRtl = const_cast<GraphiteLayout*>(this)->maLayout.rightToLeft();
|
|
|
|
int prevBase = -1;
|
|
|
|
long prevClusterWidth = 0;
|
|
|
|
for (int i = 0, nCharSlot = 0; i < nArraySize && nCharSlot < static_cast<int>(mvCharDxs.size()); ++nCharSlot, i+=2)
|
|
|
|
{
|
|
|
|
if (mvChar2BaseGlyph[nCharSlot] != -1)
|
|
|
|
{
|
|
|
|
assert((mvChar2BaseGlyph[nCharSlot] > -1) && (mvChar2BaseGlyph[nCharSlot] < (signed)mvGlyphs.size()));
|
|
|
|
GlyphItem gi = mvGlyphs[mvChar2BaseGlyph[nCharSlot]];
|
|
|
|
if (gi.mnGlyphIndex == GF_DROPPED)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int nCluster = mvChar2BaseGlyph[nCharSlot];
|
|
|
|
long origClusterWidth = gi.mnNewWidth;
|
|
|
|
long nMin = gi.maLinearPos.X();
|
|
|
|
long nMax = gi.maLinearPos.X() + gi.mnNewWidth;
|
|
|
|
// attached glyphs are always stored after their base rtl or ltr
|
|
|
|
while (++nCluster < static_cast<int>(mvGlyphs.size()) &&
|
|
|
|
!mvGlyphs[nCluster].IsClusterStart())
|
|
|
|
{
|
|
|
|
origClusterWidth += mvGlyphs[nCluster].mnNewWidth;
|
|
|
|
if (mvGlyph2Char[nCluster] == nCharSlot)
|
|
|
|
{
|
|
|
|
nMin = std::min(nMin, mvGlyphs[nCluster].maLinearPos.X());
|
|
|
|
nMax = std::min(nMax, mvGlyphs[nCluster].maLinearPos.X() + mvGlyphs[nCluster].mnNewWidth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
pCaretXArray[i+1] = nMin;
|
|
|
|
pCaretXArray[i] = nMax;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pCaretXArray[i] = nMin;
|
|
|
|
pCaretXArray[i+1] = nMax;
|
|
|
|
}
|
|
|
|
prevBase = mvChar2BaseGlyph[nCharSlot];
|
|
|
|
prevClusterWidth = origClusterWidth;
|
|
|
|
}
|
|
|
|
else if (prevBase > -1)
|
|
|
|
{
|
|
|
|
// this could probably be improved
|
|
|
|
assert((prevBase > -1) && (prevBase < (signed)mvGlyphs.size()));
|
|
|
|
GlyphItem gi = mvGlyphs[prevBase];
|
|
|
|
int nGlyph = prevBase + 1;
|
|
|
|
// try to find a better match, otherwise default to complete cluster
|
|
|
|
for (; nGlyph < static_cast<int>(mvGlyphs.size()) &&
|
|
|
|
!mvGlyphs[nGlyph].IsClusterStart(); nGlyph++)
|
|
|
|
{
|
|
|
|
if (mvGlyph2Char[nGlyph] == nCharSlot)
|
|
|
|
{
|
|
|
|
gi = mvGlyphs[nGlyph];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
long nGWidth = gi.mnNewWidth;
|
|
|
|
// if no match position at end of cluster
|
|
|
|
if (nGlyph == static_cast<int>(mvGlyphs.size()) ||
|
|
|
|
mvGlyphs[nGlyph].IsClusterStart())
|
|
|
|
{
|
|
|
|
nGWidth = prevClusterWidth;
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
pCaretXArray[i+1] = gi.maLinearPos.X();
|
|
|
|
pCaretXArray[i] = gi.maLinearPos.X();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pCaretXArray[i] = gi.maLinearPos.X() + prevClusterWidth;
|
|
|
|
pCaretXArray[i+1] = gi.maLinearPos.X() + prevClusterWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (bRtl)
|
|
|
|
{
|
|
|
|
pCaretXArray[i+1] = gi.maLinearPos.X();
|
|
|
|
pCaretXArray[i] = gi.maLinearPos.X() + gi.mnNewWidth;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pCaretXArray[i] = gi.maLinearPos.X();
|
|
|
|
pCaretXArray[i+1] = gi.maLinearPos.X() + gi.mnNewWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pCaretXArray[i] = pCaretXArray[i+1] = 0;
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"%d,%ld-%ld\t", nCharSlot, pCaretXArray[i], pCaretXArray[i+1]);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"\n");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetNextGlyphs returns a contiguous sequence of glyphs that can be
|
|
|
|
// rendered together. It should never return a dropped glyph.
|
|
|
|
// The glyph_slot returned should be the index of the next visible
|
|
|
|
// glyph after the last glyph returned by this call.
|
|
|
|
// The char_index array should be filled with the characters corresponding
|
|
|
|
// to each glyph returned.
|
|
|
|
// glyph_adv array should be a virtual width such that if successive
|
|
|
|
// glyphs returned by this method are added one after the other they
|
|
|
|
// have the correct spacing.
|
|
|
|
// The logic in this method must match that expected in MultiSalLayout which
|
|
|
|
// is used when glyph fallback is in operation.
|
|
|
|
int GraphiteLayout::GetNextGlyphs( int length, sal_GlyphId * glyph_out,
|
|
|
|
::Point & aPosOut, int &glyph_slot, sal_Int32 * glyph_adv, int *char_index) const
|
|
|
|
{
|
|
|
|
// Sanity check on the slot index.
|
|
|
|
if (glyph_slot >= signed(mvGlyphs.size()))
|
|
|
|
{
|
|
|
|
glyph_slot = mvGlyphs.size();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
assert(glyph_slot >= 0);
|
|
|
|
// Find the first glyph in the substring.
|
|
|
|
for (; glyph_slot < signed(mvGlyphs.size()) &&
|
|
|
|
((mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED);
|
|
|
|
++glyph_slot) {};
|
|
|
|
|
|
|
|
// Update the length
|
|
|
|
const int nGlyphSlotEnd = std::min(size_t(glyph_slot + length), mvGlyphs.size());
|
|
|
|
|
|
|
|
// We're all out of glyphs here.
|
|
|
|
if (glyph_slot == nGlyphSlotEnd)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find as many glyphs as we can which can be drawn in one go.
|
|
|
|
Glyphs::const_iterator glyph_itr = mvGlyphs.begin() + glyph_slot;
|
|
|
|
const int glyph_slot_begin = glyph_slot;
|
|
|
|
const int initial_y_pos = glyph_itr->maLinearPos.Y();
|
|
|
|
|
|
|
|
// Set the position to the position of the start glyph.
|
|
|
|
::Point aStartPos = glyph_itr->maLinearPos;
|
|
|
|
//aPosOut = glyph_itr->maLinearPos;
|
|
|
|
aPosOut = GetDrawPosition(aStartPos);
|
|
|
|
|
|
|
|
|
|
|
|
for (;;) // Forever
|
|
|
|
{
|
|
|
|
// last index of the range from glyph_to_chars does not include this glyph
|
|
|
|
if (char_index)
|
|
|
|
{
|
|
|
|
assert((glyph_slot >= -1) && (glyph_slot < (signed)mvGlyph2Char.size()));
|
|
|
|
if (mvGlyph2Char[glyph_slot] == -1)
|
|
|
|
*char_index++ = mvCharDxs.size();
|
|
|
|
else
|
|
|
|
*char_index++ = mvGlyph2Char[glyph_slot];
|
|
|
|
}
|
|
|
|
// Copy out this glyphs data.
|
|
|
|
++glyph_slot;
|
|
|
|
*glyph_out++ = glyph_itr->mnGlyphIndex;
|
|
|
|
|
|
|
|
// Find the actual advance - this must be correct if called from
|
|
|
|
// MultiSalLayout::AdjustLayout which requests one glyph at a time.
|
|
|
|
const long nGlyphAdvance = (glyph_slot == static_cast<int>(mvGlyphs.size()))?
|
|
|
|
glyph_itr->mnNewWidth :
|
|
|
|
((glyph_itr+1)->maLinearPos.X() - glyph_itr->maLinearPos.X());
|
|
|
|
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"GetNextGlyphs g%d c%d x%ld,%ld adv%ld, pos %ld,%ld\n", glyph_slot - 1,
|
|
|
|
mvGlyph2Char[glyph_slot-1], glyph_itr->maLinearPos.X(), glyph_itr->maLinearPos.Y(), nGlyphAdvance,
|
|
|
|
aPosOut.X(), aPosOut.Y());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (glyph_adv) // If we are returning advance store it.
|
|
|
|
*glyph_adv++ = nGlyphAdvance;
|
|
|
|
else // Stop when next advance is unexpected.
|
|
|
|
if (glyph_itr->mnOrigWidth != nGlyphAdvance) break;
|
|
|
|
|
|
|
|
// Have fetched all the glyphs we need to
|
|
|
|
if (glyph_slot == nGlyphSlotEnd)
|
|
|
|
break;
|
|
|
|
|
|
|
|
++glyph_itr;
|
|
|
|
// Stop when next y position is unexpected.
|
|
|
|
if (initial_y_pos != glyph_itr->maLinearPos.Y())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Stop if glyph dropped
|
|
|
|
if (glyph_itr->mnGlyphIndex == GF_DROPPED)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
int numGlyphs = glyph_slot - glyph_slot_begin;
|
|
|
|
// move the next glyph_slot to a glyph that hasn't been dropped
|
|
|
|
while (glyph_slot < static_cast<int>(mvGlyphs.size()) &&
|
|
|
|
(mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED)
|
|
|
|
++glyph_slot;
|
|
|
|
return numGlyphs;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GraphiteLayout::MoveGlyph( int nGlyphIndex, long nNewPos )
|
|
|
|
{
|
|
|
|
// TODO it might be better to actualy implement simplify properly, but this
|
|
|
|
// needs to be done carefully so the glyph/char maps are maintained
|
|
|
|
// If a glyph has been dropped then it wasn't returned by GetNextGlyphs, so
|
|
|
|
// the index here may be wrong
|
|
|
|
while ((mvGlyphs[nGlyphIndex].mnGlyphIndex == GF_DROPPED) &&
|
|
|
|
(nGlyphIndex < (signed)mvGlyphs.size()))
|
|
|
|
{
|
|
|
|
nGlyphIndex++;
|
|
|
|
}
|
|
|
|
const long dx = nNewPos - mvGlyphs[nGlyphIndex].maLinearPos.X();
|
|
|
|
|
|
|
|
if (dx == 0) return;
|
|
|
|
// GenericSalLayout only changes maLinearPos, mvCharDxs doesn't change
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Move %d (%ld,%ld) c%d by %ld\n", nGlyphIndex, mvGlyphs[nGlyphIndex].maLinearPos.X(), nNewPos, mvGlyph2Char[nGlyphIndex], dx);
|
|
|
|
#endif
|
|
|
|
for (size_t gi = nGlyphIndex; gi < mvGlyphs.size(); gi++)
|
|
|
|
{
|
|
|
|
mvGlyphs[gi].maLinearPos.X() += dx;
|
|
|
|
}
|
|
|
|
// width does need to be updated for correct fallback
|
|
|
|
mnWidth += dx;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GraphiteLayout::DropGlyph( int nGlyphIndex )
|
|
|
|
{
|
|
|
|
if(nGlyphIndex >= signed(mvGlyphs.size()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
GlyphItem & glyph = mvGlyphs[nGlyphIndex];
|
|
|
|
glyph.mnGlyphIndex = GF_DROPPED;
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Dropped %d\n", nGlyphIndex);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphiteLayout::Simplify( bool isBaseLayout )
|
|
|
|
{
|
|
|
|
const sal_GlyphId dropMarker = isBaseLayout ? GF_DROPPED : 0;
|
|
|
|
|
|
|
|
Glyphs::iterator gi = mvGlyphs.begin();
|
|
|
|
// TODO check whether we need to adjust positions here
|
|
|
|
// MultiSalLayout seems to move the glyphs itself, so it may not be needed.
|
|
|
|
long deltaX = 0;
|
|
|
|
while (gi != mvGlyphs.end())
|
|
|
|
{
|
|
|
|
if (gi->mnGlyphIndex == dropMarker)
|
|
|
|
{
|
|
|
|
deltaX += gi->mnNewWidth;
|
|
|
|
gi->mnNewWidth = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
deltaX = 0;
|
|
|
|
}
|
|
|
|
//mvCharDxs[mvGlyph2Char[gi->mnCharPos]] -= deltaX;
|
|
|
|
++gi;
|
|
|
|
}
|
|
|
|
#ifdef GRLAYOUT_DEBUG
|
|
|
|
fprintf(grLog(),"Simplify base%d dx=%ld newW=%ld\n", isBaseLayout, deltaX, mnWidth - deltaX);
|
|
|
|
#endif
|
|
|
|
// discard width from trailing dropped glyphs, but not those in the middle
|
|
|
|
mnWidth -= deltaX;
|
|
|
|
}
|