2
This file is a part of the NVDA project.
3
URL: http://www.nvda-project.org/
4
Copyright 2006-2010 NVDA contributers.
5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License version 2.0, as published by
7
the Free Software Foundation.
8
This program is distributed in the hope that it will be useful,
9
but WITHOUT ANY WARRANTY; without even the implied warranty of
10
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
This license can be found at:
12
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20
#include <common/xml.h>
21
#include "nvdaControllerInternal.h"
23
#include "displayModel.h"
27
void displayModelChunk_t::generateXML(wstring& text) {
30
s<<L" font-name=\""<<formatInfo.fontName<<L"\" ";
31
s<<L" font-size=\""<<formatInfo.fontSize<<L"pt\" ";
32
if(this->formatInfo.bold) s<<L" bold=\"true\"";
33
if(this->formatInfo.italic) s<<L" italic=\"true\"";
34
if(this->formatInfo.underline) s<<L" underline=\"true\"";
35
s<<L" color=\""<<this->formatInfo.color<<L"\"";
36
s<<L" background-color=\""<<this->formatInfo.backgroundColor<<L"\"";
39
for(wstring::iterator i=this->text.begin();i!=this->text.end();++i) {
40
appendCharToXML(*i,text);
42
text.append(L"</text>");
45
void displayModelChunk_t::truncate(int truncatePointX, BOOL truncateBefore) {
46
if(text.length()==0) return;
47
nhAssert(characterXArray.size()!=0);
48
deque<int>::iterator c=characterXArray.begin();
49
wstring::iterator t=text.begin();
50
if(truncateBefore&&rect.left<truncatePointX) {
51
for(;t!=text.end()&&(*c)<truncatePointX;++c,++t);
52
if(c!=characterXArray.end()) rect.left=*c; else rect.left=rect.right;
53
characterXArray.erase(characterXArray.begin(),c);
54
text.erase(text.begin(),t);
55
} else if(!truncateBefore&&truncatePointX<rect.right) {
56
for(;t!=text.end()&&(*c)<=truncatePointX;++c,++t);
62
characterXArray.erase(c,characterXArray.end());
63
text.erase(t,text.end());
67
displayModel_t::displayModel_t(): LockableAutoFreeObject(), chunksByYX() {
68
LOG_DEBUG(L"created instance at "<<this);
71
displayModel_t::~displayModel_t() {
72
LOG_DEBUG(L"destroying instance at "<<this);
73
for(displayModelChunksByPointMap_t::iterator i=chunksByYX.begin();i!=chunksByYX.end();) {
74
LOG_DEBUG(L"deleting chunk at "<<i->second);
76
chunksByYX.erase(i++);
80
size_t displayModel_t::getChunkCount() {
81
return chunksByYX.size();
84
void displayModel_t::insertChunk(const RECT& rect, int baselineFromTop, const wstring& text, int* characterEndXArray, const displayModelFormatInfo_t& formatInfo, const RECT* clippingRect) {
85
displayModelChunk_t* chunk=new displayModelChunk_t;
86
LOG_DEBUG(L"created new chunk at "<<chunk);
88
chunk->baselineFromTop=baselineFromTop;
90
chunk->formatInfo=formatInfo;
91
chunk->characterXArray.push_back(rect.left);
92
for(unsigned int i=0;i<(text.length()-1);++i) chunk->characterXArray.push_back(characterEndXArray[i]+rect.left);
93
LOG_DEBUG(L"filled in chunk with rectangle from "<<rect.left<<L","<<rect.top<<L" to "<<rect.right<<L","<<rect.bottom<<L" with text of "<<text);
94
//If a clipping rect is specified, and the chunk falls outside the clipping rect
95
//Truncate the chunk so that it stays inside the clipping rect.
97
if(clippingRect->left>chunk->rect.left) chunk->truncate(clippingRect->left,TRUE);
98
if(clippingRect->right<chunk->rect.right) chunk->truncate(clippingRect->right,FALSE);
100
//Its possible there is now no text in the chunk
101
//Only insert it if there is text.
102
if(chunk->text.length()>0) {
109
void displayModel_t::insertChunk(displayModelChunk_t* chunk) {
110
chunksByYX[make_pair(chunk->rect.top+chunk->baselineFromTop,chunk->rect.left)]=chunk;
113
void displayModel_t::clearAll() {
114
for(displayModelChunksByPointMap_t::iterator i=chunksByYX.begin();i!=chunksByYX.end();) {
116
chunksByYX.erase(i++);
120
void displayModel_t::clearRectangle(const RECT& rect, BOOL clearForText) {
121
LOG_DEBUG(L"Clearing rectangle from "<<rect.left<<L","<<rect.top<<L" to "<<rect.right<<L","<<rect.bottom);
122
set<displayModelChunk_t*> chunksForInsertion;
123
displayModelChunksByPointMap_t::iterator i=chunksByYX.begin();
125
while(i!=chunksByYX.end()) {
126
displayModelChunksByPointMap_t::iterator nextI=i;
128
displayModelChunk_t* chunk=i->second;
129
int baseline=i->first.first;
130
if(IntersectRect(&tempRect,&rect,&(chunk->rect))) {
131
//The clearing rectangle intercects the chunk's rectangle in some way.
132
if(tempRect.bottom<=baseline) {
133
//The clearing rectangle some how covers the chunk below its baseline.
134
//If we're clearing to make room for text, or the clearing rectangle starts from the very top of the chunk
135
//Then we should shrink the chunk down so it stays below the clearing rectangle.
136
//If not, then we pretend the clearRectangle did not happen (the chunk was only parcially cleared vertically so we don't care).
137
if(clearForText||tempRect.top==chunk->rect.top) {
138
chunk->rect.top=tempRect.bottom;
140
} else if(tempRect.top>baseline) {
141
//The clearing rectangle some how covers the chunk above its baseline.
142
//If we're clearing to make room for text, or the clearing rectangle starts from the very bottom of the chunk
143
//Then we should shrink the chunk up so it stays above the clearing rectangle.
144
//If not, then we pretend the clearRectangle did not happen (the chunk was only parcially cleared vertically so we don't care).
145
if(clearForText||tempRect.bottom==chunk->rect.bottom) {
146
chunk->rect.bottom=tempRect.top;
149
//The clearing rectangle covers the chunk's baseline, so remove the part of the chunk covered horozontally by the clearing rectangle.
150
if(tempRect.left==chunk->rect.left&&tempRect.right==chunk->rect.right) {
153
} else if(tempRect.left>chunk->rect.left&&tempRect.right==chunk->rect.right) {
154
chunk->truncate(tempRect.left,FALSE);
155
if(chunk->text.length()==0) {
159
} else if(tempRect.right<chunk->rect.right&&tempRect.left==chunk->rect.left) {
161
chunk->truncate(tempRect.right,TRUE);
162
if(chunk->text.length()==0) {
165
chunksForInsertion.insert(chunk);
168
displayModelChunk_t* newChunk=new displayModelChunk_t(*chunk);
169
chunk->truncate(tempRect.left,FALSE);
170
if(chunk->text.length()==0) {
174
newChunk->truncate(tempRect.right,TRUE);
175
if(newChunk->text.length()==0) {
178
chunksForInsertion.insert(newChunk);
185
for(set<displayModelChunk_t*>::iterator i=chunksForInsertion.begin();i!=chunksForInsertion.end();++i) {
188
LOG_DEBUG(L"complete");
191
void displayModel_t::copyRectangle(const RECT& srcRect, BOOL removeFromSource, BOOL opaqueCopy, int destX, int destY, const RECT* destClippingRect, displayModel_t* destModel) {
192
if(!destModel) destModel=this;
194
int deltaX=destX-srcRect.left;
195
int deltaY=destY-srcRect.top;
196
RECT destRect={srcRect.left+deltaX,srcRect.top+deltaY,srcRect.right+deltaX,srcRect.bottom+deltaY};
197
//If a clipping rectangle is provided, clip the destination rectangle
198
if(destClippingRect) {
199
IntersectRect(&tempRect,&destRect,destClippingRect);
202
//Make copies of all the needed chunks, tweek their rectangle coordinates, truncate if needed, and store them in a temporary list
203
list<displayModelChunk_t*> copiedChunks;
204
for(displayModelChunksByPointMap_t::iterator i=chunksByYX.begin();i!=chunksByYX.end();++i) {
205
//We only care about chunks that are overlapped by the source rectangle
206
if(!IntersectRect(&tempRect,&srcRect,&(i->second->rect))) continue;
208
displayModelChunk_t* chunk=new displayModelChunk_t(*(i->second));
209
//Tweek its rectangle coordinates to match where its going in the destination model
210
(chunk->rect.left)+=deltaX;
211
(chunk->rect.top)+=deltaY;
212
(chunk->rect.right)+=deltaX;
213
(chunk->rect.bottom)+=deltaY;
214
//Tweek its character x coordinates to match where its going in the destination model
215
for(deque<int>::iterator x=chunk->characterXArray.begin();x!=chunk->characterXArray.end();++x) (*x)+=deltaX;
216
//Truncate the chunk so it does not stick outside of the destination rectangle
217
if(chunk->rect.left<destRect.left) {
218
chunk->truncate(destRect.left,TRUE);
220
if(chunk->rect.right>destRect.right) {
221
chunk->truncate(destRect.right,FALSE);
223
//if the chunk is now empty due to truncation then just delete it and move on to the next
224
if(chunk->text.length()==0) {
228
//Insert the chunk in to the temporary list
229
copiedChunks.insert(copiedChunks.end(),chunk);
231
//Clear the source rectangle if requested to do so (move rather than copy)
232
if(removeFromSource) this->clearRectangle(srcRect);
233
//Clear the entire destination rectangle if requested to do so
234
if(opaqueCopy) destModel->clearRectangle(destRect);
235
//insert the copied chunks in to the destination model
236
for(list<displayModelChunk_t*>::iterator i=copiedChunks.begin();i!=copiedChunks.end();++i) {
237
if(!opaqueCopy) destModel->clearRectangle((*i)->rect);
238
destModel->insertChunk(*i);
242
void displayModel_t::renderText(const RECT& rect, const int minHorizontalWhitespace, const int minVerticalWhitespace, const bool stripOuterWhitespace, bool useXML, wstring& text, deque<RECT>& characterRects) {
245
deque<RECT> curLineCharacterRects;
246
int curLineMinTop=-1;
247
int curLineMaxBottom=-1;
248
int curLineBaseline=-1;
249
int lastChunkRight=rect.left;
250
int lastLineBottom=rect.top;
251
//Walk through all the chunks looking for any that intersect the rectangle
252
displayModelChunksByPointMap_t::iterator chunkIt=chunksByYX.begin();
253
while(chunkIt!=chunksByYX.end()) {
254
displayModelChunk_t* chunk=NULL;
255
BOOL isTempChunk=FALSE;
256
if(IntersectRect(&tempRect,&rect,&(chunkIt->second->rect))) {
257
chunk=chunkIt->second;
258
//If this chunk is not fully covered by the rectangle
259
//Copy it and truncate it so that it is fully covered
260
if(chunk->rect.left<tempRect.left||chunk->rect.right>tempRect.right) {
261
chunk=new displayModelChunk_t(*chunk);
263
if(chunk->rect.left<tempRect.left) chunk->truncate(tempRect.left,TRUE);
264
if(chunk->rect.right>tempRect.right) chunk->truncate(tempRect.right,FALSE);
267
//Find out the current line's baseline
268
curLineBaseline=chunkIt->first.first;
269
//Iterate to the next possible chunk
271
//If we have a valid chunk then add it to the current line
272
if(chunk&&chunk->text.length()>0) {
273
//Update the maximum height of the line
274
if(curLineText.length()==0) {
275
curLineMinTop=chunk->rect.top;
276
curLineMaxBottom=chunk->rect.bottom;
278
if(chunk->rect.top<curLineMinTop) curLineMinTop=chunk->rect.top;
279
if(chunk->rect.bottom>curLineMaxBottom) curLineMaxBottom=chunk->rect.bottom;
281
//Add space before this chunk if necessary
282
if(((chunk->rect.left-lastChunkRight)>=minHorizontalWhitespace)&&(lastChunkRight>rect.left||!stripOuterWhitespace)) {
284
curLineText+=L"<text> </text>";
288
tempRect.left=lastChunkRight;
289
tempRect.top=curLineBaseline-1;
290
tempRect.right=chunk->rect.left;
291
tempRect.bottom=curLineBaseline+1;
292
curLineCharacterRects.push_back(tempRect);
294
//Add text from this chunk to the current line
296
chunk->generateXML(curLineText);
298
curLineText.append(chunk->text);
300
//Copy the character X positions from this chunk in to the current line
301
deque<int>::const_iterator cxaIt=chunk->characterXArray.begin();
302
while(cxaIt!=chunk->characterXArray.end()) {
303
tempRect.left=*cxaIt;
304
tempRect.top=chunk->rect.top;
306
tempRect.right=(cxaIt!=chunk->characterXArray.end())?*cxaIt:chunk->rect.right;
307
tempRect.bottom=chunk->rect.bottom;
308
curLineCharacterRects.push_back(tempRect);
310
lastChunkRight=chunk->rect.right;
312
if((chunkIt==chunksByYX.end()||chunkIt->first.first>curLineBaseline)&&curLineText.length()>0) {
313
//This is the end of the line
314
if(((curLineMinTop-lastLineBottom)>=minVerticalWhitespace)&&(lastLineBottom>rect.top||!stripOuterWhitespace)) {
315
//There is space between this line and the last,
316
//Insert a blank line in between.
317
text+=(useXML?L"<text>\n</text>":L"\n");
318
tempRect.left=rect.left;
319
tempRect.top=lastLineBottom;
320
tempRect.right=rect.right;
321
tempRect.bottom=curLineMinTop;
322
characterRects.push_back(tempRect);
324
//Insert this line in to the output.
325
text.append(curLineText);
326
characterRects.insert(characterRects.end(),curLineCharacterRects.begin(),curLineCharacterRects.end());
327
//Add a linefeed to complete the line
328
text+=(useXML?L"<text>\n</text>":L"\n");
329
tempRect.left=lastChunkRight;
330
tempRect.top=curLineBaseline-1;
331
tempRect.right=rect.right;
332
tempRect.bottom=curLineBaseline+1;
333
characterRects.push_back(tempRect);
334
//Reset the current line values
336
curLineCharacterRects.clear();
337
lastChunkRight=rect.left;
338
lastLineBottom=curLineMaxBottom;
339
if(chunk&&isTempChunk) delete chunk;
342
if(!stripOuterWhitespace&&(rect.bottom-lastLineBottom)>=minVerticalWhitespace) {
343
//There is a gap between the bottom of the final line and the bottom of the requested rectangle,
344
//So add a blank line.
345
text+=(useXML?L"<text>\n</text>":L"\n");
346
tempRect.left=rect.left;
347
tempRect.top=lastLineBottom;
348
tempRect.right=rect.right;
349
tempRect.bottom=rect.bottom;
350
characterRects.push_back(tempRect);