mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-04 16:15:25 +00:00
1158 lines
41 KiB
C++
1158 lines
41 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (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.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Novell code.
|
|
*
|
|
* The Initial Developer of the Original Code is Novell Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* robert@ocallahan.org
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include "nsTextRunTransformations.h"
|
|
|
|
#include "nsTextTransformer.h"
|
|
#include "nsTextFrameUtils.h"
|
|
#include "gfxSkipChars.h"
|
|
|
|
#include "nsICaseConversion.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsStyleContext.h"
|
|
#include "gfxContext.h"
|
|
|
|
#define SZLIG 0x00DF
|
|
|
|
// ==============================
|
|
// First define some useful textrun wrapper implementations
|
|
// ==============================
|
|
|
|
class TextProviderWrapper : public gfxTextRun::TextProvider {
|
|
public:
|
|
virtual void ForceRememberText() {
|
|
NS_ERROR("We should never be asked to recover text, we already forced the textrun to remember it");
|
|
}
|
|
};
|
|
|
|
class ProviderWrapper : public gfxTextRun::PropertyProvider {
|
|
public:
|
|
ProviderWrapper(PropertyProvider* aProvider)
|
|
: mInner(aProvider) {}
|
|
|
|
virtual void ForceRememberText() {
|
|
NS_ERROR("We should never be asked to recover text, we already forced the textrun to remember it");
|
|
}
|
|
|
|
// Simple forwardings
|
|
virtual gfxFloat GetHyphenWidth()
|
|
{ return mInner->GetHyphenWidth(); }
|
|
|
|
protected:
|
|
PropertyProvider* mInner;
|
|
};
|
|
|
|
class WrapperTextRun : public gfxTextRun {
|
|
public:
|
|
WrapperTextRun(gfxTextRun* aInner, gfxTextRunFactory::Parameters* aParams)
|
|
// WrapperTextRuns always take Unicode
|
|
: gfxTextRun(aParams), mInner(aInner) {}
|
|
|
|
protected:
|
|
nsAutoPtr<gfxTextRun> mInner;
|
|
};
|
|
|
|
/**
|
|
* SZLIG is a problem. When uppercased it turns into "SS", yes, two S's.
|
|
*
|
|
* This adds a special wrinkle because normally DOM text only shrinks when
|
|
* being converted for display. Instead of burdening general DOM<->displaytext
|
|
* string offset conversion with the possibility of SZLIG-driven growth, we put
|
|
* all the pain right here in a special text run that knows where extra characters
|
|
* have been inserted in a string and makes the client believe that they're not
|
|
* there by adjusting character offsets.
|
|
*
|
|
* In general this class could be used to handle text runs that are derived from
|
|
* some base string by inserting characters.
|
|
*/
|
|
class nsGermanTextRun : public WrapperTextRun {
|
|
public:
|
|
/**
|
|
* The gfxSkipCharsBuilder used here represents the characters that should
|
|
* be removed from the tranformed text in aInnerTextRun to obtain the
|
|
* original string.
|
|
*/
|
|
nsGermanTextRun(gfxTextRun* aInnerTextRun, gfxSkipChars* aSkipChars,
|
|
gfxTextRunFactory::Parameters* aParams)
|
|
: WrapperTextRun(aInnerTextRun, aParams)
|
|
{
|
|
mTransform.TakeFrom(aSkipChars);
|
|
}
|
|
|
|
class GermanProviderWrapper : public ProviderWrapper {
|
|
public:
|
|
GermanProviderWrapper(PropertyProvider* aProvider, const nsGermanTextRun& aRun)
|
|
: ProviderWrapper(aProvider), mRun(aRun) {}
|
|
|
|
virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore);
|
|
virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength,
|
|
Spacing* aSpacing);
|
|
|
|
protected:
|
|
const nsGermanTextRun& mRun;
|
|
};
|
|
|
|
// Fairly simple forwarding when just offsets or a provider have to be translated
|
|
virtual PRUint8 GetCharFlags(PRUint32 aOffset)
|
|
{
|
|
TranslateOffset(&aOffset);
|
|
return mInner->GetCharFlags(aOffset);
|
|
}
|
|
virtual PRUint32 GetLength()
|
|
{
|
|
// mTransform's 'original' characters are actually the *expanded* characters
|
|
return mTransform.GetOriginalCharCount();
|
|
}
|
|
virtual void Draw(gfxContext* aContext, gfxPoint aPt,
|
|
PRUint32 aStart, PRUint32 aLength,
|
|
const gfxRect* aDirtyRect,
|
|
PropertyProvider* aBreakProvider,
|
|
gfxFloat* aAdvanceWidth)
|
|
{
|
|
GermanProviderWrapper wrapper(aBreakProvider, *this);
|
|
TranslateSubstring(&aStart, &aLength);
|
|
mInner->Draw(aContext, aPt, aStart, aLength, aDirtyRect, &wrapper, aAdvanceWidth);
|
|
}
|
|
virtual void DrawToPath(gfxContext* aContext, gfxPoint aPt,
|
|
PRUint32 aStart, PRUint32 aLength,
|
|
PropertyProvider* aBreakProvider,
|
|
gfxFloat* aAdvanceWidth)
|
|
{
|
|
GermanProviderWrapper wrapper(aBreakProvider, *this);
|
|
TranslateSubstring(&aStart, &aLength);
|
|
mInner->DrawToPath(aContext, aPt, aStart, aLength, &wrapper, aAdvanceWidth);
|
|
}
|
|
virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aTightBoundingBox,
|
|
PropertyProvider* aBreakProvider)
|
|
{
|
|
GermanProviderWrapper wrapper(aBreakProvider, *this);
|
|
TranslateSubstring(&aStart, &aLength);
|
|
return mInner->MeasureText(aStart, aLength, aTightBoundingBox, &wrapper);
|
|
}
|
|
virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aLineBreakBefore, PRBool aLineBreakAfter,
|
|
TextProvider* aProvider,
|
|
gfxFloat* aAdvanceWidthDelta)
|
|
{
|
|
TextProviderWrapper dummyTextProvider;
|
|
TranslateSubstring(&aStart, &aLength);
|
|
return mInner->SetLineBreaks(aStart, aLength, aLineBreakBefore, aLineBreakAfter,
|
|
&dummyTextProvider, aAdvanceWidthDelta);
|
|
}
|
|
virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
|
|
PropertyProvider* aBreakProvider)
|
|
{
|
|
GermanProviderWrapper wrapper(aBreakProvider, *this);
|
|
TranslateSubstring(&aStart, &aLength);
|
|
return mInner->GetAdvanceWidth(aStart, aLength, &wrapper);
|
|
}
|
|
|
|
// Tricky forwarding where output conversion is required
|
|
virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags);
|
|
virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore);
|
|
virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
|
|
PRBool aBreakBefore, gfxFloat aWidth,
|
|
PropertyProvider* aBreakProvider,
|
|
PRBool aSuppressInitialBreak,
|
|
Metrics* aMetrics, PRBool aTightBoundingBox,
|
|
PRBool* aUsedHyphenation,
|
|
PRUint32* aLastBreak);
|
|
|
|
friend class GermanProviderWrapper;
|
|
|
|
private:
|
|
// Translates an original string offset to an offset in the converted string
|
|
void TranslateOffset(PRUint32* aOffset);
|
|
// Translates original string substring offsets to substring offsets in the
|
|
// converted string
|
|
void TranslateSubstring(PRUint32* aStart, PRUint32* aLength);
|
|
// Translates converted string substring offsets to substring offsets in the
|
|
// original string
|
|
void UntranslateSubstring(PRUint32* aStart, PRUint32* aLength) const;
|
|
|
|
gfxSkipChars mTransform;
|
|
};
|
|
|
|
void
|
|
nsGermanTextRun::TranslateOffset(PRUint32* aOffset)
|
|
{
|
|
gfxSkipCharsIterator iter(mTransform);
|
|
iter.SetSkippedOffset(*aOffset);
|
|
*aOffset = iter.GetOriginalOffset();
|
|
}
|
|
|
|
void
|
|
nsGermanTextRun::TranslateSubstring(PRUint32* aStart, PRUint32* aLength)
|
|
{
|
|
gfxSkipCharsIterator iter(mTransform);
|
|
PRUint32 end = *aStart + *aLength;
|
|
iter.SetSkippedOffset(*aStart);
|
|
*aStart = iter.GetOriginalOffset();
|
|
iter.SetSkippedOffset(end);
|
|
*aLength = iter.GetOriginalOffset() - *aStart;
|
|
}
|
|
|
|
void
|
|
nsGermanTextRun::UntranslateSubstring(PRUint32* aStart, PRUint32* aLength) const
|
|
{
|
|
gfxSkipCharsIterator iter(mTransform);
|
|
PRUint32 end = *aStart + *aLength;
|
|
iter.SetOriginalOffset(*aStart);
|
|
*aStart = iter.GetSkippedOffset();
|
|
iter.SetOriginalOffset(end);
|
|
*aLength = iter.GetSkippedOffset() - *aStart;
|
|
}
|
|
|
|
void
|
|
nsGermanTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength,
|
|
PRUint8* aFlags)
|
|
{
|
|
PRUint32 convertedStart = aStart;
|
|
PRUint32 convertedLength = aLength;
|
|
TranslateSubstring(&convertedStart, &convertedLength);
|
|
|
|
nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
|
|
if (!buffer.AppendElements(convertedLength)) {
|
|
memset(aFlags, 0, aLength);
|
|
return;
|
|
}
|
|
mInner->GetCharFlags(convertedStart, convertedLength, buffer.Elements());
|
|
|
|
gfxSkipCharsIterator iter(mTransform);
|
|
nsSkipCharsRunIterator
|
|
run(iter, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
|
|
// Iterate over the original-string runs. The client simply never sees
|
|
// flags for the extra Ss, since they don't exist as far as the client knows.
|
|
while (run.NextRun()) {
|
|
memcpy(aFlags + run.GetSkippedOffset() - aStart,
|
|
buffer.Elements() + run.GetOriginalOffset() - convertedStart,
|
|
run.GetRunLength());
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
nsGermanTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore)
|
|
{
|
|
PRUint32 convertedStart = aStart;
|
|
PRUint32 convertedLength = aLength;
|
|
TranslateSubstring(&convertedStart, &convertedLength);
|
|
|
|
nsAutoTArray<PRPackedBool,BIG_TEXT_NODE_SIZE> buffer;
|
|
if (!buffer.AppendElements(convertedLength))
|
|
return PR_TRUE;
|
|
|
|
gfxSkipCharsIterator iter(mTransform);
|
|
nsSkipCharsRunIterator
|
|
run(iter, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
|
|
while (run.NextRun()) {
|
|
memcpy(buffer.Elements() + run.GetOriginalOffset() - convertedStart,
|
|
aBreakBefore + run.GetSkippedOffset() - aStart,
|
|
run.GetRunLength());
|
|
}
|
|
|
|
return mInner->SetPotentialLineBreaks(convertedStart, convertedLength, buffer.Elements());
|
|
}
|
|
|
|
PRUint32
|
|
nsGermanTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
|
|
PRBool aLineBreakBefore, gfxFloat aWidth,
|
|
PropertyProvider* aBreakProvider,
|
|
PRBool aSuppressInitialBreak,
|
|
Metrics* aMetrics, PRBool aTightBoundingBox,
|
|
PRBool* aUsedHyphenation,
|
|
PRUint32* aLastBreak)
|
|
{
|
|
GermanProviderWrapper wrapper(aBreakProvider, *this);
|
|
PRUint32 convertedStart = aStart;
|
|
PRUint32 convertedLength = aMaxLength;
|
|
TranslateSubstring(&convertedStart, &convertedLength);
|
|
PRUint32 lastBreak;
|
|
PRUint32 result =
|
|
mInner->BreakAndMeasureText(convertedStart, convertedLength, aLineBreakBefore,
|
|
aWidth, &wrapper, aSuppressInitialBreak, aMetrics, aTightBoundingBox,
|
|
aUsedHyphenation, &lastBreak);
|
|
gfxSkipCharsIterator iter(mTransform);
|
|
iter.SetOriginalOffset(convertedStart + lastBreak);
|
|
*aLastBreak = iter.GetSkippedOffset() - aStart;
|
|
iter.SetOriginalOffset(convertedStart + result);
|
|
return iter.GetSkippedOffset() - aStart;
|
|
}
|
|
|
|
void
|
|
nsGermanTextRun::GermanProviderWrapper::GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore)
|
|
{
|
|
PRUint32 unconvertedStart = aStart;
|
|
PRUint32 unconvertedLength = aLength;
|
|
mRun.UntranslateSubstring(&unconvertedStart, &unconvertedLength);
|
|
|
|
// I'm being a bit clever here and reusing aBreakBefore since it's at least
|
|
// as big as the temporary buffer we need. We put our temporary buffer
|
|
// at the end of aBreakBefore so that when we start filling aBreakBefore
|
|
// below, we don't overwrite data we need --- until the very last memmove,
|
|
// which will be in-place.
|
|
NS_ASSERTION(aLength >= unconvertedLength,
|
|
"We only handle *growing* strings here");
|
|
PRPackedBool* buffer = aBreakBefore + aLength - unconvertedLength;
|
|
mInner->GetHyphenationBreaks(unconvertedStart, unconvertedLength, buffer);
|
|
|
|
gfxSkipCharsIterator iter(mRun.mTransform, aStart);
|
|
nsSkipCharsRunIterator
|
|
run(iter, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
|
|
run.SetVisitSkipped();
|
|
while (run.NextRun()) {
|
|
if (run.IsSkipped()) {
|
|
// Disallow hyphenation inside the SS
|
|
memset(aBreakBefore + run.GetOriginalOffset() - aStart, PR_FALSE,
|
|
run.GetRunLength());
|
|
} else {
|
|
memmove(aBreakBefore + run.GetOriginalOffset() - aStart,
|
|
buffer + run.GetSkippedOffset() - unconvertedStart,
|
|
run.GetRunLength());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsGermanTextRun::GermanProviderWrapper::GetSpacing(PRUint32 aStart, PRUint32 aLength,
|
|
Spacing* aSpacing)
|
|
{
|
|
PRUint32 unconvertedStart = aStart;
|
|
PRUint32 unconvertedLength = aLength;
|
|
mRun.UntranslateSubstring(&unconvertedStart, &unconvertedLength);
|
|
|
|
// I'm being a bit clever here and reusing aSpacing since it's at least
|
|
// as big as the temporary buffer we need. We put our temporary buffer
|
|
// at the end of aSpacing so that when we start filling aSpacing
|
|
// below, we don't overwrite data we need --- until the very last memmove,
|
|
// which will be in-place.
|
|
NS_ASSERTION(aLength >= unconvertedLength,
|
|
"We only handle *growing* strings here");
|
|
Spacing* buffer = aSpacing + aLength - unconvertedLength;
|
|
mInner->GetSpacing(unconvertedStart, unconvertedLength, buffer);
|
|
|
|
gfxSkipCharsIterator iter(mRun.mTransform, aStart);
|
|
nsSkipCharsRunIterator
|
|
run(iter, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
|
|
run.SetVisitSkipped();
|
|
// The following code relies on the fact that the only spacing that can be
|
|
// added after a SZLIG is letter-spacing, and the same space should be added
|
|
// after both S's as was addeded after the SZLIG.
|
|
gfxFloat lastSpaceAfter = 0;
|
|
while (run.NextRun()) {
|
|
if (run.IsSkipped()) {
|
|
PRUint32 offset = run.GetOriginalOffset() - aStart;
|
|
PRInt32 i;
|
|
for (i = 0; i < run.GetRunLength(); ++i) {
|
|
aSpacing[offset + i].mBefore = 0;
|
|
aSpacing[offset + i].mAfter = lastSpaceAfter;
|
|
}
|
|
} else {
|
|
memcpy(aSpacing + run.GetOriginalOffset() - aStart,
|
|
buffer + run.GetSkippedOffset() - unconvertedStart,
|
|
run.GetRunLength()*sizeof(Spacing));
|
|
lastSpaceAfter =
|
|
aSpacing[run.GetOriginalOffset() - aStart + run.GetRunLength() - 1].mAfter;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This textrun class encapsulates the concatenation of a list of text runs. This
|
|
* allows text with runs of different styles (e.g., fonts) to be presented
|
|
* as a single text run.
|
|
*/
|
|
class nsMultiTextRun : public WrapperTextRun {
|
|
public:
|
|
nsMultiTextRun(gfxTextRun* aBaseTextRun, gfxTextRunFactory::Parameters* aParams)
|
|
: WrapperTextRun(aBaseTextRun, aParams) {}
|
|
|
|
struct ChildRun {
|
|
void Set(gfxTextRun* aChild, PRUint32 aOffset, PRUint32 aLength) {
|
|
mChild = aChild;
|
|
mOffset = aOffset;
|
|
mLength = aLength;
|
|
}
|
|
|
|
nsAutoPtr<gfxTextRun> mChild;
|
|
PRUint32 mOffset;
|
|
PRUint32 mLength;
|
|
};
|
|
|
|
// Creates a textrun and adds it to our list.
|
|
nsresult AddChildRun(gfxTextRunFactory* aFactory, const PRUnichar* aText, PRUint32 aLength,
|
|
gfxTextRunFactory::Parameters* aParams);
|
|
// Clears the list of child runs so that it can be regenerated.
|
|
void ClearChildren() { mRuns.Clear(); }
|
|
nsresult AddChildRun(gfxTextRun* aChild, PRUint32 aOffset, PRUint32 aLength) {
|
|
ChildRun* run = mRuns.AppendElement();
|
|
if (!run)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
run->Set(aChild, aOffset, aLength);
|
|
return NS_OK;
|
|
}
|
|
|
|
class MultiProviderWrapper : public ProviderWrapper {
|
|
public:
|
|
MultiProviderWrapper(PropertyProvider* aProvider, const nsMultiTextRun& aRun)
|
|
: ProviderWrapper(aProvider), mRun(aRun) {}
|
|
|
|
void SetChildRun(ChildRun* aRun) { mChild = aRun; }
|
|
|
|
virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore)
|
|
{
|
|
mInner->GetHyphenationBreaks(mChild->mOffset + aStart, aLength, aBreakBefore);
|
|
}
|
|
|
|
virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength,
|
|
Spacing* aSpacing)
|
|
{
|
|
mInner->GetSpacing(mChild->mOffset + aStart, aLength, aSpacing);
|
|
}
|
|
|
|
protected:
|
|
const nsMultiTextRun& mRun;
|
|
ChildRun* mChild;
|
|
};
|
|
|
|
virtual PRUint8 GetCharFlags(PRUint32 aOffset);
|
|
virtual PRUint32 GetLength();
|
|
|
|
virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore);
|
|
virtual void Draw(gfxContext* aContext, gfxPoint aPt,
|
|
PRUint32 aStart, PRUint32 aLength,
|
|
const gfxRect* aDirtyRect,
|
|
PropertyProvider* aBreakProvider,
|
|
gfxFloat* aAdvanceWidth);
|
|
virtual void DrawToPath(gfxContext* aContext, gfxPoint aPt,
|
|
PRUint32 aStart, PRUint32 aLength,
|
|
PropertyProvider* aBreakProvider,
|
|
gfxFloat* aAdvanceWidth);
|
|
virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aTightBoundingBox,
|
|
PropertyProvider* aBreakProvider);
|
|
virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aLineBreakBefore, PRBool aLineBreakAfter,
|
|
TextProvider* aProvider,
|
|
gfxFloat* aAdvanceWidthDelta);
|
|
virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
|
|
PropertyProvider* aBreakProvider);
|
|
virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags);
|
|
virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
|
|
PRBool aLineBreakBefore, gfxFloat aWidth,
|
|
PropertyProvider* aBreakProvider,
|
|
PRBool aSuppressInitialBreak,
|
|
Metrics* aMetrics, PRBool aTightBoundingBox,
|
|
PRBool* aUsedHyphenation,
|
|
PRUint32* aLastBreak);
|
|
|
|
friend class MultiProviderWrapper;
|
|
|
|
private:
|
|
PRUint32 FindChildIndexContaining(PRUint32 aOffset);
|
|
ChildRun* FindChildContaining(PRUint32 aOffset) {
|
|
return &mRuns[FindChildIndexContaining(aOffset)];
|
|
}
|
|
|
|
class ChildIterator {
|
|
public:
|
|
ChildIterator(nsMultiTextRun& aMulti, PRUint32 aOffset, PRUint32 aLength,
|
|
MultiProviderWrapper* aWrapper = nsnull);
|
|
PRBool NextChild();
|
|
ChildRun* GetChild() { return mChild; }
|
|
gfxTextRun* GetTextRun() { return mChild->mChild; }
|
|
PRUint32 GetChildOffset() { return mChildOffset; }
|
|
PRUint32 GetChildLength() { return mChildLength; }
|
|
PRUint32 GetOffset() { return mOffset; }
|
|
private:
|
|
nsMultiTextRun& mMulti;
|
|
MultiProviderWrapper* mWrapper;
|
|
ChildRun* mChild;
|
|
PRUint32 mIndex;
|
|
PRUint32 mOffset;
|
|
PRUint32 mRemaining;
|
|
PRUint32 mChildOffset;
|
|
PRUint32 mChildLength;
|
|
};
|
|
friend class ChildIterator;
|
|
|
|
void CombineMetrics(gfxTextRun::Metrics* aRunning, const gfxTextRun::Metrics& aMetrics);
|
|
|
|
nsTArray<ChildRun> mRuns;
|
|
};
|
|
|
|
nsMultiTextRun::ChildIterator::ChildIterator(nsMultiTextRun& aMulti,
|
|
PRUint32 aOffset, PRUint32 aLength,
|
|
MultiProviderWrapper* aWrapper)
|
|
: mMulti(aMulti), mWrapper(aWrapper), mRemaining(aLength)
|
|
{
|
|
mIndex = mMulti.FindChildIndexContaining(aOffset);
|
|
}
|
|
|
|
PRBool
|
|
nsMultiTextRun::ChildIterator::NextChild()
|
|
{
|
|
if (mRemaining <= 0)
|
|
return PR_FALSE;
|
|
NS_ASSERTION(mIndex < mMulti.mRuns.Length(), "substring overrun");
|
|
mChild = &mMulti.mRuns[mIndex];
|
|
++mIndex;
|
|
|
|
mChildOffset = PR_MAX(mChild->mOffset, mOffset) - mChild->mOffset;
|
|
mChildLength = PR_MIN(mChild->mLength, mChildOffset + mRemaining);
|
|
|
|
mOffset += mChildLength;
|
|
mRemaining -= mChildLength;
|
|
if (mWrapper) {
|
|
mWrapper->SetChildRun(mChild);
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRUint32
|
|
nsMultiTextRun::GetLength()
|
|
{
|
|
if (mRuns.Length() == 0)
|
|
return 0;
|
|
ChildRun* last = &mRuns[mRuns.Length() - 1];
|
|
return last->mOffset + last->mLength;
|
|
}
|
|
|
|
nsresult
|
|
nsMultiTextRun::AddChildRun(gfxTextRunFactory* aFactory, const PRUnichar* aText,
|
|
PRUint32 aLength,
|
|
gfxTextRunFactory::Parameters* aParams)
|
|
{
|
|
PRUint32 offset = GetLength();
|
|
|
|
// Update line break list in aParams for the creation of the child. We maintain
|
|
// the invariant that aParams->mInitialBreaks points to the breaks for the
|
|
// next child to be added and subsequent children.
|
|
PRUint32 breakCount = 0;
|
|
PRUint32 currentBreakCount = aParams->mInitialBreakCount;
|
|
while (breakCount < currentBreakCount &&
|
|
aParams->mInitialBreaks[breakCount] <= offset + aLength) {
|
|
// Get the break position into child-textrun coordinates
|
|
aParams->mInitialBreaks[breakCount] -= offset;
|
|
++breakCount;
|
|
}
|
|
aParams->mInitialBreakCount = breakCount;
|
|
|
|
gfxTextRun* run = aFactory->MakeTextRun(aText, aLength, aParams);
|
|
if (!run)
|
|
return PR_FALSE;
|
|
// force child to remember its text so we don't have to support recovering it
|
|
run->RememberText(aText, aLength);
|
|
|
|
// Now update line break list in aParams for the next iteration
|
|
// If there's a break at the end of this child, leave it in the list so
|
|
// that the next child will see it at its start.
|
|
if (breakCount > 0 && aParams->mInitialBreaks[breakCount - 1] == aLength) {
|
|
// restore to global-textrun coordinates. The rest of the breaks are
|
|
// skipped, never to be used again, so we don't need to restore them.
|
|
aParams->mInitialBreaks[breakCount - 1] = offset + aLength;
|
|
--breakCount;
|
|
}
|
|
aParams->mInitialBreaks += breakCount;
|
|
aParams->mInitialBreakCount -= breakCount;
|
|
|
|
return AddChildRun(run, offset, aLength);
|
|
}
|
|
|
|
PRUint32
|
|
nsMultiTextRun::FindChildIndexContaining(PRUint32 aOffset)
|
|
{
|
|
PRUint32 start = 0;
|
|
PRUint32 end = mRuns.Length();
|
|
while (end - start > 1) {
|
|
PRUint32 mid = (start + end)/2;
|
|
if (mRuns[mid].mOffset <= aOffset) {
|
|
start = mid;
|
|
} else {
|
|
end = mid;
|
|
}
|
|
}
|
|
NS_ASSERTION(mRuns[start].mOffset + mRuns[start].mLength > aOffset,
|
|
"Substring offset out of range!");
|
|
return start;
|
|
}
|
|
|
|
void
|
|
nsMultiTextRun::CombineMetrics(gfxTextRun::Metrics* aRunning,
|
|
const gfxTextRun::Metrics& aMetrics)
|
|
{
|
|
if (IsRightToLeft()) {
|
|
// 'aRunning' is to the right of 'aMetrics'
|
|
gfxTextRun::Metrics tmp = aMetrics;
|
|
tmp.CombineWith(*aRunning);
|
|
*aRunning = tmp;
|
|
} else {
|
|
// 'aRunning' is to the left of 'aMetrics'
|
|
aRunning->CombineWith(aMetrics);
|
|
}
|
|
}
|
|
|
|
PRUint8
|
|
nsMultiTextRun::GetCharFlags(PRUint32 aOffset)
|
|
{
|
|
ChildRun* child = FindChildContaining(aOffset);
|
|
return mInner->GetCharFlags(aOffset - child->mOffset);
|
|
}
|
|
|
|
void
|
|
nsMultiTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags)
|
|
{
|
|
ChildIterator iter(*this, aStart, aLength);
|
|
while (iter.NextChild()) {
|
|
iter.GetTextRun()->GetCharFlags(iter.GetChildOffset(), iter.GetChildLength(),
|
|
aFlags);
|
|
aFlags += iter.GetChildLength();
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
nsMultiTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore)
|
|
{
|
|
ChildIterator iter(*this, aStart, aLength);
|
|
PRBool changed = PR_FALSE;
|
|
while (iter.NextChild()) {
|
|
changed |= iter.GetTextRun()->SetPotentialLineBreaks(iter.GetChildOffset(), iter.GetChildLength(),
|
|
aBreakBefore);
|
|
aBreakBefore += iter.GetChildLength();
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
void
|
|
nsMultiTextRun::Draw(gfxContext* aContext, gfxPoint aPt,
|
|
PRUint32 aStart, PRUint32 aLength,
|
|
const gfxRect* aDirtyRect, PropertyProvider* aBreakProvider,
|
|
gfxFloat* aAdvanceWidth)
|
|
{
|
|
MultiProviderWrapper wrapper(aBreakProvider, *this);
|
|
ChildIterator iter(*this, aStart, aLength, &wrapper);
|
|
gfxFloat totalAdvance = 0;
|
|
while (iter.NextChild()) {
|
|
gfxFloat advance;
|
|
gfxPoint pt = gfxPoint(aPt.x + iter.GetTextRun()->GetDirection()*totalAdvance, aPt.y);
|
|
iter.GetTextRun()->Draw(aContext, pt,
|
|
iter.GetChildOffset(), iter.GetChildLength(),
|
|
aDirtyRect, &wrapper, &advance);
|
|
totalAdvance += advance;
|
|
}
|
|
if (aAdvanceWidth) {
|
|
*aAdvanceWidth = totalAdvance;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsMultiTextRun::DrawToPath(gfxContext* aContext, gfxPoint aPt,
|
|
PRUint32 aStart, PRUint32 aLength,
|
|
PropertyProvider* aBreakProvider,
|
|
gfxFloat* aAdvanceWidth)
|
|
{
|
|
MultiProviderWrapper wrapper(aBreakProvider, *this);
|
|
ChildIterator iter(*this, aStart, aLength, &wrapper);
|
|
gfxFloat totalAdvance = 0;
|
|
while (iter.NextChild()) {
|
|
gfxFloat advance;
|
|
gfxPoint pt = gfxPoint(aPt.x + iter.GetTextRun()->GetDirection()*totalAdvance, aPt.y);
|
|
iter.GetTextRun()->DrawToPath(aContext, pt,
|
|
iter.GetChildOffset(), iter.GetChildLength(),
|
|
&wrapper, &advance);
|
|
totalAdvance += advance;
|
|
}
|
|
if (aAdvanceWidth) {
|
|
*aAdvanceWidth = totalAdvance;
|
|
}
|
|
}
|
|
|
|
gfxTextRun::Metrics
|
|
nsMultiTextRun::MeasureText(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aTightBoundingBox,
|
|
PropertyProvider* aBreakProvider)
|
|
{
|
|
MultiProviderWrapper wrapper(aBreakProvider, *this);
|
|
ChildIterator iter(*this, aStart, aLength, &wrapper);
|
|
gfxTextRun::Metrics result;
|
|
while (iter.NextChild()) {
|
|
gfxTextRun::Metrics metrics = iter.GetTextRun()->MeasureText(
|
|
iter.GetChildOffset(), iter.GetChildLength(), aTightBoundingBox, &wrapper);
|
|
CombineMetrics(&result, metrics);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
PRUint32
|
|
nsMultiTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
|
|
PRBool aBreakBefore, gfxFloat aWidth,
|
|
PropertyProvider* aBreakProvider,
|
|
PRBool aSuppressInitialBreak,
|
|
Metrics* aMetrics, PRBool aTightBoundingBox,
|
|
PRBool* aUsedHyphenation,
|
|
PRUint32* aLastBreak)
|
|
{
|
|
MultiProviderWrapper wrapper(aBreakProvider, *this);
|
|
ChildIterator iter(*this, aStart, aMaxLength, &wrapper);
|
|
PRUint32 fitSoFar = 0;
|
|
gfxTextRun::Metrics totalMetrics;
|
|
PRUint32 lastBreak = aMaxLength;
|
|
|
|
while (iter.NextChild()) {
|
|
gfxTextRun::Metrics metrics;
|
|
PRUint32 childLastBreak;
|
|
PRUint32 fits = iter.GetTextRun()->BreakAndMeasureText(
|
|
iter.GetChildOffset(), iter.GetChildLength(), aBreakBefore,
|
|
aWidth - aMetrics->mAdvanceWidth, &wrapper, aSuppressInitialBreak,
|
|
&metrics, aTightBoundingBox, aUsedHyphenation, &childLastBreak);
|
|
|
|
CombineMetrics(&totalMetrics, metrics);
|
|
if (childLastBreak < iter.GetChildLength()) {
|
|
lastBreak = fitSoFar + childLastBreak;
|
|
}
|
|
fitSoFar += fits;
|
|
|
|
if (fits < iter.GetChildLength())
|
|
break;
|
|
aBreakBefore = PR_FALSE;
|
|
aSuppressInitialBreak = PR_FALSE;
|
|
}
|
|
|
|
if (aMetrics) {
|
|
*aMetrics = totalMetrics;
|
|
}
|
|
if (aLastBreak) {
|
|
*aLastBreak = lastBreak;
|
|
}
|
|
return fitSoFar;
|
|
}
|
|
|
|
void
|
|
nsMultiTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aLineBreakBefore, PRBool aLineBreakAfter,
|
|
TextProvider* aProvider,
|
|
gfxFloat* aAdvanceWidthDelta)
|
|
{
|
|
TextProviderWrapper dummyTextProvider;
|
|
ChildIterator iter(*this, aStart, aLength);
|
|
gfxFloat delta = 0;
|
|
while (iter.NextChild()) {
|
|
gfxFloat thisDelta;
|
|
iter.GetTextRun()->SetLineBreaks(
|
|
iter.GetChildOffset(), iter.GetChildLength(),
|
|
aLineBreakBefore && iter.GetOffset() == aStart,
|
|
aLineBreakAfter && iter.GetOffset() + iter.GetChildLength() == aStart + aLength,
|
|
&dummyTextProvider, &thisDelta);
|
|
delta += thisDelta;
|
|
}
|
|
*aAdvanceWidthDelta = delta;
|
|
}
|
|
|
|
gfxFloat
|
|
nsMultiTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
|
|
PropertyProvider* aBreakProvider)
|
|
{
|
|
MultiProviderWrapper wrapper(aBreakProvider, *this);
|
|
ChildIterator iter(*this, aStart, aLength, &wrapper);
|
|
gfxFloat result;
|
|
while (iter.NextChild()) {
|
|
result += iter.GetTextRun()->GetAdvanceWidth(
|
|
iter.GetChildOffset(), iter.GetChildLength(), &wrapper);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ==============================
|
|
// Now define factories that use the above textruns
|
|
// ==============================
|
|
|
|
static gfxTextRunFactory::Parameters
|
|
GetParametersForInner(const gfxTextRunFactory::Parameters& aParams,
|
|
gfxSkipChars* aDummy)
|
|
{
|
|
gfxTextRunFactory::Parameters params =
|
|
{ aParams.mContext, nsnull, aParams.mLangGroup, aDummy,
|
|
aParams.mInitialBreaks, aParams.mInitialBreakCount,
|
|
aParams.mAppUnitsPerDevUnit, aParams.mFlags };
|
|
return params;
|
|
}
|
|
|
|
gfxTextRun*
|
|
nsTransformingTextRunFactory::MakeTextRun(const PRUint8* aString, PRUint32 aLength,
|
|
gfxTextRunFactory::Parameters* aParams)
|
|
{
|
|
// We'll only have a Unicode code path to minimize the amount of code needed
|
|
// for this rarely used feature
|
|
NS_ConvertASCIItoUTF16 unicodeString(NS_REINTERPRET_CAST(const char*, aString), aLength);
|
|
Parameters params = *aParams;
|
|
params.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT;
|
|
return MakeTextRun(unicodeString.get(), aLength, ¶ms);
|
|
}
|
|
|
|
/**
|
|
* For smallcaps we divide the text into runs of lowercase and non-lowercase
|
|
* clusters and build a nsMultiTextRun for the separate runs. For the lowercase
|
|
* runs we use nsCaseTransformTextRunFactory to convert to uppercase.
|
|
*/
|
|
gfxTextRun*
|
|
nsFontVariantTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
|
|
gfxTextRunFactory::Parameters* aParams)
|
|
{
|
|
nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
|
|
if (!converter || !mStyles)
|
|
return nsnull;
|
|
|
|
gfxFontStyle fontStyle = *mFontGroup->GetStyle();
|
|
fontStyle.size *= 0.8;
|
|
nsRefPtr<gfxFontGroup> smallFont = mFontGroup->Copy(&fontStyle);
|
|
if (!smallFont)
|
|
return nsnull;
|
|
|
|
gfxSkipChars dummy;
|
|
gfxTextRunFactory::Parameters innerParams = GetParametersForInner(*aParams, &dummy);
|
|
gfxTextRun* inner = mFontGroup->MakeTextRun(aString, aLength, &innerParams);
|
|
if (!inner)
|
|
return nsnull;
|
|
// force the inner to remember the text, so we don't have to worry about
|
|
// reconstructing it later.
|
|
inner->RememberText(aString, aLength);
|
|
|
|
nsAutoPtr<nsMultiTextRun> textRun;
|
|
textRun = new nsMultiTextRun(inner, aParams);
|
|
if (!textRun)
|
|
return nsnull;
|
|
|
|
nsRefPtr<nsCaseTransformTextRunFactory> uppercaseFactory =
|
|
new nsCaseTransformTextRunFactory(smallFont, nsnull, PR_TRUE);
|
|
if (!uppercaseFactory)
|
|
return nsnull;
|
|
|
|
PRUint32 i;
|
|
PRUint32 runStart = 0;
|
|
PRPackedBool runIsLowercase = PR_FALSE;
|
|
for (i = 0; i <= aLength; ++i) {
|
|
PRBool isLowercase = PR_FALSE;
|
|
if (i < aLength) {
|
|
// Characters that aren't the start of a cluster are ignored here. They
|
|
// get added to whatever lowercase/non-lowercase run we're in.
|
|
if (!(inner->GetCharFlags(i) & gfxTextRun::CLUSTER_START))
|
|
continue;
|
|
|
|
if (mStyles[i]->GetStyleFont()->mFont.variant == NS_STYLE_FONT_VARIANT_SMALL_CAPS) {
|
|
PRUnichar ch = aString[i];
|
|
PRUnichar ch2;
|
|
converter->ToUpper(ch, &ch2);
|
|
isLowercase = ch != ch2;
|
|
} else {
|
|
// Don't transform the character! I.e., pretend that it's not lowercase
|
|
}
|
|
}
|
|
|
|
if ((i == aLength || runIsLowercase != isLowercase) && runStart < i) {
|
|
gfxTextRunFactory* factory;
|
|
if (runIsLowercase) {
|
|
factory = uppercaseFactory;
|
|
if (mInnerTransformingTextRunFactory) {
|
|
mInnerTransformingTextRunFactory->SetStyles(mStyles + runStart);
|
|
}
|
|
} else {
|
|
factory = mFontGroup;
|
|
}
|
|
nsresult rv = textRun->AddChildRun(factory, aString + runStart, i - runStart, &innerParams);
|
|
if (NS_FAILED(rv))
|
|
return nsnull;
|
|
runStart = i;
|
|
runIsLowercase = isLowercase;
|
|
}
|
|
}
|
|
|
|
return textRun.forget();
|
|
}
|
|
|
|
class nsCaseTransformingTextRun : public nsMultiTextRun {
|
|
public:
|
|
nsCaseTransformingTextRun(gfxTextRun* aBaseTextRun,
|
|
gfxTextRunFactory::Parameters* aParams,
|
|
nsCaseTransformTextRunFactory* aFactory,
|
|
const PRUnichar* aString, PRUint32 aLength)
|
|
: nsMultiTextRun(aBaseTextRun, aParams), mString(aString, aLength),
|
|
mFactory(aFactory), mRefContext(aParams->mContext),
|
|
mLangGroup(aParams->mLangGroup),
|
|
mAppUnitsPerDevUnit(aParams->mAppUnitsPerDevUnit),
|
|
mFlags(aParams->mFlags)
|
|
{
|
|
PRUint32 i;
|
|
for (i = 0; i < aLength; ++i) {
|
|
mLineBreakOpportunities.AppendElement(PR_FALSE);
|
|
if (!aFactory->IsAllUppercase()) {
|
|
mStyles.AppendElement(aFactory->GetStyles()[i]);
|
|
}
|
|
}
|
|
for (i = 0; i < aParams->mInitialBreakCount; ++i) {
|
|
mLineBreaks.AppendElement(aParams->mInitialBreaks[i]);
|
|
}
|
|
}
|
|
|
|
virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore);
|
|
virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aLineBreakBefore, PRBool aLineBreakAfter,
|
|
TextProvider* aProvider,
|
|
gfxFloat* aAdvanceWidthDelta);
|
|
nsresult Build();
|
|
|
|
private:
|
|
nsString mString;
|
|
nsRefPtr<nsCaseTransformTextRunFactory> mFactory;
|
|
nsRefPtr<gfxContext> mRefContext;
|
|
nsCOMPtr<nsIAtom> mLangGroup;
|
|
nsTArray<PRUint32> mLineBreaks;
|
|
nsTArray<PRPackedBool> mLineBreakOpportunities;
|
|
nsTArray<nsRefPtr<nsStyleContext> > mStyles;
|
|
gfxFloat mAppUnitsPerDevUnit;
|
|
PRUint32 mFlags;
|
|
};
|
|
|
|
nsresult
|
|
nsCaseTransformingTextRun::Build()
|
|
{
|
|
nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
|
|
if (!converter)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
nsAutoString convertedString;
|
|
PRUint32 i;
|
|
// skipBuilder represents a translation from the *transformed* string to the
|
|
// *original* string, because in this case, that's the way deletions go
|
|
gfxSkipCharsBuilder skipBuilder;
|
|
nsAutoTArray<nsStyleContext*,1000> styles;
|
|
|
|
for (i = 0; i < mString.Length(); ++i) {
|
|
PRUnichar ch = mString.CharAt(i);
|
|
|
|
// We're definitely going to keep at least one char in the hypothetical
|
|
// "transformation" from original to transformed string
|
|
skipBuilder.KeepChar();
|
|
|
|
PRUint8 style = mFactory->IsAllUppercase() ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
|
|
: mStyles[i]->GetStyleText()->mTextTransform;
|
|
|
|
switch (style) {
|
|
case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
|
|
converter->ToLower(ch, &ch);
|
|
break;
|
|
case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
|
|
if (ch == SZLIG) {
|
|
convertedString.Append('S');
|
|
skipBuilder.SkipChar();
|
|
ch = 'S';
|
|
} else {
|
|
converter->ToUpper(ch, &ch);
|
|
}
|
|
break;
|
|
case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
|
|
if (mLineBreakOpportunities[i]) {
|
|
if (ch == SZLIG) {
|
|
styles.AppendElement(mStyles[i]);
|
|
convertedString.Append('S');
|
|
skipBuilder.SkipChar();
|
|
ch = 'S';
|
|
} else {
|
|
converter->ToTitle(ch, &ch);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
styles.AppendElement(mStyles[i]);
|
|
convertedString.Append(ch);
|
|
}
|
|
|
|
PRBool allCharsKept = skipBuilder.GetAllCharsKept();
|
|
gfxSkipChars skipChars;
|
|
skipChars.TakeFrom(&skipBuilder);
|
|
|
|
// Convert line break offsets
|
|
nsAutoTArray<PRUint32,50> lineBreaks;
|
|
if (!lineBreaks.AppendElements(mLineBreaks.Length()))
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
gfxSkipCharsIterator iter(skipChars);
|
|
for (i = 0; i < mLineBreaks.Length(); ++i) {
|
|
lineBreaks[i] = iter.ConvertSkippedToOriginal(mLineBreaks[i]);
|
|
}
|
|
|
|
// Create a textrun for the transformed string
|
|
gfxSkipChars dummy;
|
|
gfxTextRunFactory::Parameters innerParams =
|
|
{ mRefContext, nsnull, mLangGroup, &dummy, mLineBreaks.Elements(),
|
|
mLineBreaks.Length(), PRUint32(mAppUnitsPerDevUnit),
|
|
mFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT };
|
|
|
|
if (mFactory->GetInnerTransformingTextRunFactory()) {
|
|
mFactory->GetInnerTransformingTextRunFactory()->SetStyles(styles.Elements());
|
|
}
|
|
|
|
nsAutoPtr<gfxTextRun> innerTextRun;
|
|
innerTextRun =
|
|
mFactory->GetInnerTextRunFactory()->MakeTextRun(convertedString.get(), convertedString.Length(),
|
|
&innerParams);
|
|
if (!innerTextRun)
|
|
return NS_ERROR_FAILURE;
|
|
// Make sure the inner textrun never calls ForceRememberText, because we don't
|
|
// implement it
|
|
innerTextRun->RememberText(convertedString.get(), convertedString.Length());
|
|
|
|
if (!allCharsKept) {
|
|
// We need to adjust offsets because characters were inserted.
|
|
gfxTextRun* wrapper = new nsGermanTextRun(innerTextRun, &skipChars, &innerParams);
|
|
if (!wrapper)
|
|
return NS_ERROR_FAILURE;
|
|
innerTextRun.forget();
|
|
innerTextRun = wrapper;
|
|
}
|
|
|
|
ClearChildren();
|
|
nsresult rv = AddChildRun(innerTextRun, 0, convertedString.Length());
|
|
if (NS_SUCCEEDED(rv)) {
|
|
innerTextRun.forget();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
PRBool
|
|
nsCaseTransformingTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRPackedBool* aBreakBefore)
|
|
{
|
|
PRUint32 i;
|
|
PRBool changed = PR_FALSE;
|
|
PRBool changedStyle = PR_FALSE;
|
|
for (i = 0; i < aLength; ++i) {
|
|
if (mLineBreakOpportunities[aStart + i] != aBreakBefore[i]) {
|
|
changed = PR_TRUE;
|
|
if (mStyles[i]->GetStyleText()->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
|
|
changedStyle = PR_TRUE;
|
|
}
|
|
mLineBreakOpportunities[aStart + i] = aBreakBefore[i];
|
|
}
|
|
}
|
|
if (!changedStyle)
|
|
return nsMultiTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore);
|
|
|
|
Build();
|
|
return changed;
|
|
}
|
|
|
|
void
|
|
nsCaseTransformingTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
|
|
PRBool aLineBreakBefore, PRBool aLineBreakAfter,
|
|
TextProvider* aProvider,
|
|
gfxFloat* aAdvanceWidthDelta)
|
|
{
|
|
nsTArray<PRUint32> newBreaks;
|
|
for (PRUint32 i = 0; i < mLineBreaks.Length(); ++i) {
|
|
PRUint32 b = mLineBreaks[i];
|
|
if (b == aStart && !aLineBreakBefore)
|
|
continue;
|
|
if (b > aStart && aLineBreakBefore) {
|
|
newBreaks.AppendElement(aStart);
|
|
aLineBreakBefore = PR_FALSE;
|
|
}
|
|
if (b == aStart + aLength && !aLineBreakAfter)
|
|
continue;
|
|
if (b > aStart + aLength && aLineBreakAfter) {
|
|
newBreaks.AppendElement(aStart + aLength);
|
|
aLineBreakAfter = PR_FALSE;
|
|
}
|
|
newBreaks.AppendElement(b);
|
|
}
|
|
mLineBreaks.SwapElements(newBreaks);
|
|
nsMultiTextRun::SetLineBreaks(aStart, aLength, aLineBreakBefore, aLineBreakAfter,
|
|
aProvider, aAdvanceWidthDelta);
|
|
}
|
|
|
|
/**
|
|
* When doing case conversion we first transform the string. If the string
|
|
* ends up with no extra characters inserted, we can just create a textrun
|
|
* from the transformed string and hand it back to the caller, because string
|
|
* offsets are the same. If extra characters are inserted due to
|
|
* uppercased SZLIG, we have to create a special text run, nsGermanTextRun,
|
|
* which adjusts character offsets for us.
|
|
*/
|
|
gfxTextRun*
|
|
nsCaseTransformTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
|
|
gfxTextRunFactory::Parameters* aParams)
|
|
{
|
|
// Create a textrun for the transformed string
|
|
gfxSkipChars dummy;
|
|
gfxTextRunFactory::Parameters innerParams = GetParametersForInner(*aParams, &dummy);
|
|
innerParams.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT;
|
|
|
|
if (mInnerTransformingTextRunFactory) {
|
|
mInnerTransformingTextRunFactory->SetStyles(mStyles);
|
|
}
|
|
|
|
// This is the dummy that nsMultiTextRun needs
|
|
nsAutoPtr<gfxTextRun> innerTextRun;
|
|
innerTextRun =
|
|
mInnerTextRunFactory->MakeTextRun(aString, aLength, &innerParams);
|
|
if (!innerTextRun)
|
|
return nsnull;
|
|
// Make sure the inner textrun never calls ForceRememberText, because we don't
|
|
// implement it
|
|
innerTextRun->RememberText(aString, aLength);
|
|
|
|
nsAutoPtr<nsCaseTransformingTextRun> textRun;
|
|
textRun =
|
|
new nsCaseTransformingTextRun(innerTextRun, aParams, this, aString, aLength);
|
|
if (!textRun)
|
|
return nsnull;
|
|
innerTextRun.forget();
|
|
nsresult rv = textRun->Build();
|
|
if (NS_FAILED(rv))
|
|
return nsnull;
|
|
return textRun.forget();
|
|
}
|