1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <breakit.hxx>
21 #include <editeng/unolingu.hxx>
22 #include <com/sun/star/i18n/WordType.hpp>
23 #include <com/sun/star/i18n/XBreakIterator.hpp>
24 #include <viewopt.hxx>
26 #include <SwPortionHandler.hxx>
27 #include "porhyph.hxx"
29 #include "itrform2.hxx"
31 #include <rootfrm.hxx>
33 using namespace ::com::sun::star
;
34 using namespace ::com::sun::star::uno
;
35 using namespace ::com::sun::star::beans
;
36 using namespace ::com::sun::star::linguistic2
;
37 using namespace ::com::sun::star::i18n
;
39 Reference
< XHyphenatedWord
> SwTextFormatInfo::HyphWord(
40 const OUString
&rText
, const sal_Int32 nMinTrail
)
42 if( rText
.getLength() < 4 || m_pFnt
->IsSymbol(m_pVsh
) )
44 Reference
< XHyphenator
> xHyph
= ::GetHyphenator();
45 Reference
< XHyphenatedWord
> xHyphWord
;
48 xHyphWord
= xHyph
->hyphenate( rText
,
49 g_pBreakIt
->GetLocale( m_pFnt
->GetLanguage() ),
50 rText
.getLength() - nMinTrail
, GetHyphValues() );
56 * We format a row for interactive hyphenation
58 bool SwTextFrame::Hyphenate(SwInterHyphInfoTextFrame
& rHyphInf
)
60 vcl::RenderContext
* pRenderContext
= getRootFrame()->GetCurrShell()->GetOut();
61 OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"swapped frame at SwTextFrame::Hyphenate" );
63 assert(g_pBreakIt
&& g_pBreakIt
->GetBreakIter().is());
65 // We lock it, to start with
66 OSL_ENSURE( !IsLocked(), "SwTextFrame::Hyphenate: this is locked" );
68 // The frame::Frame must have a valid SSize!
75 // We always need to enable hyphenation
76 // Don't be afraid: the SwTextIter saves the old row in the hyphenate
77 TextFrameLockGuard
aLock( this );
82 SwTextFormatInfo
aInf( getRootFrame()->GetCurrShell()->GetOut(), this, true ); // true for interactive hyph!
83 SwTextFormatter
aLine( this, &aInf
);
84 aLine
.CharToLine( rHyphInf
.m_nStart
);
86 // If we're within the first word of a row, it could've been hyphenated
87 // in the row earlier.
88 // That's why we go one row back.
91 SwLinePortion
*pPor
= aLine
.GetCurr()->GetFirstPortion();
92 while( pPor
->GetNextPortion() )
93 pPor
= pPor
->GetNextPortion();
94 if( pPor
->GetWhichPor() == PortionType::SoftHyphen
||
95 pPor
->GetWhichPor() == PortionType::SoftHyphenStr
)
99 const TextFrameIndex nEnd
= rHyphInf
.m_nEnd
;
100 while( !bRet
&& aLine
.GetStart() < nEnd
)
102 bRet
= aLine
.Hyphenate( rHyphInf
);
108 SwapWidthAndHeight();
114 * We format a row for interactive hyphenation
115 * We can assume that we've already formatted.
116 * We just reformat the row, the hyphenator will be prepared like
117 * the UI expects it to be.
118 * TODO: We can of course optimize this a lot.
120 void SetParaPortion( SwTextInfo
*pInf
, SwParaPortion
*pRoot
)
122 OSL_ENSURE( pRoot
, "SetParaPortion: no root anymore" );
123 pInf
->m_pPara
= pRoot
;
126 bool SwTextFormatter::Hyphenate(SwInterHyphInfoTextFrame
& rHyphInf
)
128 SwTextFormatInfo
&rInf
= GetInfo();
130 // We never need to hyphenate anything in the last row
131 // Except for, if it contains a FlyPortion or if it's the
132 // last row of the Master
133 if( !GetNext() && !rInf
.GetTextFly().IsOn() && !m_pFrame
->GetFollow() )
136 TextFrameIndex nWrdStart
= m_nStart
;
138 // We need to retain the old row
139 // E.g.: The attribute for hyphenation was not set, but
140 // it's always set in SwTextFrame::Hyphenate, because we want
141 // to set breakpoints.
142 SwLineLayout
*pOldCurr
= m_pCurr
;
146 // 5298: IsParaLine() (ex.IsFirstLine) calls GetParaPortion().
147 // We have to create the same conditions: in the first line
148 // we format SwParaPortions...
149 if( pOldCurr
->IsParaPortion() )
151 SwParaPortion
*pPara
= new SwParaPortion();
152 SetParaPortion( &rInf
, pPara
);
154 OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: not the first" );
157 m_pCurr
= new SwLineLayout();
159 nWrdStart
= FormatLine( nWrdStart
);
161 // You always should keep in mind that for example there are fields
162 // which can be hyphenated
163 if( m_pCurr
->PrtWidth() && m_pCurr
->GetLen() )
165 // We must be prepared that there are FlyFrames in the line,
166 // at which line breaking is possible. So we search for the first
167 // HyphPortion in the specified range.
169 SwLinePortion
*pPos
= m_pCurr
->GetNextPortion();
170 const TextFrameIndex nPamStart
= rHyphInf
.m_nStart
;
171 nWrdStart
= m_nStart
;
172 const TextFrameIndex nEnd
= rHyphInf
.m_nEnd
;
175 // Either we are above or we are running into a HyphPortion
176 // at the end of line or before a Fly.
177 if( nWrdStart
>= nEnd
)
179 nWrdStart
= TextFrameIndex(0);
183 if( nWrdStart
>= nPamStart
&& pPos
->InHyphGrp()
184 && ( !pPos
->IsSoftHyphPortion()
185 || static_cast<SwSoftHyphPortion
*>(pPos
)->IsExpand() ) )
187 nWrdStart
= nWrdStart
+ pPos
->GetLen();
191 nWrdStart
= nWrdStart
+ pPos
->GetLen();
192 pPos
= pPos
->GetNextPortion();
194 // When pPos is null, no hyphen position was found.
196 nWrdStart
= TextFrameIndex(0);
199 // In case the whole line is zero-length, that's the same situation as
200 // above when the portion iteration ends without explicitly breaking
202 nWrdStart
= TextFrameIndex(0);
204 // the old LineLayout is set again ...
208 if( pOldCurr
->IsParaPortion() )
210 SetParaPortion( &rInf
, static_cast<SwParaPortion
*>(pOldCurr
) );
211 OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: even not the first" );
214 if (nWrdStart
== TextFrameIndex(0))
217 // nWrdStart contains the position in string that should be hyphenated
218 rHyphInf
.m_nWordStart
= nWrdStart
;
220 TextFrameIndex
nLen(0);
221 const TextFrameIndex nEnd
= nWrdStart
;
223 // we search forwards
224 Reference
< XHyphenatedWord
> xHyphWord
;
226 Boundary
const aBound
= g_pBreakIt
->GetBreakIter()->getWordBoundary(
227 rInf
.GetText(), sal_Int32(nWrdStart
),
228 g_pBreakIt
->GetLocale( rInf
.GetFont()->GetLanguage() ), WordType::DICTIONARY_WORD
, true );
229 nWrdStart
= TextFrameIndex(aBound
.startPos
);
230 nLen
= TextFrameIndex(aBound
.endPos
) - nWrdStart
;
231 if (nLen
== TextFrameIndex(0))
234 OUString
const aSelText(rInf
.GetText().copy(sal_Int32(nWrdStart
), sal_Int32(nLen
)));
235 const sal_Int32 nMinTrail
= (nWrdStart
+ nLen
> nEnd
)
236 ? sal_Int32(nWrdStart
+ nLen
- nEnd
) - 1
239 //!! rHyphInf.SetHyphWord( ... ) must done here
240 xHyphWord
= rInf
.HyphWord( aSelText
, nMinTrail
);
241 if ( xHyphWord
.is() )
243 rHyphInf
.SetHyphWord( xHyphWord
);
244 rHyphInf
.m_nWordStart
= nWrdStart
;
245 rHyphInf
.m_nWordLen
= nLen
;
252 bool SwTextPortion::CreateHyphen( SwTextFormatInfo
&rInf
, SwTextGuess
const &rGuess
)
254 const Reference
< XHyphenatedWord
>& xHyphWord
= rGuess
.HyphWord();
256 OSL_ENSURE( !mpNextPortion
, "SwTextPortion::CreateHyphen(): another portion, another planet..." );
257 OSL_ENSURE( xHyphWord
.is(), "SwTextPortion::CreateHyphen(): You are lucky! The code is robust here." );
259 if( rInf
.IsHyphForbud() ||
260 mpNextPortion
|| // robust
261 !xHyphWord
.is() || // more robust
262 // multi-line fields can't be hyphenated interactively
263 ( rInf
.IsInterHyph() && InFieldGrp() ) )
266 std::unique_ptr
<SwHyphPortion
> pHyphPor
;
267 TextFrameIndex nPorEnd
;
268 SwTextSizeInfo
aInf( rInf
);
270 // first case: hyphenated word has alternative spelling
271 if ( xHyphWord
->isAlternativeSpelling() )
273 SvxAlternativeSpelling aAltSpell
= SvxGetAltSpelling( xHyphWord
);
274 OSL_ENSURE( aAltSpell
.bIsAltSpelling
, "no alternative spelling" );
276 OUString aAltText
= aAltSpell
.aReplacement
;
277 nPorEnd
= TextFrameIndex(aAltSpell
.nChangedPos
) + rGuess
.BreakStart() - rGuess
.FieldDiff();
278 sal_Int32 nTmpLen
= 0;
280 // soft hyphen at alternative spelling position?
281 if( rInf
.GetText()[sal_Int32(rInf
.GetSoftHyphPos())] == CHAR_SOFTHYPHEN
)
283 pHyphPor
.reset(new SwSoftHyphStrPortion( aAltText
));
287 pHyphPor
.reset(new SwHyphStrPortion( aAltText
));
290 // length of pHyphPor is adjusted
291 pHyphPor
->SetLen( TextFrameIndex(aAltText
.getLength() + 1) );
292 static_cast<SwPosSize
&>(*pHyphPor
) = pHyphPor
->GetTextSize( rInf
);
293 pHyphPor
->SetLen( TextFrameIndex(aAltSpell
.nChangedLength
+ nTmpLen
) );
297 // second case: no alternative spelling
298 pHyphPor
.reset(new SwHyphPortion
);
299 pHyphPor
->SetLen(TextFrameIndex(1));
301 static const void* nLastFontCacheId
= nullptr;
302 static sal_uInt16 aMiniCacheH
= 0, aMiniCacheW
= 0;
303 const void* nTmpFontCacheId
;
305 rInf
.GetFont()->GetFontCacheId( nTmpFontCacheId
, nFntIdx
, rInf
.GetFont()->GetActual() );
306 if( !nLastFontCacheId
|| nLastFontCacheId
!= nTmpFontCacheId
) {
307 nLastFontCacheId
= nTmpFontCacheId
;
308 static_cast<SwPosSize
&>(*pHyphPor
) = pHyphPor
->GetTextSize( rInf
);
309 aMiniCacheH
= pHyphPor
->Height();
310 aMiniCacheW
= pHyphPor
->Width();
312 pHyphPor
->Height( aMiniCacheH
);
313 pHyphPor
->Width( aMiniCacheW
);
315 pHyphPor
->SetLen(TextFrameIndex(0));
317 // values required for this
318 nPorEnd
= TextFrameIndex(xHyphWord
->getHyphenPos() + 1)
319 + rGuess
.BreakStart() - rGuess
.FieldDiff();
322 // portion end must be in front of us
323 // we do not put hyphens at start of line
324 if ( nPorEnd
> rInf
.GetIdx() ||
325 ( nPorEnd
== rInf
.GetIdx() && rInf
.GetLineStart() != rInf
.GetIdx() ) )
327 aInf
.SetLen( nPorEnd
- rInf
.GetIdx() );
328 pHyphPor
->SetAscent( GetAscent() );
329 SetLen( aInf
.GetLen() );
330 CalcTextSize( aInf
);
332 Insert( pHyphPor
.release() );
334 short nKern
= rInf
.GetFont()->CheckKerning();
336 new SwKernPortion( *this, nKern
);
341 // last exit for the lost
343 BreakCut( rInf
, rGuess
);
347 bool SwHyphPortion::GetExpText( const SwTextSizeInfo
&/*rInf*/, OUString
&rText
) const
353 void SwHyphPortion::HandlePortion( SwPortionHandler
& rPH
) const
355 rPH
.Special( GetLen(), OUString('-'), GetWhichPor() );
358 void SwHyphPortion::dumpAsXml(xmlTextWriterPtr pWriter
, const OUString
& rText
,
359 TextFrameIndex
& nOffset
) const
361 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwHyphPortion"));
362 dumpAsXmlAttributes(pWriter
, rText
, nOffset
);
365 (void)xmlTextWriterEndElement(pWriter
);
368 bool SwHyphPortion::Format( SwTextFormatInfo
&rInf
)
370 const SwLinePortion
*pLast
= rInf
.GetLast();
371 Height( pLast
->Height() );
372 SetAscent( pLast
->GetAscent() );
375 if( !GetExpText( rInf
, aText
) )
378 PrtWidth( rInf
.GetTextSize( aText
).Width() );
379 const bool bFull
= rInf
.Width() <= rInf
.X() + PrtWidth();
380 if( bFull
&& !rInf
.IsUnderflow() ) {
382 rInf
.SetUnderflow( this );
388 bool SwHyphStrPortion::GetExpText( const SwTextSizeInfo
&, OUString
&rText
) const
394 void SwHyphStrPortion::HandlePortion( SwPortionHandler
& rPH
) const
396 rPH
.Special( GetLen(), m_aExpand
, GetWhichPor() );
399 SwLinePortion
*SwSoftHyphPortion::Compress() { return this; }
401 SwSoftHyphPortion::SwSoftHyphPortion() :
402 m_bExpand(false), m_nViewWidth(0)
404 SetLen(TextFrameIndex(1));
405 SetWhichPor( PortionType::SoftHyphen
);
408 sal_uInt16
SwSoftHyphPortion::GetViewWidth( const SwTextSizeInfo
&rInf
) const
410 // Although we're in the const, nViewWidth should be calculated at
411 // the last possible moment
412 if( !Width() && rInf
.OnWin() && rInf
.GetOpt().IsSoftHyph() && !IsExpand() )
415 const_cast<SwSoftHyphPortion
*>(this)->m_nViewWidth
416 = rInf
.GetTextSize(OUString('-')).Width();
419 const_cast<SwSoftHyphPortion
*>(this)->m_nViewWidth
= 0;
426 * 1) SoftHyph is in the line, ViewOpt off
427 * -> invisible, neighbors unchanged
428 * 2) SoftHyph is in the line, ViewOpt on
429 * -> visible, neighbors unchanged
430 * 3) SoftHyph is at the end of the line, ViewOpt on or off
431 * -> always visible, neighbors unchanged
433 void SwSoftHyphPortion::Paint( const SwTextPaintInfo
&rInf
) const
437 rInf
.DrawViewOpt( *this, PortionType::SoftHyphen
);
438 SwExpandPortion::Paint( rInf
);
443 * We get the final width from the FormatEOL()
445 * During the underflow-phase we determine, whether or not
446 * there's an alternative spelling at all ...
449 * 1) {Au}{-}{to}, {to} does not fit anymore => underflow
450 * 2) {-} calls hyphenate => no alternative
451 * 3) FormatEOL() and bFull = true
454 * 1) {Zuc}{-}{ker}, {ker} does not fit anymore => underflow
455 * 2) {-} calls hyphenate => alternative!
456 * 3) Underflow() and bFull = true
457 * 4) {Zuc} calls hyphenate => {Zuk}{-}{ker}
459 bool SwSoftHyphPortion::Format( SwTextFormatInfo
&rInf
)
463 // special case for old German spelling
464 if( rInf
.IsUnderflow() )
466 if( rInf
.GetSoftHyphPos() )
469 const bool bHyph
= rInf
.ChgHyph( true );
470 if( rInf
.IsHyphenate() )
472 rInf
.SetSoftHyphPos( rInf
.GetIdx() );
474 // if the soft hyphened word has an alternative spelling
475 // when hyphenated (old German spelling), the soft hyphen
476 // portion has to trigger an underflow
478 bFull
= rInf
.IsInterHyph() ||
479 !aGuess
.AlternativeSpelling(rInf
, rInf
.GetIdx() - TextFrameIndex(1));
481 rInf
.ChgHyph( bHyph
);
483 if( bFull
&& !rInf
.IsHyphForbud() )
485 rInf
.SetSoftHyphPos(TextFrameIndex(0));
488 rInf
.GetRoot()->SetMidHyph( true );
490 rInf
.GetRoot()->SetEndHyph( true );
494 rInf
.SetSoftHyphPos( rInf
.GetIdx() );
496 rInf
.SetUnderflow( this );
501 rInf
.SetSoftHyphPos(TextFrameIndex(0));
503 bFull
= SwHyphPortion::Format( rInf
);
507 // By default, we do not have a width, but we do have a height
516 void SwSoftHyphPortion::FormatEOL( SwTextFormatInfo
&rInf
)
522 if( rInf
.GetLast() == this )
523 rInf
.SetLast( FindPrevPortion( rInf
.GetRoot() ) );
525 // We need to reset the old values
526 const SwTwips nOldX
= rInf
.X();
527 TextFrameIndex
const nOldIdx
= rInf
.GetIdx();
528 rInf
.X( rInf
.X() - PrtWidth() );
529 rInf
.SetIdx( rInf
.GetIdx() - GetLen() );
530 const bool bFull
= SwHyphPortion::Format( rInf
);
532 // Shady business: We're allowed to get wider, but a Fly is also
533 // being processed, which needs a correct X position
534 if( bFull
|| !rInf
.GetFly() )
537 rInf
.X( nOldX
+ Width() );
538 rInf
.SetIdx( nOldIdx
);
543 * - if the special characters should be visible
544 * - if we're at the end of the line
545 * - if we're before a (real/emulated) line break
547 bool SwSoftHyphPortion::GetExpText( const SwTextSizeInfo
&rInf
, OUString
&rText
) const
549 if( IsExpand() || ( rInf
.OnWin() && rInf
.GetOpt().IsSoftHyph() ) ||
550 ( GetNextPortion() && ( GetNextPortion()->InFixGrp() ||
551 GetNextPortion()->IsDropPortion() || GetNextPortion()->IsLayPortion() ||
552 GetNextPortion()->IsParaPortion() || GetNextPortion()->IsBreakPortion() ) ) )
554 return SwHyphPortion::GetExpText( rInf
, rText
);
559 void SwSoftHyphPortion::HandlePortion( SwPortionHandler
& rPH
) const
561 const PortionType nWhich
= ! Width() ?
562 PortionType::SoftHyphenComp
:
564 rPH
.Special( GetLen(), OUString('-'), nWhich
);
567 void SwSoftHyphStrPortion::Paint( const SwTextPaintInfo
&rInf
) const
570 // {Zu}{k-}{ker}, {k-} will be gray instead of {-}
571 rInf
.DrawViewOpt( *this, PortionType::SoftHyphen
);
572 SwHyphStrPortion::Paint( rInf
);
575 SwSoftHyphStrPortion::SwSoftHyphStrPortion( std::u16string_view rStr
)
576 : SwHyphStrPortion( rStr
)
578 SetLen(TextFrameIndex(1));
579 SetWhichPor( PortionType::SoftHyphenStr
);
582 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */