|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "gfxDWriteTextAnalysis.h" |
|
7 |
|
8 TextAnalysis::TextAnalysis(const wchar_t* text, |
|
9 UINT32 textLength, |
|
10 const wchar_t* localeName, |
|
11 DWRITE_READING_DIRECTION readingDirection) |
|
12 : mText(text) |
|
13 , mTextLength(textLength) |
|
14 , mLocaleName(localeName) |
|
15 , mReadingDirection(readingDirection) |
|
16 , mCurrentRun(nullptr) |
|
17 { |
|
18 } |
|
19 |
|
20 TextAnalysis::~TextAnalysis() |
|
21 { |
|
22 // delete runs, except mRunHead which is part of the TextAnalysis object |
|
23 for (Run *run = mRunHead.nextRun; run;) { |
|
24 Run *origRun = run; |
|
25 run = run->nextRun; |
|
26 delete origRun; |
|
27 } |
|
28 } |
|
29 |
|
30 STDMETHODIMP |
|
31 TextAnalysis::GenerateResults(IDWriteTextAnalyzer* textAnalyzer, |
|
32 OUT Run **runHead) |
|
33 { |
|
34 // Analyzes the text using the script analyzer and returns |
|
35 // the result as a series of runs. |
|
36 |
|
37 HRESULT hr = S_OK; |
|
38 |
|
39 // Initially start out with one result that covers the entire range. |
|
40 // This result will be subdivided by the analysis processes. |
|
41 mRunHead.mTextStart = 0; |
|
42 mRunHead.mTextLength = mTextLength; |
|
43 mRunHead.mBidiLevel = |
|
44 (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); |
|
45 mRunHead.nextRun = nullptr; |
|
46 mCurrentRun = &mRunHead; |
|
47 |
|
48 // Call each of the analyzers in sequence, recording their results. |
|
49 if (SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this, |
|
50 0, |
|
51 mTextLength, |
|
52 this))) { |
|
53 *runHead = &mRunHead; |
|
54 } |
|
55 |
|
56 return hr; |
|
57 } |
|
58 |
|
59 |
|
60 //////////////////////////////////////////////////////////////////////////////// |
|
61 // IDWriteTextAnalysisSource source implementation |
|
62 |
|
63 IFACEMETHODIMP |
|
64 TextAnalysis::GetTextAtPosition(UINT32 textPosition, |
|
65 OUT WCHAR const** textString, |
|
66 OUT UINT32* textLength) |
|
67 { |
|
68 if (textPosition >= mTextLength) { |
|
69 // No text at this position, valid query though. |
|
70 *textString = nullptr; |
|
71 *textLength = 0; |
|
72 } else { |
|
73 *textString = mText + textPosition; |
|
74 *textLength = mTextLength - textPosition; |
|
75 } |
|
76 return S_OK; |
|
77 } |
|
78 |
|
79 |
|
80 IFACEMETHODIMP |
|
81 TextAnalysis::GetTextBeforePosition(UINT32 textPosition, |
|
82 OUT WCHAR const** textString, |
|
83 OUT UINT32* textLength) |
|
84 { |
|
85 if (textPosition == 0 || textPosition > mTextLength) { |
|
86 // Either there is no text before here (== 0), or this |
|
87 // is an invalid position. The query is considered valid thouh. |
|
88 *textString = nullptr; |
|
89 *textLength = 0; |
|
90 } else { |
|
91 *textString = mText; |
|
92 *textLength = textPosition; |
|
93 } |
|
94 return S_OK; |
|
95 } |
|
96 |
|
97 |
|
98 DWRITE_READING_DIRECTION STDMETHODCALLTYPE |
|
99 TextAnalysis::GetParagraphReadingDirection() |
|
100 { |
|
101 // We support only a single reading direction. |
|
102 return mReadingDirection; |
|
103 } |
|
104 |
|
105 |
|
106 IFACEMETHODIMP |
|
107 TextAnalysis::GetLocaleName(UINT32 textPosition, |
|
108 OUT UINT32* textLength, |
|
109 OUT WCHAR const** localeName) |
|
110 { |
|
111 // Single locale name is used, valid until the end of the string. |
|
112 *localeName = mLocaleName; |
|
113 *textLength = mTextLength - textPosition; |
|
114 |
|
115 return S_OK; |
|
116 } |
|
117 |
|
118 |
|
119 IFACEMETHODIMP |
|
120 TextAnalysis::GetNumberSubstitution(UINT32 textPosition, |
|
121 OUT UINT32* textLength, |
|
122 OUT IDWriteNumberSubstitution** numberSubstitution) |
|
123 { |
|
124 // We do not support number substitution. |
|
125 *numberSubstitution = nullptr; |
|
126 *textLength = mTextLength - textPosition; |
|
127 |
|
128 return S_OK; |
|
129 } |
|
130 |
|
131 |
|
132 //////////////////////////////////////////////////////////////////////////////// |
|
133 // IDWriteTextAnalysisSink implementation |
|
134 |
|
135 IFACEMETHODIMP |
|
136 TextAnalysis::SetLineBreakpoints(UINT32 textPosition, |
|
137 UINT32 textLength, |
|
138 DWRITE_LINE_BREAKPOINT const* lineBreakpoints) |
|
139 { |
|
140 // We don't use this for now. |
|
141 return S_OK; |
|
142 } |
|
143 |
|
144 |
|
145 IFACEMETHODIMP |
|
146 TextAnalysis::SetScriptAnalysis(UINT32 textPosition, |
|
147 UINT32 textLength, |
|
148 DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) |
|
149 { |
|
150 SetCurrentRun(textPosition); |
|
151 SplitCurrentRun(textPosition); |
|
152 while (textLength > 0) { |
|
153 Run *run = FetchNextRun(&textLength); |
|
154 run->mScript = *scriptAnalysis; |
|
155 } |
|
156 |
|
157 return S_OK; |
|
158 } |
|
159 |
|
160 |
|
161 IFACEMETHODIMP |
|
162 TextAnalysis::SetBidiLevel(UINT32 textPosition, |
|
163 UINT32 textLength, |
|
164 UINT8 explicitLevel, |
|
165 UINT8 resolvedLevel) |
|
166 { |
|
167 // We don't use this for now. |
|
168 return S_OK; |
|
169 } |
|
170 |
|
171 |
|
172 IFACEMETHODIMP |
|
173 TextAnalysis::SetNumberSubstitution(UINT32 textPosition, |
|
174 UINT32 textLength, |
|
175 IDWriteNumberSubstitution* numberSubstitution) |
|
176 { |
|
177 // We don't use this for now. |
|
178 return S_OK; |
|
179 } |
|
180 |
|
181 |
|
182 //////////////////////////////////////////////////////////////////////////////// |
|
183 // Run modification. |
|
184 |
|
185 TextAnalysis::Run * |
|
186 TextAnalysis::FetchNextRun(IN OUT UINT32* textLength) |
|
187 { |
|
188 // Used by the sink setters, this returns a reference to the next run. |
|
189 // Position and length are adjusted to now point after the current run |
|
190 // being returned. |
|
191 |
|
192 Run *origRun = mCurrentRun; |
|
193 // Split the tail if needed (the length remaining is less than the |
|
194 // current run's size). |
|
195 if (*textLength < mCurrentRun->mTextLength) { |
|
196 SplitCurrentRun(mCurrentRun->mTextStart + *textLength); |
|
197 } else { |
|
198 // Just advance the current run. |
|
199 mCurrentRun = mCurrentRun->nextRun; |
|
200 } |
|
201 *textLength -= origRun->mTextLength; |
|
202 |
|
203 // Return a reference to the run that was just current. |
|
204 return origRun; |
|
205 } |
|
206 |
|
207 |
|
208 void TextAnalysis::SetCurrentRun(UINT32 textPosition) |
|
209 { |
|
210 // Move the current run to the given position. |
|
211 // Since the analyzers generally return results in a forward manner, |
|
212 // this will usually just return early. If not, find the |
|
213 // corresponding run for the text position. |
|
214 |
|
215 if (mCurrentRun && mCurrentRun->ContainsTextPosition(textPosition)) { |
|
216 return; |
|
217 } |
|
218 |
|
219 for (Run *run = &mRunHead; run; run = run->nextRun) { |
|
220 if (run->ContainsTextPosition(textPosition)) { |
|
221 mCurrentRun = run; |
|
222 return; |
|
223 } |
|
224 } |
|
225 NS_NOTREACHED("We should always be able to find the text position in one \ |
|
226 of our runs"); |
|
227 } |
|
228 |
|
229 |
|
230 void TextAnalysis::SplitCurrentRun(UINT32 splitPosition) |
|
231 { |
|
232 if (!mCurrentRun) { |
|
233 NS_ASSERTION(false, "SplitCurrentRun called without current run."); |
|
234 // Shouldn't be calling this when no current run is set! |
|
235 return; |
|
236 } |
|
237 // Split the current run. |
|
238 if (splitPosition <= mCurrentRun->mTextStart) { |
|
239 // No need to split, already the start of a run |
|
240 // or before it. Usually the first. |
|
241 return; |
|
242 } |
|
243 Run *newRun = new Run; |
|
244 |
|
245 *newRun = *mCurrentRun; |
|
246 |
|
247 // Insert the new run in our linked list. |
|
248 newRun->nextRun = mCurrentRun->nextRun; |
|
249 mCurrentRun->nextRun = newRun; |
|
250 |
|
251 // Adjust runs' text positions and lengths. |
|
252 UINT32 splitPoint = splitPosition - mCurrentRun->mTextStart; |
|
253 newRun->mTextStart += splitPoint; |
|
254 newRun->mTextLength -= splitPoint; |
|
255 mCurrentRun->mTextLength = splitPoint; |
|
256 mCurrentRun = newRun; |
|
257 } |