michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxDWriteTextAnalysis.h" michael@0: michael@0: TextAnalysis::TextAnalysis(const wchar_t* text, michael@0: UINT32 textLength, michael@0: const wchar_t* localeName, michael@0: DWRITE_READING_DIRECTION readingDirection) michael@0: : mText(text) michael@0: , mTextLength(textLength) michael@0: , mLocaleName(localeName) michael@0: , mReadingDirection(readingDirection) michael@0: , mCurrentRun(nullptr) michael@0: { michael@0: } michael@0: michael@0: TextAnalysis::~TextAnalysis() michael@0: { michael@0: // delete runs, except mRunHead which is part of the TextAnalysis object michael@0: for (Run *run = mRunHead.nextRun; run;) { michael@0: Run *origRun = run; michael@0: run = run->nextRun; michael@0: delete origRun; michael@0: } michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: TextAnalysis::GenerateResults(IDWriteTextAnalyzer* textAnalyzer, michael@0: OUT Run **runHead) michael@0: { michael@0: // Analyzes the text using the script analyzer and returns michael@0: // the result as a series of runs. michael@0: michael@0: HRESULT hr = S_OK; michael@0: michael@0: // Initially start out with one result that covers the entire range. michael@0: // This result will be subdivided by the analysis processes. michael@0: mRunHead.mTextStart = 0; michael@0: mRunHead.mTextLength = mTextLength; michael@0: mRunHead.mBidiLevel = michael@0: (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); michael@0: mRunHead.nextRun = nullptr; michael@0: mCurrentRun = &mRunHead; michael@0: michael@0: // Call each of the analyzers in sequence, recording their results. michael@0: if (SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this, michael@0: 0, michael@0: mTextLength, michael@0: this))) { michael@0: *runHead = &mRunHead; michael@0: } michael@0: michael@0: return hr; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // IDWriteTextAnalysisSource source implementation michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::GetTextAtPosition(UINT32 textPosition, michael@0: OUT WCHAR const** textString, michael@0: OUT UINT32* textLength) michael@0: { michael@0: if (textPosition >= mTextLength) { michael@0: // No text at this position, valid query though. michael@0: *textString = nullptr; michael@0: *textLength = 0; michael@0: } else { michael@0: *textString = mText + textPosition; michael@0: *textLength = mTextLength - textPosition; michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::GetTextBeforePosition(UINT32 textPosition, michael@0: OUT WCHAR const** textString, michael@0: OUT UINT32* textLength) michael@0: { michael@0: if (textPosition == 0 || textPosition > mTextLength) { michael@0: // Either there is no text before here (== 0), or this michael@0: // is an invalid position. The query is considered valid thouh. michael@0: *textString = nullptr; michael@0: *textLength = 0; michael@0: } else { michael@0: *textString = mText; michael@0: *textLength = textPosition; michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: DWRITE_READING_DIRECTION STDMETHODCALLTYPE michael@0: TextAnalysis::GetParagraphReadingDirection() michael@0: { michael@0: // We support only a single reading direction. michael@0: return mReadingDirection; michael@0: } michael@0: michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::GetLocaleName(UINT32 textPosition, michael@0: OUT UINT32* textLength, michael@0: OUT WCHAR const** localeName) michael@0: { michael@0: // Single locale name is used, valid until the end of the string. michael@0: *localeName = mLocaleName; michael@0: *textLength = mTextLength - textPosition; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::GetNumberSubstitution(UINT32 textPosition, michael@0: OUT UINT32* textLength, michael@0: OUT IDWriteNumberSubstitution** numberSubstitution) michael@0: { michael@0: // We do not support number substitution. michael@0: *numberSubstitution = nullptr; michael@0: *textLength = mTextLength - textPosition; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // IDWriteTextAnalysisSink implementation michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::SetLineBreakpoints(UINT32 textPosition, michael@0: UINT32 textLength, michael@0: DWRITE_LINE_BREAKPOINT const* lineBreakpoints) michael@0: { michael@0: // We don't use this for now. michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::SetScriptAnalysis(UINT32 textPosition, michael@0: UINT32 textLength, michael@0: DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) michael@0: { michael@0: SetCurrentRun(textPosition); michael@0: SplitCurrentRun(textPosition); michael@0: while (textLength > 0) { michael@0: Run *run = FetchNextRun(&textLength); michael@0: run->mScript = *scriptAnalysis; michael@0: } michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::SetBidiLevel(UINT32 textPosition, michael@0: UINT32 textLength, michael@0: UINT8 explicitLevel, michael@0: UINT8 resolvedLevel) michael@0: { michael@0: // We don't use this for now. michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: IFACEMETHODIMP michael@0: TextAnalysis::SetNumberSubstitution(UINT32 textPosition, michael@0: UINT32 textLength, michael@0: IDWriteNumberSubstitution* numberSubstitution) michael@0: { michael@0: // We don't use this for now. michael@0: return S_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Run modification. michael@0: michael@0: TextAnalysis::Run * michael@0: TextAnalysis::FetchNextRun(IN OUT UINT32* textLength) michael@0: { michael@0: // Used by the sink setters, this returns a reference to the next run. michael@0: // Position and length are adjusted to now point after the current run michael@0: // being returned. michael@0: michael@0: Run *origRun = mCurrentRun; michael@0: // Split the tail if needed (the length remaining is less than the michael@0: // current run's size). michael@0: if (*textLength < mCurrentRun->mTextLength) { michael@0: SplitCurrentRun(mCurrentRun->mTextStart + *textLength); michael@0: } else { michael@0: // Just advance the current run. michael@0: mCurrentRun = mCurrentRun->nextRun; michael@0: } michael@0: *textLength -= origRun->mTextLength; michael@0: michael@0: // Return a reference to the run that was just current. michael@0: return origRun; michael@0: } michael@0: michael@0: michael@0: void TextAnalysis::SetCurrentRun(UINT32 textPosition) michael@0: { michael@0: // Move the current run to the given position. michael@0: // Since the analyzers generally return results in a forward manner, michael@0: // this will usually just return early. If not, find the michael@0: // corresponding run for the text position. michael@0: michael@0: if (mCurrentRun && mCurrentRun->ContainsTextPosition(textPosition)) { michael@0: return; michael@0: } michael@0: michael@0: for (Run *run = &mRunHead; run; run = run->nextRun) { michael@0: if (run->ContainsTextPosition(textPosition)) { michael@0: mCurrentRun = run; michael@0: return; michael@0: } michael@0: } michael@0: NS_NOTREACHED("We should always be able to find the text position in one \ michael@0: of our runs"); michael@0: } michael@0: michael@0: michael@0: void TextAnalysis::SplitCurrentRun(UINT32 splitPosition) michael@0: { michael@0: if (!mCurrentRun) { michael@0: NS_ASSERTION(false, "SplitCurrentRun called without current run."); michael@0: // Shouldn't be calling this when no current run is set! michael@0: return; michael@0: } michael@0: // Split the current run. michael@0: if (splitPosition <= mCurrentRun->mTextStart) { michael@0: // No need to split, already the start of a run michael@0: // or before it. Usually the first. michael@0: return; michael@0: } michael@0: Run *newRun = new Run; michael@0: michael@0: *newRun = *mCurrentRun; michael@0: michael@0: // Insert the new run in our linked list. michael@0: newRun->nextRun = mCurrentRun->nextRun; michael@0: mCurrentRun->nextRun = newRun; michael@0: michael@0: // Adjust runs' text positions and lengths. michael@0: UINT32 splitPoint = splitPosition - mCurrentRun->mTextStart; michael@0: newRun->mTextStart += splitPoint; michael@0: newRun->mTextLength -= splitPoint; michael@0: mCurrentRun->mTextLength = splitPoint; michael@0: mCurrentRun = newRun; michael@0: }