blob: 0459da5faf0306cbd602a45135c1fd36a5ff11a4 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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
#ifdef WNT
#include <tools/svwin.h>
#include <svsys.h>
#endif
#ifdef UNX
#include <graphite_adaptors.hxx>
#endif
#include <salgdi.hxx>
#include <unicode/uchar.h>
#include <unicode/ubidi.h>
#include <unicode/uscript.h>
// Graphite Libraries (must be after vcl headers on windows)
#include <preextstl.h>
#include <graphite/GrClient.h>
#include <graphite/Font.h>
#include <graphite/ITextSource.h>
#include <graphite/Segment.h>
#include <graphite/SegmentPainter.h>
#include <postextstl.h>
#include <graphite_layout.hxx>
#include <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()
{
#ifdef WNT
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 <graphite_cache.hxx>
#endif
namespace
{
typedef ext_std::pair<gr::GlyphIterator, gr::GlyphIterator> glyph_range_t;
typedef ext_std::pair<gr::GlyphSetIterator, gr::GlyphSetIterator> glyph_set_range_t;
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;
ubidi_setPara(ubidi, reinterpret_cast<const UChar *>(buffer), charCount,
(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.
typedef ext_std::pair< gr::GlyphSetIterator, gr::GlyphSetIterator > GrGlyphSet;
int nChar = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
glyph_range_t iGlyphs = rSegment.glyphs();
int nGlyphs = iGlyphs.second - iGlyphs.first;
gr::GlyphIterator prevBase = iGlyphs.second;
float fSegmentAdvance = rSegment.advanceWidth();
float fMinX = fSegmentAdvance;
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,
fSegmentAdvance, 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, fSegmentAdvance,
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,float fSegmentAdvance,
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;
// can we break before this cluster?
// Glyphs may have either a positive or negative breakWeight refering to
// the position after or before the glyph respectively
int nPrevBreakWeight = 0;
if (nFirstGlyphInCluster > 0)
{
nPrevBreakWeight = (iGlyphs.first + (nFirstGlyphInCluster - 1))->breakweight();
}
int nBreakWeight = aFirstGlyph.breakweight();
if (nBreakWeight < 0)
{
// negative means it applies to the position before the glyph's character
nBreakWeight *= -1;
if (nPrevBreakWeight > 0 && nPrevBreakWeight < nBreakWeight)
{
// prevBreakWeight wins
nBreakWeight = nPrevBreakWeight;
}
}
else
{
nBreakWeight = 0;
// positive means break after
if (nPrevBreakWeight > 0)
nBreakWeight = nPrevBreakWeight;
}
if (nBreakWeight > gr::klbNoBreak/*0*/ &&
// nBreakWeight <= gr::klbHyphenBreak) // uses Graphite hyphenation
nBreakWeight <= gr::klbLetterBreak) // Needed for issue 111272
{
if (nBreakWeight < gr::klbHyphenBreak)
rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] |= WORD_BREAK_BEFORE;
else
rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] |= HYPHEN_BREAK_BEFORE;
}
// always allow a break before a space even if graphite doesn't
if (rArgs.mpStr[nFirstCharInCluster] == 0x20)
rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] |= WORD_BREAK_BEFORE;
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 = fSegmentAdvance;
nNextOrigin = round(fSegmentAdvance * fScaling + rDXOffset);
aBounds.second = std::max(fSegmentAdvance, 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 bw%d\n", nFirstGlyphInCluster, nNextGlyph, nFirstCharInCluster, nNextChar, rArgs.mpStr[nFirstCharInCluster], nXPos, aFirstGlyph.yOffset(), nBreakWeight);
#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(true);
// 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 = NULL;
// Graphite can in rare cases crash with a zero length
if (rArgs.mnMinCharPos < rArgs.mnEndCharPos)
{
pSegment = CreateSegment(rArgs, &pSegRecord);
if (!pSegment)
return false;
}
else
{
clear();
return true;
}
// layout the glyphs as required by OpenOffice
bool success = LayoutGlyphs(rArgs, pSegment, pSegRecord);
if (pSegRecord) pSegRecord->unlock();
else delete pSegment;
#else
gr::Segment * pSegment = NULL;
bool success = true;
if (rArgs.mnMinCharPos < rArgs.mnEndCharPos)
{
pSegment = CreateSegment(rArgs);
if (!pSegment)
return false;
success = LayoutGlyphs(rArgs, pSegment);
if (pSegment) delete pSegment;
}
else
{
clear();
}
#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?
ext_std::wstring aFace;
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;
};
protected:
virtual void UniqueCacheInfo( ext_std::wstring& stuFace, bool& fBold, bool& fItalic )
{
#ifdef WIN32
dynamic_cast<GraphiteWinFont&>(mrRealFont).UniqueCacheInfo(stuFace, fBold, fItalic);
#else
#ifdef UNX
dynamic_cast<GraphiteFontAdaptor&>(mrRealFont).UniqueCacheInfo(stuFace, fBold, fItalic);
#else
#error Unknown base type for gr::Font::UniqueCacheInfo
#endif
#endif
}
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);
// 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. Extra context, can also cause problems with ligatures stradling
// a hyphenation point, so disable if CTL is disabled.
const int nSegCharLimit = min(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH);
int limit = rArgs.mnEndCharPos;
if ((nSegCharLimit > limit) && !(SAL_LAYOUT_COMPLEX_DISABLED & rArgs.mnFlags))
{
limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos,
nSegCharLimit - rArgs.mnEndCharPos, 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, limit);
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);
if (limit > rArgs.mnMinCharPos && limit == rArgs.mnEndCharPos
&& pSegment->stopCharacter() != limit)
{
// check that the last character is not part of a ligature
glyph_set_range_t aGlyphSet = pSegment->charToGlyphs(limit - 1);
if (aGlyphSet.first == aGlyphSet.second)
{
// no glyphs associated with this glyph - occurs mid ligature
pSegment = NULL;
*pSegRecord = NULL;
}
else
{
while (aGlyphSet.first != aGlyphSet.second)
{
int lastChar = static_cast<int>((*aGlyphSet.first).lastChar());
if (lastChar >= limit)
{
pSegment = NULL;
*pSegRecord = NULL;
break;
}
aGlyphSet.first++;
}
}
}
if (pSegment)
return pSegment;
}
}
#endif
// 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
{
#ifdef GRLAYOUT_DEBUG
fprintf(grLog(), "Gr::LayoutText failed: ");
for (int i = mnMinCharPos; i < limit; i++)
{
fprintf(grLog(), "%04x ", rArgs.mpStr[i]);
}
fprintf(grLog(), "\n");
#endif
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) &&
(pSegRecord->fontScale() == mfScaling) &&
!(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,
mvChar2BaseGlyph, mvGlyph2Char,
mfScaling);
}
#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
{
#ifdef GRLAYOUT_DEBUG
fprintf(grLog(),"Gr::GetTextBreak c[%d-%d) maxWidth %ld char extra %ld factor %d\n",
mnMinCharPos, mnEndCharPos, maxmnWidth, char_extra, factor);
#endif
// return quickly if this segment is narrower than the target width
if (maxmnWidth > mnWidth * factor + char_extra * (mnEndCharPos - mnMinCharPos - 1))
return STRING_LEN;
long nWidth = mvCharDxs[0] * factor;
int nLastBreak = -1;
for (size_t i = 1; i < mvCharDxs.size(); i++)
{
nWidth += char_extra;
if (nWidth > maxmnWidth) break;
if (mvChar2BaseGlyph[i] != -1)
{
if (mvChar2BaseGlyph[i] & (WORD_BREAK_BEFORE | HYPHEN_BREAK_BEFORE))
nLastBreak = static_cast<int>(i);
}
nWidth += (mvCharDxs[i] - mvCharDxs[i-1]) * factor;
}
int nBreak = mnMinCharPos;
if (nLastBreak > -1)
nBreak += nLastBreak;
#ifdef GRLAYOUT_DEBUG
fprintf(grLog(), "Gr::GetTextBreak break after %d\n", nBreak - mnMinCharPos);
#endif
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) ||
((signed)(mvChar2BaseGlyph[i] & GLYPH_INDEX_MASK) < (signed)mvGlyphs.size()));
if (mvChar2BaseGlyph[i] != -1 &&
mvGlyphs[mvChar2BaseGlyph[i] & GLYPH_INDEX_MASK].maGlyphId == 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,%d ", (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 );
}
}
}
else if (rArgs.mnLayoutWidth > 0)
{
#ifdef GRLAYOUT_DEBUG
fprintf(grLog(), "AdjustLayout width %ld=>%ld\n", mnWidth, rArgs.mnLayoutWidth);
#endif
expandOrCondense(rArgs);
}
}
void GraphiteLayout::expandOrCondense(ImplLayoutArgs &rArgs)
{
int nDeltaWidth = rArgs.mnLayoutWidth - mnWidth;
if (nDeltaWidth > 0) // expand, just expand between clusters
{
int nClusterCount = 0;
for (size_t j = 0; j < mvGlyphs.size(); j++)
{
if (mvGlyphs[j].IsClusterStart())
{
++nClusterCount;
}
}
if (nClusterCount > 1)
{
float fExtraPerCluster = static_cast<float>(nDeltaWidth) / static_cast<float>(nClusterCount - 1);
int nCluster = 0;
int nOffset = 0;
for (size_t i = 0; i < mvGlyphs.size(); i++)
{
if (mvGlyphs[i].IsClusterStart())
{
nOffset = FRound( fExtraPerCluster * nCluster );
size_t nCharIndex = mvGlyph2Char[i];
mvCharDxs[nCharIndex] += nOffset;
// adjust char dxs for rest of characters in cluster
while (++nCharIndex < mvGlyph2Char.size())
{
int nChar2Base = (mvChar2BaseGlyph[nCharIndex] == -1)? -1 : (int)(mvChar2BaseGlyph[nCharIndex] & GLYPH_INDEX_MASK);
if (nChar2Base == -1 || nChar2Base == static_cast<int>(i))
mvCharDxs[nCharIndex] += nOffset;
}
++nCluster;
}
mvGlyphs[i].maLinearPos.X() += nOffset;
}
}
}
else // condense - apply a factor to all glyph positions
{
if (mvGlyphs.size() == 0) return;
Glyphs::iterator iLastGlyph = mvGlyphs.begin() + (mvGlyphs.size() - 1);
// position last glyph using original width
float fXFactor = static_cast<float>(rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth) / static_cast<float>(iLastGlyph->maLinearPos.X());
#ifdef GRLAYOUT_DEBUG
fprintf(grLog(), "Condense by factor %f\n", fXFactor);
#endif
iLastGlyph->maLinearPos.X() = rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth;
Glyphs::iterator iGlyph = mvGlyphs.begin();
while (iGlyph != iLastGlyph)
{
iGlyph->maLinearPos.X() = FRound( fXFactor * iGlyph->maLinearPos.X() );
++iGlyph;
}
for (size_t i = 0; i < mvCharDxs.size(); i++)
{
mvCharDxs[i] = FRound( fXFactor * mvCharDxs[i] );
}
}
mnWidth = rArgs.mnLayoutWidth;
}
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,%d ", (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)? (signed)mvGlyphs.size() : -1;
int nPrevClusterLastChar = -1;
for (size_t i = 0; i < nChars; i++)
{
int nChar2Base = (mvChar2BaseGlyph[i] == -1)? -1 : (int)(mvChar2BaseGlyph[i] & GLYPH_INDEX_MASK);
if ((nChar2Base > -1) && (nChar2Base != nPrevClusterGlyph))
{
assert((nChar2Base > -1) && (nChar2Base < (signed)mvGlyphs.size()));
GlyphItem & gi = mvGlyphs[nChar2Base];
if (!gi.IsClusterStart())
continue;
// find last glyph of this cluster
size_t j = i + 1;
int nLastChar = i;
int nLastGlyph = nChar2Base;
for (; j < nChars; j++)
{
int nChar2BaseJ = (mvChar2BaseGlyph[j] == -1)? -1 : (int)(mvChar2BaseGlyph[j] & GLYPH_INDEX_MASK);
assert((nChar2BaseJ >= -1) && (nChar2BaseJ < (signed)mvGlyphs.size()));
if (nChar2BaseJ != -1 && mvGlyphs[nChar2BaseJ].IsClusterStart())
{
nLastGlyph = nChar2BaseJ + ((bRtl)? +1 : -1);
nLastChar = j - 1;
break;
}
}
if (nLastGlyph < 0)
{
nLastGlyph = nChar2Base;
}
// 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 = nChar2Base;
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%lu last glyph %d/%lu\n", i, nLastGlyph, mvGlyphs.size());
#endif
assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size()));
mvGlyphs[nLastGlyph].mnNewWidth += nDWidth;
if (gi.maGlyphId != GF_DROPPED)
mvGlyphs[nLastGlyph].mnNewWidth += nDWidth;
else
nDGlyphOrigin += nDWidth;
// update glyph positions
if (bRtl)
{
for (int n = nChar2Base; n <= nLastGlyph; n++)
{
assert((n > - 1) && (n < (signed)mvGlyphs.size()));
mvGlyphs[n].maLinearPos.X() += -nDGlyphOrigin + nXOffset;
}
}
else
{
for (int n = nChar2Base; n <= nLastGlyph; n++)
{
assert((n > - 1) && (n < (signed)mvGlyphs.size()));
mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + nXOffset;
}
}
rDeltaWidth[nChar2Base] = nDWidth;
#ifdef GRLAYOUT_DEBUG
fprintf(grLog(),"c%d g%d-%d dW%ld-%ld=%ld dX%ld x%ld\t", (int)i, nChar2Base, nLastGlyph, nNewClusterWidth, nOrigClusterWidth, nDWidth, nDGlyphOrigin, mvGlyphs[nChar2Base].maLinearPos.X());
#endif
nPrevClusterGlyph = nChar2Base;
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 %d(%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).maGlyphId ) )
{
++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 %u\n", nKashidaCount, (*i).maGlyphId);
#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)
{
int nChar2Base = mvChar2BaseGlyph[nCharSlot] & GLYPH_INDEX_MASK;
assert((mvChar2BaseGlyph[nCharSlot] > -1) && (nChar2Base < (signed)mvGlyphs.size()));
GlyphItem gi = mvGlyphs[nChar2Base];
if (gi.maGlyphId == GF_DROPPED)
{
continue;
}
int nCluster = nChar2Base;
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 = nChar2Base;
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,%d-%d\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)->maGlyphId == 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->maGlyphId;
// 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,
GLYPH_INDEX_MASK&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->maGlyphId == 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)->maGlyphId == 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].maGlyphId == 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.maGlyphId = 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->maGlyphId == 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;
}