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::linguistic2
;
36 using namespace ::com::sun::star::i18n
;
38 Reference
< XHyphenatedWord
> SwTextFormatInfo::HyphWord(
39 const OUString
&rText
, const sal_Int32 nMinTrail
)
41 if( rText
.getLength() < 4 || m_pFnt
->IsSymbol(m_pVsh
) )
43 Reference
< XHyphenator
> xHyph
= ::GetHyphenator();
44 Reference
< XHyphenatedWord
> xHyphWord
;
47 xHyphWord
= xHyph
->hyphenate( rText
,
48 g_pBreakIt
->GetLocale( m_pFnt
->GetLanguage() ),
49 rText
.getLength() - nMinTrail
, GetHyphValues() );
55 * We format a row for interactive hyphenation
57 bool SwTextFrame::Hyphenate(SwInterHyphInfoTextFrame
& rHyphInf
)
59 vcl::RenderContext
* pRenderContext
= getRootFrame()->GetCurrShell()->GetOut();
60 OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"swapped frame at SwTextFrame::Hyphenate" );
62 assert(g_pBreakIt
&& g_pBreakIt
->GetBreakIter().is());
64 // We lock it, to start with
65 OSL_ENSURE( !IsLocked(), "SwTextFrame::Hyphenate: this is locked" );
67 // The frame::Frame must have a valid SSize!
74 // We always need to enable hyphenation
75 // Don't be afraid: the SwTextIter saves the old row in the hyphenate
76 TextFrameLockGuard
aLock( this );
81 SwTextFormatInfo
aInf( getRootFrame()->GetCurrShell()->GetOut(), this, true ); // true for interactive hyph!
82 SwTextFormatter
aLine( this, &aInf
);
83 aLine
.CharToLine( rHyphInf
.m_nStart
);
85 // If we're within the first word of a row, it could've been hyphenated
86 // in the row earlier.
87 // That's why we go one row back.
90 SwLinePortion
*pPor
= aLine
.GetCurr()->GetFirstPortion();
91 while( pPor
->GetNextPortion() )
92 pPor
= pPor
->GetNextPortion();
93 if( pPor
->GetWhichPor() == PortionType::SoftHyphen
||
94 pPor
->GetWhichPor() == PortionType::SoftHyphenStr
)
98 const TextFrameIndex nEnd
= rHyphInf
.m_nEnd
;
99 while( !bRet
&& aLine
.GetStart() < nEnd
)
101 bRet
= aLine
.Hyphenate( rHyphInf
);
107 SwapWidthAndHeight();
113 * We format a row for interactive hyphenation
114 * We can assume that we've already formatted.
115 * We just reformat the row, the hyphenator will be prepared like
116 * the UI expects it to be.
117 * TODO: We can of course optimize this a lot.
119 void SetParaPortion( SwTextInfo
*pInf
, SwParaPortion
*pRoot
)
121 OSL_ENSURE( pRoot
, "SetParaPortion: no root anymore" );
122 pInf
->m_pPara
= pRoot
;
125 bool SwTextFormatter::Hyphenate(SwInterHyphInfoTextFrame
& rHyphInf
)
127 SwTextFormatInfo
&rInf
= GetInfo();
129 // We never need to hyphenate anything in the last row
130 // Except for, if it contains a FlyPortion or if it's the
131 // last row of the Master
132 if( !GetNext() && !rInf
.GetTextFly().IsOn() && !m_pFrame
->GetFollow() )
135 TextFrameIndex nWrdStart
= m_nStart
;
137 // We need to retain the old row
138 // E.g.: The attribute for hyphenation was not set, but
139 // it's always set in SwTextFrame::Hyphenate, because we want
140 // to set breakpoints.
141 SwLineLayout
*pOldCurr
= m_pCurr
;
145 // 5298: IsParaLine() (ex.IsFirstLine) calls GetParaPortion().
146 // We have to create the same conditions: in the first line
147 // we format SwParaPortions...
148 if( pOldCurr
->IsParaPortion() )
150 SwParaPortion
*pPara
= new SwParaPortion();
151 SetParaPortion( &rInf
, pPara
);
153 OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: not the first" );
156 m_pCurr
= new SwLineLayout();
158 nWrdStart
= FormatLine( nWrdStart
);
160 // You always should keep in mind that for example there are fields
161 // which can be hyphenated
162 if( m_pCurr
->PrtWidth() && m_pCurr
->GetLen() )
164 // We must be prepared that there are FlyFrames in the line,
165 // at which line breaking is possible. So we search for the first
166 // HyphPortion in the specified range.
168 SwLinePortion
*pPos
= m_pCurr
->GetNextPortion();
169 const TextFrameIndex nPamStart
= rHyphInf
.m_nStart
;
170 nWrdStart
= m_nStart
;
171 const TextFrameIndex nEnd
= rHyphInf
.m_nEnd
;
174 // Either we are above or we are running into a HyphPortion
175 // at the end of line or before a Fly.
176 if( nWrdStart
>= nEnd
)
178 nWrdStart
= TextFrameIndex(0);
182 if( nWrdStart
>= nPamStart
&& pPos
->InHyphGrp()
183 && ( !pPos
->IsSoftHyphPortion()
184 || static_cast<SwSoftHyphPortion
*>(pPos
)->IsExpand() ) )
186 nWrdStart
= nWrdStart
+ pPos
->GetLen();
190 nWrdStart
= nWrdStart
+ pPos
->GetLen();
191 pPos
= pPos
->GetNextPortion();
193 // When pPos is null, no hyphen position was found.
195 nWrdStart
= TextFrameIndex(0);
198 // In case the whole line is zero-length, that's the same situation as
199 // above when the portion iteration ends without explicitly breaking
201 nWrdStart
= TextFrameIndex(0);
203 // the old LineLayout is set again ...
207 if( pOldCurr
->IsParaPortion() )
209 SetParaPortion( &rInf
, static_cast<SwParaPortion
*>(pOldCurr
) );
210 OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: even not the first" );
213 if (nWrdStart
== TextFrameIndex(0))
216 // nWrdStart contains the position in string that should be hyphenated
217 rHyphInf
.m_nWordStart
= nWrdStart
;
219 TextFrameIndex
nLen(0);
220 const TextFrameIndex nEnd
= nWrdStart
;
222 // we search forwards
223 Reference
< XHyphenatedWord
> xHyphWord
;
225 Boundary
const aBound
= g_pBreakIt
->GetBreakIter()->getWordBoundary(
226 rInf
.GetText(), sal_Int32(nWrdStart
),
227 g_pBreakIt
->GetLocale( rInf
.GetFont()->GetLanguage() ), WordType::DICTIONARY_WORD
, true );
228 nWrdStart
= TextFrameIndex(aBound
.startPos
);
229 nLen
= TextFrameIndex(aBound
.endPos
) - nWrdStart
;
230 if (nLen
== TextFrameIndex(0))
233 OUString
const aSelText(rInf
.GetText().copy(sal_Int32(nWrdStart
), sal_Int32(nLen
)));
234 const sal_Int32 nMinTrail
= (nWrdStart
+ nLen
> nEnd
)
235 ? sal_Int32(nWrdStart
+ nLen
- nEnd
) - 1
238 //!! rHyphInf.SetHyphWord( ... ) must done here
239 xHyphWord
= rInf
.HyphWord( aSelText
, nMinTrail
);
240 if ( xHyphWord
.is() )
242 rHyphInf
.SetHyphWord( xHyphWord
);
243 rHyphInf
.m_nWordStart
= nWrdStart
;
244 rHyphInf
.m_nWordLen
= nLen
;
251 bool SwTextPortion::CreateHyphen( SwTextFormatInfo
&rInf
, SwTextGuess
const &rGuess
)
253 const Reference
< XHyphenatedWord
>& xHyphWord
= rGuess
.HyphWord();
255 OSL_ENSURE( !mpNextPortion
, "SwTextPortion::CreateHyphen(): another portion, another planet..." );
256 OSL_ENSURE( xHyphWord
.is(), "SwTextPortion::CreateHyphen(): You are lucky! The code is robust here." );
258 if( rInf
.IsHyphForbud() ||
259 mpNextPortion
|| // robust
260 !xHyphWord
.is() || // more robust
261 // multi-line fields can't be hyphenated interactively
262 ( rInf
.IsInterHyph() && InFieldGrp() ) )
265 std::unique_ptr
<SwHyphPortion
> pHyphPor
;
266 TextFrameIndex nPorEnd
;
267 SwTextSizeInfo
aInf( rInf
);
269 // first case: hyphenated word has alternative spelling
270 if ( xHyphWord
->isAlternativeSpelling() )
272 SvxAlternativeSpelling aAltSpell
= SvxGetAltSpelling( xHyphWord
);
273 OSL_ENSURE( aAltSpell
.bIsAltSpelling
, "no alternative spelling" );
275 OUString aAltText
= aAltSpell
.aReplacement
;
276 nPorEnd
= TextFrameIndex(aAltSpell
.nChangedPos
) + rGuess
.BreakStart() - rGuess
.FieldDiff();
277 sal_Int32 nTmpLen
= 0;
279 // soft hyphen at alternative spelling position?
280 if( rInf
.GetText()[sal_Int32(rInf
.GetSoftHyphPos())] == CHAR_SOFTHYPHEN
)
282 pHyphPor
.reset(new SwSoftHyphStrPortion( aAltText
));
286 pHyphPor
.reset(new SwHyphStrPortion( aAltText
));
289 // length of pHyphPor is adjusted
290 pHyphPor
->SetLen( TextFrameIndex(aAltText
.getLength() + 1) );
291 static_cast<SwPosSize
&>(*pHyphPor
) = pHyphPor
->GetTextSize( rInf
);
292 pHyphPor
->SetLen( TextFrameIndex(aAltSpell
.nChangedLength
+ nTmpLen
) );
296 // second case: no alternative spelling
297 pHyphPor
.reset(new SwHyphPortion
);
298 pHyphPor
->SetLen(TextFrameIndex(1));
300 static const void* nLastFontCacheId
= nullptr;
301 static SwTwips aMiniCacheH
= 0, aMiniCacheW
= 0;
302 const void* nTmpFontCacheId
;
304 rInf
.GetFont()->GetFontCacheId( nTmpFontCacheId
, nFntIdx
, rInf
.GetFont()->GetActual() );
305 if( !nLastFontCacheId
|| nLastFontCacheId
!= nTmpFontCacheId
) {
306 nLastFontCacheId
= nTmpFontCacheId
;
307 static_cast<SwPosSize
&>(*pHyphPor
) = pHyphPor
->GetTextSize( rInf
);
308 aMiniCacheH
= pHyphPor
->Height();
309 aMiniCacheW
= pHyphPor
->Width();
311 pHyphPor
->Height( aMiniCacheH
);
312 pHyphPor
->Width( aMiniCacheW
);
314 pHyphPor
->SetLen(TextFrameIndex(0));
316 // values required for this
317 nPorEnd
= TextFrameIndex(xHyphWord
->getHyphenPos() + 1)
318 + rGuess
.BreakStart() - rGuess
.FieldDiff();
321 // portion end must be in front of us
322 // we do not put hyphens at start of line
323 if ( nPorEnd
> rInf
.GetIdx() ||
324 ( nPorEnd
== rInf
.GetIdx() && rInf
.GetLineStart() != rInf
.GetIdx() ) )
326 aInf
.SetLen( nPorEnd
- rInf
.GetIdx() );
327 if (auto stClampedContext
= aInf
.GetLayoutContext(); stClampedContext
.has_value())
329 stClampedContext
->m_nEnd
= nPorEnd
.get();
330 aInf
.SetLayoutContext(stClampedContext
);
333 pHyphPor
->SetAscent( GetAscent() );
334 SetLen( aInf
.GetLen() );
335 SetLayoutContext(aInf
.GetLayoutContext());
336 CalcTextSize( aInf
);
338 Insert( pHyphPor
.release() );
340 short nKern
= rInf
.GetFont()->CheckKerning();
342 new SwKernPortion( *this, nKern
);
347 // last exit for the lost
349 BreakCut( rInf
, rGuess
);
353 bool SwHyphPortion::GetExpText( const SwTextSizeInfo
&/*rInf*/, OUString
&rText
) const
359 void SwHyphPortion::HandlePortion( SwPortionHandler
& rPH
) const
361 rPH
.Special( GetLen(), OUString('-'), GetWhichPor() );
364 void SwHyphPortion::dumpAsXml(xmlTextWriterPtr pWriter
, const OUString
& rText
,
365 TextFrameIndex
& nOffset
) const
367 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwHyphPortion"));
368 dumpAsXmlAttributes(pWriter
, rText
, nOffset
);
371 (void)xmlTextWriterEndElement(pWriter
);
374 bool SwHyphPortion::Format( SwTextFormatInfo
&rInf
)
376 const SwLinePortion
*pLast
= rInf
.GetLast();
377 Height( pLast
->Height() );
378 SetAscent( pLast
->GetAscent() );
381 if( !GetExpText( rInf
, aText
) )
384 PrtWidth( rInf
.GetTextSize( aText
).Width() );
385 const bool bFull
= rInf
.Width() <= rInf
.X() + PrtWidth();
386 if( bFull
&& !rInf
.IsUnderflow() ) {
388 rInf
.SetUnderflow( this );
394 bool SwHyphStrPortion::GetExpText( const SwTextSizeInfo
&, OUString
&rText
) const
400 void SwHyphStrPortion::HandlePortion( SwPortionHandler
& rPH
) const
402 rPH
.Special( GetLen(), m_aExpand
, GetWhichPor() );
405 SwLinePortion
*SwSoftHyphPortion::Compress() { return this; }
407 SwSoftHyphPortion::SwSoftHyphPortion() :
408 m_bExpand(false), m_nViewWidth(0)
410 SetLen(TextFrameIndex(1));
411 SetWhichPor( PortionType::SoftHyphen
);
414 SwTwips
SwSoftHyphPortion::GetViewWidth(const SwTextSizeInfo
& rInf
) const
416 // Although we're in the const, nViewWidth should be calculated at
417 // the last possible moment
418 if( !Width() && rInf
.OnWin() && rInf
.GetOpt().IsSoftHyph() && !IsExpand() )
421 const_cast<SwSoftHyphPortion
*>(this)->m_nViewWidth
422 = rInf
.GetTextSize(OUString('-')).Width();
425 const_cast<SwSoftHyphPortion
*>(this)->m_nViewWidth
= 0;
432 * 1) SoftHyph is in the line, ViewOpt off
433 * -> invisible, neighbors unchanged
434 * 2) SoftHyph is in the line, ViewOpt on
435 * -> visible, neighbors unchanged
436 * 3) SoftHyph is at the end of the line, ViewOpt on or off
437 * -> always visible, neighbors unchanged
439 void SwSoftHyphPortion::Paint( const SwTextPaintInfo
&rInf
) const
443 rInf
.DrawViewOpt( *this, PortionType::SoftHyphen
);
444 SwExpandPortion::Paint( rInf
);
446 if (rInf
.GetOpt().IsViewMetaChars() && !rInf
.GetOpt().IsPrinting()
447 && !rInf
.GetOpt().IsPDFExport())
449 OUString aMarker
= u
"-"_ustr
;
450 SwTextPaintInfo
aInf(rInf
, &aMarker
);
451 SwTextPortion aMarkerPor
;
452 SwPosSize
aMarkerSize(rInf
.GetTextSize(aMarker
));
453 aMarkerPor
.Width(aMarkerSize
.Width());
454 aMarkerPor
.Height(aMarkerSize
.Height());
455 aMarkerPor
.SetAscent(GetAscent());
457 Color colorBackup
= aInf
.GetFont()->GetColor();
458 aInf
.GetFont()->SetColor(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor());
459 aInf
.DrawText(aMarkerPor
, TextFrameIndex(aMarker
.getLength()), true);
460 aInf
.GetFont()->SetColor(colorBackup
);
466 * We get the final width from the FormatEOL()
468 * During the underflow-phase we determine, whether or not
469 * there's an alternative spelling at all ...
472 * 1) {Au}{-}{to}, {to} does not fit anymore => underflow
473 * 2) {-} calls hyphenate => no alternative
474 * 3) FormatEOL() and bFull = true
477 * 1) {Zuc}{-}{ker}, {ker} does not fit anymore => underflow
478 * 2) {-} calls hyphenate => alternative!
479 * 3) Underflow() and bFull = true
480 * 4) {Zuc} calls hyphenate => {Zuk}{-}{ker}
482 bool SwSoftHyphPortion::Format( SwTextFormatInfo
&rInf
)
486 // special case for old German spelling
487 if( rInf
.IsUnderflow() )
489 if( rInf
.GetSoftHyphPos() )
492 const bool bHyph
= rInf
.ChgHyph( true );
493 if( rInf
.IsHyphenate() )
495 rInf
.SetSoftHyphPos( rInf
.GetIdx() );
497 // if the soft hyphened word has an alternative spelling
498 // when hyphenated (old German spelling), the soft hyphen
499 // portion has to trigger an underflow
501 bFull
= rInf
.IsInterHyph() ||
502 !aGuess
.AlternativeSpelling(rInf
, rInf
.GetIdx() - TextFrameIndex(1));
504 rInf
.ChgHyph( bHyph
);
506 if( bFull
&& !rInf
.IsHyphForbud() )
508 rInf
.SetSoftHyphPos(TextFrameIndex(0));
511 rInf
.GetRoot()->SetMidHyph( true );
513 rInf
.GetRoot()->SetEndHyph( true );
517 rInf
.SetSoftHyphPos( rInf
.GetIdx() );
519 rInf
.SetUnderflow( this );
524 rInf
.SetSoftHyphPos(TextFrameIndex(0));
526 bFull
= SwHyphPortion::Format( rInf
);
530 // By default, we do not have a width, but we do have a height
539 void SwSoftHyphPortion::FormatEOL( SwTextFormatInfo
&rInf
)
545 if( rInf
.GetLast() == this )
546 rInf
.SetLast( FindPrevPortion( rInf
.GetRoot() ) );
548 // We need to reset the old values
549 const SwTwips nOldX
= rInf
.X();
550 TextFrameIndex
const nOldIdx
= rInf
.GetIdx();
551 rInf
.X( rInf
.X() - PrtWidth() );
552 rInf
.SetIdx( rInf
.GetIdx() - GetLen() );
553 const bool bFull
= SwHyphPortion::Format( rInf
);
555 // Shady business: We're allowed to get wider, but a Fly is also
556 // being processed, which needs a correct X position
557 if( bFull
|| !rInf
.GetFly() )
560 rInf
.X( nOldX
+ Width() );
561 rInf
.SetIdx( nOldIdx
);
566 * - if the special characters should be visible
567 * - if we're at the end of the line
568 * - if we're before a (real/emulated) line break
570 bool SwSoftHyphPortion::GetExpText( const SwTextSizeInfo
&rInf
, OUString
&rText
) const
572 if( IsExpand() || ( rInf
.OnWin() && rInf
.GetOpt().IsSoftHyph() ) ||
573 ( GetNextPortion() && ( GetNextPortion()->InFixGrp() ||
574 GetNextPortion()->IsDropPortion() || GetNextPortion()->IsLayPortion() ||
575 GetNextPortion()->IsParaPortion() || GetNextPortion()->IsBreakPortion() ) ) )
577 return SwHyphPortion::GetExpText( rInf
, rText
);
582 void SwSoftHyphPortion::HandlePortion( SwPortionHandler
& rPH
) const
584 const PortionType nWhich
= ! Width() ?
585 PortionType::SoftHyphenComp
:
587 rPH
.Special( GetLen(), OUString('-'), nWhich
);
590 void SwSoftHyphStrPortion::Paint( const SwTextPaintInfo
&rInf
) const
593 // {Zu}{k-}{ker}, {k-} will be gray instead of {-}
594 rInf
.DrawViewOpt( *this, PortionType::SoftHyphen
);
595 SwHyphStrPortion::Paint( rInf
);
598 SwSoftHyphStrPortion::SwSoftHyphStrPortion( std::u16string_view rStr
)
599 : SwHyphStrPortion( rStr
)
601 SetLen(TextFrameIndex(1));
602 SetWhichPor( PortionType::SoftHyphenStr
);
605 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */