1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_sw.hxx"
26
27 #include <ctype.h>
28 #include <editeng/unolingu.hxx>
29 #include <tools/shl.hxx> // needed for SW_MOD() macro
30 #include <errhdl.hxx> // ASSERTs
31 #include <dlelstnr.hxx>
32 #include <swmodule.hxx>
33 #include <IDocumentSettingAccess.hxx>
34 #include <txtcfg.hxx>
35 #include <guess.hxx>
36 #include <inftxt.hxx>
37 #include <pagefrm.hxx>
38 #include <pagedesc.hxx> // SwPageDesc
39 #include <tgrditem.hxx>
40 #include <com/sun/star/i18n/BreakType.hpp>
41 #include <com/sun/star/i18n/WordType.hpp>
42 #include <unotools/charclass.hxx>
43 #include <porfld.hxx>
44 #include <paratr.hxx>
45
46 using ::rtl::OUString;
47 using namespace ::com::sun::star;
48 using namespace ::com::sun::star::uno;
49 using namespace ::com::sun::star::i18n;
50 using namespace ::com::sun::star::beans;
51 using namespace ::com::sun::star::linguistic2;
52
53 #define CH_FULL_BLANK 0x3000
54
55 /*************************************************************************
56 * SwTxtGuess::Guess
57 *
58 * provides information for line break calculation
59 * returns true if no line break has to be performed
60 * otherwise possible break or hyphenation position is determined
61 *************************************************************************/
62
Guess(const SwTxtPortion & rPor,SwTxtFormatInfo & rInf,const KSHORT nPorHeight)63 sal_Bool SwTxtGuess::Guess( const SwTxtPortion& rPor, SwTxtFormatInfo &rInf,
64 const KSHORT nPorHeight )
65 {
66 nCutPos = rInf.GetIdx();
67
68 // Empty strings are always 0
69 if( !rInf.GetLen() || !rInf.GetTxt().Len() )
70 return sal_False;
71
72 ASSERT( rInf.GetIdx() < rInf.GetTxt().Len(),
73 "+SwTxtGuess::Guess: invalid SwTxtFormatInfo" );
74
75 ASSERT( nPorHeight, "+SwTxtGuess::Guess: no height" );
76
77 sal_uInt16 nMinSize;
78 sal_uInt16 nMaxSizeDiff;
79
80 const SwScriptInfo& rSI =
81 ((SwParaPortion*)rInf.GetParaPortion())->GetScriptInfo();
82
83 sal_uInt16 nMaxComp = ( SW_CJK == rInf.GetFont()->GetActual() ) &&
84 rSI.CountCompChg() &&
85 ! rInf.IsMulti() &&
86 ! rPor.InFldGrp() &&
87 ! rPor.IsDropPortion() ?
88 10000 :
89 0 ;
90
91 SwTwips nLineWidth = rInf.Width() - rInf.X();
92 xub_StrLen nMaxLen = rInf.GetTxt().Len() - rInf.GetIdx();
93
94 if ( rInf.GetLen() < nMaxLen )
95 nMaxLen = rInf.GetLen();
96
97 if( !nMaxLen )
98 return sal_False;
99
100 KSHORT nItalic = 0;
101 if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() )
102 {
103 sal_Bool bAddItalic = sal_True;
104
105 // do not add extra italic value if we have an active character grid
106 if ( rInf.SnapToGrid() )
107 {
108 GETGRID( rInf.GetTxtFrm()->FindPageFrm() )
109 bAddItalic = !pGrid || GRID_LINES_CHARS != pGrid->GetGridType();
110 }
111
112 // do not add extra italic value for an isolated blank:
113 if ( 1 == rInf.GetLen() &&
114 CH_BLANK == rInf.GetTxt().GetChar( rInf.GetIdx() ) )
115 bAddItalic = sal_False;
116
117 nItalic = bAddItalic ? nPorHeight / 12 : 0;
118
119 nLineWidth -= nItalic;
120
121 // --> FME 2005-05-13 #i46524# LineBreak bug with italics
122 if ( nLineWidth < 0 ) nLineWidth = 0;
123 // <--
124 }
125
126 // first check if everything fits to line
127 if ( long ( nLineWidth ) * 2 > long ( nMaxLen ) * nPorHeight )
128 {
129 // call GetTxtSize with maximum compression (for kanas)
130 rInf.GetTxtSize( &rSI, rInf.GetIdx(), nMaxLen,
131 nMaxComp, nMinSize, nMaxSizeDiff );
132
133 nBreakWidth = nMinSize;
134
135 if ( nBreakWidth <= nLineWidth )
136 {
137 // portion fits to line
138 nCutPos = rInf.GetIdx() + nMaxLen;
139 if( nItalic &&
140 ( nCutPos >= rInf.GetTxt().Len() ||
141 // --> FME 2005-05-13 #i48035# Needed for CalcFitToContent
142 // if first line ends with a manual line break
143 rInf.GetTxt().GetChar( nCutPos ) == CH_BREAK ) )
144 // <--
145 nBreakWidth = nBreakWidth + nItalic;
146
147 // save maximum width for later use
148 if ( nMaxSizeDiff )
149 rInf.SetMaxWidthDiff( (sal_uLong)&rPor, nMaxSizeDiff );
150
151 return sal_True;
152 }
153 }
154
155 sal_Bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud();
156 xub_StrLen nHyphPos = 0;
157
158 // nCutPos is the first character not fitting to the current line
159 // nHyphPos is the first character not fitting to the current line,
160 // considering an additional "-" for hyphenation
161 if( bHyph )
162 {
163 nCutPos = rInf.GetTxtBreak( nLineWidth, nMaxLen, nMaxComp, nHyphPos );
164
165 if ( !nHyphPos && rInf.GetIdx() )
166 nHyphPos = rInf.GetIdx() - 1;
167 }
168 else
169 {
170 nCutPos = rInf.GetTxtBreak( nLineWidth, nMaxLen, nMaxComp );
171
172 #ifdef DBG_UTIL
173 if ( STRING_LEN != nCutPos )
174 {
175 rInf.GetTxtSize( &rSI, rInf.GetIdx(), nCutPos - rInf.GetIdx(),
176 nMaxComp, nMinSize, nMaxSizeDiff );
177 ASSERT( nMinSize <= nLineWidth, "What a Guess!!!" );
178 }
179 #endif
180 }
181
182 if( nCutPos > rInf.GetIdx() + nMaxLen )
183 {
184 // second check if everything fits to line
185 nCutPos = nBreakPos = rInf.GetIdx() + nMaxLen - 1;
186 rInf.GetTxtSize( &rSI, rInf.GetIdx(), nMaxLen, nMaxComp,
187 nMinSize, nMaxSizeDiff );
188
189 nBreakWidth = nMinSize;
190
191 // The following comparison should always give sal_True, otherwise
192 // a pixel rounding error in GetTxtBreak will appear
193 if ( nBreakWidth <= nLineWidth )
194 {
195 if( nItalic && ( nBreakPos + 1 ) >= rInf.GetTxt().Len() )
196 nBreakWidth = nBreakWidth + nItalic;
197
198 // save maximum width for later use
199 if ( nMaxSizeDiff )
200 rInf.SetMaxWidthDiff( (sal_uLong)&rPor, nMaxSizeDiff );
201
202 return sal_True;
203 }
204 }
205
206 // we have to trigger an underflow for a footnote portion
207 // which does not fit to the current line
208 if ( rPor.IsFtnPortion() )
209 {
210 nBreakPos = rInf.GetIdx();
211 nCutPos = rInf.GetLen();
212 return sal_False;
213 }
214
215 xub_StrLen nPorLen = 0;
216 // do not call the break iterator nCutPos is a blank
217 xub_Unicode cCutChar = rInf.GetTxt().GetChar( nCutPos );
218 if( CH_BLANK == cCutChar || CH_FULL_BLANK == cCutChar )
219 {
220 nBreakPos = nCutPos;
221 xub_StrLen nX = nBreakPos;
222
223 const SvxAdjust& rAdjust = rInf.GetTxtFrm()->GetTxtNode()->GetSwAttrSet().GetAdjust().GetAdjust();
224 if ( rAdjust == SVX_ADJUST_LEFT )
225 {
226 // we step back until a non blank character has been found
227 // or there is only one more character left
228 while( nX && nBreakPos > rInf.GetTxt().Len() &&
229 ( CH_BLANK == ( cCutChar = rInf.GetChar( --nX ) ) ||
230 CH_FULL_BLANK == cCutChar ) )
231 --nBreakPos;
232 }
233 else //#i20878#
234 {
235 while( nX && nBreakPos > rInf.GetLineStart() + 1 &&
236 ( CH_BLANK == ( cCutChar = rInf.GetChar( --nX ) ) ||
237 CH_FULL_BLANK == cCutChar ) )
238 --nBreakPos;
239 }
240
241 if( nBreakPos > rInf.GetIdx() )
242 nPorLen = nBreakPos - rInf.GetIdx();
243 while( ++nCutPos < rInf.GetTxt().Len() &&
244 ( CH_BLANK == ( cCutChar = rInf.GetChar( nCutPos ) ) ||
245 CH_FULL_BLANK == cCutChar ) )
246 ; // nothing
247
248 nBreakStart = nCutPos;
249 }
250 else if( pBreakIt->GetBreakIter().is() )
251 {
252 // New: We should have a look into the last portion, if it was a
253 // field portion. For this, we expand the text of the field portion
254 // into our string. If the line break position is inside of before
255 // the field portion, we trigger an underflow.
256
257 xub_StrLen nOldIdx = rInf.GetIdx();
258 xub_Unicode cFldChr = 0;
259
260 #if OSL_DEBUG_LEVEL > 1
261 XubString aDebugString;
262 #endif
263
264 // be careful: a field portion can be both: 0x01 (common field)
265 // or 0x02 (the follow of a footnode)
266 if ( rInf.GetLast() && rInf.GetLast()->InFldGrp() &&
267 ! rInf.GetLast()->IsFtnPortion() &&
268 rInf.GetIdx() > rInf.GetLineStart() &&
269 CH_TXTATR_BREAKWORD ==
270 ( cFldChr = rInf.GetTxt().GetChar( rInf.GetIdx() - 1 ) ) )
271 {
272 SwFldPortion* pFld = (SwFldPortion*)rInf.GetLast();
273 XubString aTxt;
274 pFld->GetExpTxt( rInf, aTxt );
275
276 if ( aTxt.Len() )
277 {
278 nFieldDiff = aTxt.Len() - 1;
279 nCutPos = nCutPos + nFieldDiff;
280 nHyphPos = nHyphPos + nFieldDiff;
281
282 #if OSL_DEBUG_LEVEL > 1
283 aDebugString = rInf.GetTxt();
284 #endif
285
286 XubString& rOldTxt = (XubString&)rInf.GetTxt();
287 rOldTxt.Erase( rInf.GetIdx() - 1, 1 );
288 rOldTxt.Insert( aTxt, rInf.GetIdx() - 1 );
289 rInf.SetIdx( rInf.GetIdx() + nFieldDiff );
290 }
291 else
292 cFldChr = 0;
293 }
294
295 LineBreakHyphenationOptions aHyphOpt;
296 Reference< XHyphenator > xHyph;
297 if( bHyph )
298 {
299 xHyph = ::GetHyphenator();
300 aHyphOpt = LineBreakHyphenationOptions( xHyph,
301 rInf.GetHyphValues(), nHyphPos );
302 }
303
304 // Get Language for break iterator.
305 // We have to switch the current language if we have a script
306 // change at nCutPos. Otherwise LATIN punctuation would never
307 // be allowed to be hanging punctuation.
308 // NEVER call GetLang if the string has been modified!!!
309 LanguageType aLang = rInf.GetFont()->GetLanguage();
310
311 // If we are inside a field portion, we use a temporar string which
312 // differs from the string at the textnode. Therefore we are not allowed
313 // to call the GetLang function.
314 if ( nCutPos && ! rPor.InFldGrp() )
315 {
316 const CharClass& rCC = GetAppCharClass();
317
318 // step back until a non-punctuation character is reached
319 xub_StrLen nLangIndex = nCutPos;
320
321 // If a field has been expanded right in front of us we do not
322 // step further than the beginning of the expanded field
323 // (which is the position of the field placeholder in our
324 // original string).
325 const xub_StrLen nDoNotStepOver = CH_TXTATR_BREAKWORD == cFldChr ?
326 rInf.GetIdx() - nFieldDiff - 1:
327 0;
328
329 while ( nLangIndex > nDoNotStepOver &&
330 ! rCC.isLetterNumeric( rInf.GetTxt(), nLangIndex ) )
331 --nLangIndex;
332
333 // last "real" character is not inside our current portion
334 // we have to check the script type of the last "real" character
335 if ( nLangIndex < rInf.GetIdx() )
336 {
337 sal_uInt16 nScript = pBreakIt->GetRealScriptOfText( rInf.GetTxt(),
338 nLangIndex );
339 ASSERT( nScript, "Script is not between 1 and 4" );
340
341 // compare current script with script from last "real" character
342 if ( nScript - 1 != rInf.GetFont()->GetActual() )
343 aLang = rInf.GetTxtFrm()->GetTxtNode()->GetLang(
344 CH_TXTATR_BREAKWORD == cFldChr ?
345 nDoNotStepOver :
346 nLangIndex, 0, nScript );
347 }
348 }
349
350 const ForbiddenCharacters aForbidden(
351 *rInf.GetTxtFrm()->GetNode()->getIDocumentSettingAccess()->getForbiddenCharacters( aLang, true ) );
352
353 const sal_Bool bAllowHanging = rInf.IsHanging() && ! rInf.IsMulti() &&
354 ! rPor.InFldGrp();
355
356 LineBreakUserOptions aUserOpt(
357 aForbidden.beginLine, aForbidden.endLine,
358 rInf.HasForbiddenChars(), bAllowHanging, sal_False );
359
360 //! register listener to LinguServiceEvents now in order to get
361 //! notified about relevant changes in the future
362 SwModule *pModule = SW_MOD();
363 if (!pModule->GetLngSvcEvtListener().is())
364 pModule->CreateLngSvcEvtListener();
365
366 // !!! We must have a local copy of the locale, because inside
367 // getLineBreak the LinguEventListener can trigger a new formatting,
368 // which can corrupt the locale pointer inside pBreakIt.
369 const lang::Locale aLocale = pBreakIt->GetLocale( aLang );
370
371 // determines first possible line break from nRightPos to
372 // start index of current line
373 LineBreakResults aResult = pBreakIt->GetBreakIter()->getLineBreak(
374 rInf.GetTxt(), nCutPos, aLocale,
375 rInf.GetLineStart(), aHyphOpt, aUserOpt );
376
377 nBreakPos = (xub_StrLen)aResult.breakIndex;
378
379 // if we are formatting multi portions we want to allow line breaks
380 // at the border between single line and multi line portion
381 // we have to be carefull with footnote portions, they always come in
382 // with an index 0
383 if ( nBreakPos < rInf.GetLineStart() && rInf.IsFirstMulti() &&
384 ! rInf.IsFtnInside() )
385 nBreakPos = rInf.GetLineStart();
386
387 nBreakStart = nBreakPos;
388
389 bHyph = BreakType::HYPHENATION == aResult.breakType;
390
391 if ( bHyph && nBreakPos != STRING_LEN)
392 {
393 // found hyphenation position within line
394 // nBreakPos is set to the hyphenation position
395 xHyphWord = aResult.rHyphenatedWord;
396 nBreakPos += xHyphWord->getHyphenationPos() + 1;
397
398 #if OSL_DEBUG_LEVEL > 1
399 // e.g., Schif-fahrt, referes to our string
400 const String aWord = xHyphWord->getWord();
401 // e.g., Schiff-fahrt, referes to the word after hyphenation
402 const String aHyphenatedWord = xHyphWord->getHyphenatedWord();
403 // e.g., Schif-fahrt: 5, referes to our string
404 const sal_uInt16 nHyphenationPos = xHyphWord->getHyphenationPos();
405 (void)nHyphenationPos;
406 // e.g., Schiff-fahrt: 6, referes to the word after hyphenation
407 const sal_uInt16 nHyphenPos = xHyphWord->getHyphenPos();
408 (void)nHyphenPos;
409 #endif
410
411 // if not in interactive mode, we have to break behind a soft hyphen
412 if ( ! rInf.IsInterHyph() && rInf.GetIdx() )
413 {
414 const long nSoftHyphPos =
415 xHyphWord->getWord().indexOf( CHAR_SOFTHYPHEN );
416
417 if ( nSoftHyphPos >= 0 &&
418 nBreakStart + nSoftHyphPos <= nBreakPos &&
419 nBreakPos > rInf.GetLineStart() )
420 nBreakPos = rInf.GetIdx() - 1;
421 }
422
423 if( nBreakPos >= rInf.GetIdx() )
424 {
425 nPorLen = nBreakPos - rInf.GetIdx();
426 if( '-' == rInf.GetTxt().GetChar( nBreakPos - 1 ) )
427 xHyphWord = NULL;
428 }
429 }
430 else if ( !bHyph && nBreakPos >= rInf.GetLineStart() )
431 {
432 ASSERT( nBreakPos != STRING_LEN, "we should have found a break pos" );
433
434 // found break position within line
435 xHyphWord = NULL;
436
437 // check, if break position is soft hyphen and an underflow
438 // has to be triggered
439 if( nBreakPos > rInf.GetLineStart() && rInf.GetIdx() &&
440 CHAR_SOFTHYPHEN == rInf.GetTxt().GetChar( nBreakPos - 1 ) )
441 nBreakPos = rInf.GetIdx() - 1;
442
443 const SvxAdjust& rAdjust = rInf.GetTxtFrm()->GetTxtNode()->GetSwAttrSet().GetAdjust().GetAdjust();
444 if( rAdjust != SVX_ADJUST_LEFT )
445 {
446 // Delete any blanks at the end of a line, but be careful:
447 // If a field has been expanded, we do not want to delete any
448 // blanks inside the field portion. This would cause an unwanted
449 // underflow
450 xub_StrLen nX = nBreakPos;
451 while( nX > rInf.GetLineStart() &&
452 ( CH_TXTATR_BREAKWORD != cFldChr || nX > rInf.GetIdx() ) &&
453 ( CH_BLANK == rInf.GetChar( --nX ) ||
454 CH_FULL_BLANK == rInf.GetChar( nX ) ) )
455 nBreakPos = nX;
456 }
457 if( nBreakPos > rInf.GetIdx() )
458 nPorLen = nBreakPos - rInf.GetIdx();
459 }
460 else
461 {
462 // no line break found, setting nBreakPos to STRING_LEN
463 // causes a break cut
464 nBreakPos = STRING_LEN;
465 ASSERT( nCutPos >= rInf.GetIdx(), "Deep cut" );
466 nPorLen = nCutPos - rInf.GetIdx();
467 }
468
469 if( nBreakPos > nCutPos && nBreakPos != STRING_LEN )
470 {
471 const xub_StrLen nHangingLen = nBreakPos - nCutPos;
472 SwPosSize aTmpSize = rInf.GetTxtSize( &rSI, nCutPos,
473 nHangingLen, 0 );
474 ASSERT( !pHanging, "A hanging portion is hanging around" );
475 pHanging = new SwHangingPortion( aTmpSize );
476 pHanging->SetLen( nHangingLen );
477 nPorLen = nCutPos - rInf.GetIdx();
478 }
479
480 // If we expanded a field, we must repair the original string.
481 // In case we do not trigger an underflow, we correct the nBreakPos
482 // value, but we cannot correct the nBreakStart value:
483 // If we have found a hyphenation position, nBreakStart can lie before
484 // the field.
485 if ( CH_TXTATR_BREAKWORD == cFldChr )
486 {
487 if ( nBreakPos < rInf.GetIdx() )
488 nBreakPos = nOldIdx - 1;
489 else if ( STRING_LEN != nBreakPos )
490 {
491 ASSERT( nBreakPos >= nFieldDiff, "I've got field trouble!" );
492 nBreakPos = nBreakPos - nFieldDiff;
493 }
494
495 ASSERT( nCutPos >= rInf.GetIdx() && nCutPos >= nFieldDiff,
496 "I've got field trouble, part2!" );
497 nCutPos = nCutPos - nFieldDiff;
498
499 XubString& rOldTxt = (XubString&)rInf.GetTxt();
500 rOldTxt.Erase( nOldIdx - 1, nFieldDiff + 1 );
501 rOldTxt.Insert( cFldChr, nOldIdx - 1 );
502 rInf.SetIdx( nOldIdx );
503
504 #if OSL_DEBUG_LEVEL > 1
505 ASSERT( aDebugString == rInf.GetTxt(),
506 "Somebody, somebody, somebody put something in my string" );
507 #endif
508 }
509 }
510
511 if( nPorLen )
512 {
513 rInf.GetTxtSize( &rSI, rInf.GetIdx(), nPorLen,
514 nMaxComp, nMinSize, nMaxSizeDiff );
515
516 // save maximum width for later use
517 if ( nMaxSizeDiff )
518 rInf.SetMaxWidthDiff( (sal_uLong)&rPor, nMaxSizeDiff );
519
520 nBreakWidth = nItalic + nMinSize;
521 }
522 else
523 nBreakWidth = 0;
524
525 if( pHanging )
526 nBreakPos = nCutPos;
527
528 return sal_False;
529 }
530
531 /*************************************************************************
532 * SwTxtGuess::AlternativeSpelling
533 *************************************************************************/
534
535 // returns true if word at position nPos has a diffenrent spelling
536 // if hyphenated at this position (old german spelling)
537
AlternativeSpelling(const SwTxtFormatInfo & rInf,const xub_StrLen nPos)538 sal_Bool SwTxtGuess::AlternativeSpelling( const SwTxtFormatInfo &rInf,
539 const xub_StrLen nPos )
540 {
541 // get word boundaries
542 xub_StrLen nWordLen;
543
544 Boundary aBound =
545 pBreakIt->GetBreakIter()->getWordBoundary( rInf.GetTxt(), nPos,
546 pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ),
547 WordType::DICTIONARY_WORD, sal_True );
548 nBreakStart = (xub_StrLen)aBound.startPos;
549 nWordLen = static_cast<xub_StrLen>(aBound.endPos - nBreakStart);
550
551 // if everything else fails, we want to cut at nPos
552 nCutPos = nPos;
553
554 XubString aTxt( rInf.GetTxt().Copy( nBreakStart, nWordLen ) );
555
556 // check, if word has alternative spelling
557 Reference< XHyphenator > xHyph( ::GetHyphenator() );
558 ASSERT( xHyph.is(), "Hyphenator is missing");
559 //! subtract 1 since the UNO-interface is 0 based
560 xHyphWord = xHyph->queryAlternativeSpelling( OUString(aTxt),
561 pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ),
562 nPos - nBreakStart, rInf.GetHyphValues() );
563 return xHyphWord.is() && xHyphWord->isAlternativeSpelling();
564 }
565
566