1066 lines
38 KiB
C++
1066 lines
38 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
#ifndef INCLUDED_SW_SOURCE_CORE_INC_TXTFRM_HXX
|
|
#define INCLUDED_SW_SOURCE_CORE_INC_TXTFRM_HXX
|
|
|
|
#include <com/sun/star/uno/Sequence.hxx>
|
|
#include <svl/ctloptions.hxx>
|
|
#include "cntfrm.hxx"
|
|
#include "TextFrameIndex.hxx"
|
|
#include <nodeoffset.hxx>
|
|
|
|
#include <set>
|
|
#include <utility>
|
|
|
|
#include <view.hxx>
|
|
|
|
namespace com::sun::star::linguistic2 { class XHyphenatedWord; }
|
|
|
|
namespace sw::mark { class MarkBase; }
|
|
namespace sw { enum class ParagraphBreakMode; }
|
|
class SwCharRange;
|
|
class SwTextNode;
|
|
class SwTextAttrEnd;
|
|
class SwTextFormatter;
|
|
class SwTextFormatInfo;
|
|
class SwParaPortion;
|
|
class WidowsAndOrphans;
|
|
class SwTextFootnote;
|
|
class SwInterHyphInfo; // Hyphenate()
|
|
class SwCache;
|
|
class SwBorderAttrs;
|
|
class SwFrameFormat;
|
|
struct SwCursorMoveState;
|
|
struct SwFillData;
|
|
class SwPortionHandler;
|
|
class SwScriptInfo;
|
|
enum class ExpandMode;
|
|
class SwTextAttr;
|
|
class SwViewShell;
|
|
class SwWrtShell;
|
|
class SwNode;
|
|
class SwFlyAtContentFrame;
|
|
|
|
/// a clone of SwInterHyphInfo, but with TextFrameIndex instead of node index
|
|
class SwInterHyphInfoTextFrame
|
|
{
|
|
private:
|
|
/// output: hyphenated word
|
|
css::uno::Reference<css::linguistic2::XHyphenatedWord> m_xHyphWord;
|
|
public:
|
|
/// input: requested range to hyphenate
|
|
TextFrameIndex m_nStart;
|
|
TextFrameIndex m_nEnd;
|
|
/// output: found word
|
|
TextFrameIndex m_nWordStart;
|
|
TextFrameIndex m_nWordLen;
|
|
|
|
SwInterHyphInfoTextFrame(SwTextFrame const& rFrame,
|
|
SwTextNode const& rNode, SwInterHyphInfo const& rHyphInfo);
|
|
void UpdateTextNodeHyphInfo(SwTextFrame const& rFrame,
|
|
SwTextNode const& rNode, SwInterHyphInfo & o_rHyphInfo);
|
|
|
|
void SetHyphWord(const css::uno::Reference<css::linguistic2::XHyphenatedWord> &xHW)
|
|
{
|
|
m_xHyphWord = xHW;
|
|
}
|
|
};
|
|
|
|
namespace sw {
|
|
|
|
/**
|
|
* Describes a part of a single text node, which will be part of a text frame,
|
|
* even when redlines are hidden at a layout level.
|
|
*/
|
|
struct Extent
|
|
{
|
|
SwTextNode * /*const logically, but need assignment for std::vector*/ pNode;
|
|
sal_Int32 nStart;
|
|
sal_Int32 nEnd;
|
|
Extent(SwTextNode *const p, sal_Int32 const s, sal_Int32 const e)
|
|
: pNode(p), nStart(s), nEnd(e)
|
|
{
|
|
assert(pNode);
|
|
assert(nStart != nEnd);
|
|
}
|
|
};
|
|
|
|
struct MergedPara;
|
|
class InsertText;
|
|
|
|
std::pair<SwTextNode*, sal_Int32> MapViewToModel(MergedPara const&, TextFrameIndex nIndex);
|
|
TextFrameIndex MapModelToView(MergedPara const&, SwTextNode const* pNode, sal_Int32 nIndex);
|
|
|
|
bool IsShowHiddenChars(SwViewShell const*const pViewShell);
|
|
void FindParaPropsNodeIgnoreHidden(MergedPara & rMerged,
|
|
sw::ParagraphBreakMode const eMode, SwScriptInfo * pScriptInfo);
|
|
|
|
// warning: Existing must be used only once; a second use would delete the frame created by the first one...
|
|
enum class FrameMode { New, Existing };
|
|
std::unique_ptr<sw::MergedPara> CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, FrameMode eMode);
|
|
SwTextFrame * MakeTextFrame(SwTextNode & rNode, SwFrame *, sw::FrameMode eMode);
|
|
|
|
bool FrameContainsNode(SwContentFrame const& rFrame, SwNodeOffset nNodeIndex);
|
|
bool IsParaPropsNode(SwRootFrame const& rLayout, SwTextNode const& rNode);
|
|
SwTextNode * GetParaPropsNode(SwRootFrame const& rLayout, SwNode const& rNode);
|
|
SwPosition GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos);
|
|
std::pair<SwTextNode *, SwTextNode *>
|
|
GetFirstAndLastNode(SwRootFrame const& rLayout, SwNode const& rPos);
|
|
|
|
SwTextNode const& GetAttrMerged(SfxItemSet & rFormatSet,
|
|
SwTextNode const& rNode, SwRootFrame const* pLayout);
|
|
|
|
void GotoPrevLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const* pLayout);
|
|
void GotoNextLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const* pLayout);
|
|
|
|
TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged,
|
|
sw::ParagraphBreakMode eMode, SwScriptInfo * pScriptInfo,
|
|
bool isRealDelete,
|
|
SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 nLen);
|
|
|
|
void MoveMergedFlysAndFootnotes(std::vector<SwTextFrame*> const& rFrames,
|
|
SwTextNode const& rFirstNode, SwTextNode & rSecondNode, bool);
|
|
|
|
void MoveDeletedPrevFrames(const SwTextNode & rDeletedPrev, SwTextNode & rNode);
|
|
enum class Recreate { No, ThisNode, Predecessor };
|
|
void CheckResetRedlineMergeFlag(SwTextNode & rNode, Recreate eRecreateMerged);
|
|
|
|
void UpdateFramesForAddDeleteRedline(SwDoc & rDoc, SwPaM const& rPam);
|
|
void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam);
|
|
|
|
void AddRemoveFlysAnchoredToFrameStartingAtNode(
|
|
SwTextFrame & rFrame, SwTextNode & rTextNode,
|
|
std::set<SwNodeOffset> *pSkipped);
|
|
|
|
OUString GetExpandTextMerged(SwRootFrame const* pLayout,
|
|
SwTextNode const& rNode, bool bWithNumber,
|
|
bool bWithSpacesForLevel, ExpandMode i_mode);
|
|
|
|
bool IsMarkHidden(SwRootFrame const& rLayout, ::sw::mark::MarkBase const& rMark);
|
|
bool IsMarkHintHidden(SwRootFrame const& rLayout,
|
|
SwTextNode const& rNode, SwTextAttrEnd const& rHint);
|
|
|
|
void RecreateStartTextFrames(SwTextNode & rNode);
|
|
|
|
sw::InsertText MakeInsertText(SwTextNode& rNode, const sal_Int32 nPos, const sal_Int32 nLen);
|
|
|
|
/**
|
|
* Decides if rTextNode has a numbering which has layout-level values (e.g. Arabic, but not
|
|
* none or bullets).
|
|
*/
|
|
bool HasNumberingWhichNeedsLayoutUpdate(const SwTextNode& rTextNode);
|
|
|
|
} // namespace sw
|
|
|
|
/// Represents the visualization of a paragraph. Typical upper is an
|
|
/// SwBodyFrame. The first text portion of the first line is az SwParaPortion.
|
|
class SW_DLLPUBLIC SwTextFrame final : public SwContentFrame
|
|
{
|
|
friend class SwTextIter;
|
|
friend class SwTestFormat;
|
|
friend class WidowsAndOrphans;
|
|
friend class TextFrameLockGuard; // May Lock()/Unlock()
|
|
friend bool sw_ChangeOffset(SwTextFrame* pFrame, TextFrameIndex nNew);
|
|
|
|
/// SwLineLayout cache: the lines are not actually owned by the SwTextFrame
|
|
/// but by this SwCache, so they will be deleted in large documents
|
|
/// if there are too many of them, but the "valid" flags of the frame
|
|
/// will still be set; GetFormatted() is the function that forces
|
|
/// recreation of the SwLineLayout by Format() if necessary.
|
|
static SwCache *s_pTextCache;
|
|
|
|
sal_Int32 mnAllLines :24; // Line count for the Paint (including nThisLines)
|
|
sal_Int32 mnThisLines :8; // Count of Lines of the Frame
|
|
|
|
// The x position for flys anchored at this paragraph.
|
|
// These values are calculated in SwTextFrame::CalcBaseOfstForFly()
|
|
SwTwips mnFlyAnchorOfst;
|
|
// The x position for wrap-through flys anchored at this paragraph.
|
|
SwTwips mnFlyAnchorOfstNoWrap;
|
|
/// The y position for wrap-through flys anchored at this paragraph.
|
|
SwTwips mnFlyAnchorVertOfstNoWrap;
|
|
SwTwips mnFootnoteLine;
|
|
// OD 2004-03-17 #i11860# - re-factoring of #i11859#
|
|
// member for height of last line (value needed for proportional line spacing)
|
|
SwTwips mnHeightOfLastLine;
|
|
// member for the additional first line offset, which is caused by the list
|
|
// label alignment for list level position and space mode LABEL_ALIGNMENT.
|
|
// This additional first line offset is used for the text formatting.
|
|
// It is NOT used for the determination of printing area.
|
|
SwTwips mnAdditionalFirstLineOffset;
|
|
|
|
/// redline merge data
|
|
std::unique_ptr<sw::MergedPara> m_pMergedPara;
|
|
|
|
TextFrameIndex mnOffset; // Is the offset in the Content (character count)
|
|
|
|
sal_uInt16 mnCacheIndex; // Index into the cache, USHRT_MAX if there's definitely no fitting object in the cache
|
|
|
|
// Separates the Master and creates a Follow or adjusts the data in the Follow
|
|
void AdjustFollow_( SwTextFormatter &rLine, TextFrameIndex nOffset,
|
|
TextFrameIndex nStrEnd, bool bDontJoin );
|
|
|
|
// Iterates all Lines and sets the line spacing using the attribute
|
|
void CalcLineSpace();
|
|
|
|
// Only called in Format
|
|
void AdjustFrame( const SwTwips nChgHeight, bool bHasToFit = false );
|
|
|
|
// Evaluates the Preps in Format()
|
|
bool CalcPreps();
|
|
void PrepWidows( const sal_uInt16 nNeed, bool bNotify );
|
|
void InvalidateRange_( const SwCharRange &, const tools::Long = 0);
|
|
inline void InvalidateRange( const SwCharRange &, const tools::Long = 0);
|
|
|
|
// WidowsAndOrphans, AdjustFrame, AdjustFollow
|
|
void FormatAdjust( SwTextFormatter &rLine, WidowsAndOrphans &rFrameBreak,
|
|
TextFrameIndex nStrLen, const bool bDummy );
|
|
void ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew );
|
|
|
|
bool mbLocked : 1; // In the Format?
|
|
bool mbWidow : 1; // Is our follow a Widow?
|
|
bool mbJustWidow : 1; // Did we just request Widow flag on master?
|
|
bool mbEmpty : 1; // Are we an empty paragraph?
|
|
bool mbInFootnoteConnect : 1; // Is in Connect at the moment
|
|
bool mbFootnote : 1; // Has at least one footnote
|
|
bool mbRepaint : 1; // TextFrame: Repaint is ready to be fetched
|
|
/// Contains rotated portions.
|
|
bool mbHasRotatedPortions : 1;
|
|
bool mbFieldFollow : 1; // Start with Field rest of the Master
|
|
bool mbHasAnimation : 1; // Contains animated SwGrfNumPortion
|
|
bool mbIsSwapped : 1; // during text formatting we swap the
|
|
// width and height for vertical formatting
|
|
// OD 14.03.2003 #i11760# - flag to control, if follow is formatted in
|
|
// method <CalcFollow(..)>.
|
|
// E.g., avoid formatting of follow, if method <SwLayoutFrame::FormatWidthCols(..)>
|
|
// is running.
|
|
bool mbFollowFormatAllowed : 1;
|
|
|
|
void ResetPreps();
|
|
void Lock() { mbLocked = true; }
|
|
void Unlock() { mbLocked = false; }
|
|
void SetWidow( const bool bNew ) { mbWidow = bNew; }
|
|
void SetJustWidow( const bool bNew ) { mbJustWidow = bNew; }
|
|
void SetEmpty( const bool bNew ) { mbEmpty = bNew; }
|
|
void SetFieldFollow( const bool bNew ) { mbFieldFollow = bNew; }
|
|
|
|
bool IsIdxInside(TextFrameIndex nPos, TextFrameIndex nLen) const;
|
|
|
|
// Changes the Frame or not (cf. FlyCnt)
|
|
bool GetModelPositionForViewPoint_(SwPosition *pPos, const Point &rPoint,
|
|
const bool bChgFrame, SwCursorMoveState* = nullptr ) const;
|
|
void FillCursorPos( SwFillData &rFill ) const;
|
|
|
|
// Format exactly one Line
|
|
bool FormatLine( SwTextFormatter &rLine, const bool bPrev );
|
|
|
|
// In order to safe stack space, we split this method:
|
|
// Format_ calls Format_ with parameters
|
|
void FormatImpl( vcl::RenderContext* pRenderContext, SwParaPortion *pPara,
|
|
::std::vector<SwAnchoredObject *> & rIntersectingObjs);
|
|
void Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf,
|
|
const bool bAdjust = false );
|
|
void FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf );
|
|
|
|
// Formats the Follow and ensures disposing on orphans
|
|
bool CalcFollow(TextFrameIndex nTextOfst);
|
|
|
|
virtual void MakePos() override;
|
|
|
|
// Corrects the position from which we need to format
|
|
static TextFrameIndex FindBrk(std::u16string_view aText, TextFrameIndex nStart,
|
|
TextFrameIndex nEnd);
|
|
|
|
// inline branch
|
|
SwTwips GetFootnoteFrameHeight_() const;
|
|
|
|
// Outsourced to CalcPreps
|
|
bool CalcPrepFootnoteAdjust();
|
|
|
|
// For Footnote and WidOrp: Forced validation
|
|
void ValidateFrame();
|
|
void ValidateBodyFrame();
|
|
|
|
bool GetDropRect_( SwRect &rRect ) const;
|
|
|
|
void SetPara( SwParaPortion *pNew, bool bDelete = true );
|
|
|
|
bool IsFootnoteNumFrame_() const;
|
|
|
|
// Refresh formatting information
|
|
bool FormatQuick( bool bForceQuickFormat );
|
|
|
|
// Opt: Format empty paragraphs
|
|
bool FormatEmpty();
|
|
SwTwips EmptyHeight() const;
|
|
|
|
// Opt: Paint empty paragraphs
|
|
bool PaintEmpty( const SwRect &, bool bCheck ) const;
|
|
|
|
void ChgThisLines(); // Must always be called if the Line count could have changed
|
|
|
|
// required for 'new' relative anchor position
|
|
void CalcBaseOfstForFly();
|
|
|
|
/** method to determine height of last line, needed for proportional line spacing
|
|
|
|
OD 2004-03-17 #i11860#
|
|
OD 2005-05-20 #i47162# - introduce new optional parameter <_bUseFont>
|
|
in order to force the usage of the former algorithm to determine the
|
|
height of the last line, which uses the font.
|
|
|
|
@param _bUseFont
|
|
optional input parameter - boolean indicating, if the font has to be
|
|
used to determine the height of the last line. default value: false
|
|
*/
|
|
void CalcHeightOfLastLine( const bool _bUseFont = false );
|
|
|
|
virtual void DestroyImpl() override;
|
|
virtual ~SwTextFrame() override;
|
|
|
|
void UpdateOutlineContentVisibilityButton(SwWrtShell* pWrtSh) const;
|
|
void PaintOutlineContentVisibilityButton() const;
|
|
|
|
void PaintParagraphStylesHighlighting() const;
|
|
|
|
virtual void SwClientNotify(SwModify const& rModify, SfxHint const& rHint) override;
|
|
|
|
/// Like GetDrawObjs(), but limit to fly frames which are allowed to split.
|
|
std::vector<SwFlyAtContentFrame*> GetSplitFlyDrawObjs() const;
|
|
|
|
public:
|
|
|
|
virtual const SvxFormatBreakItem& GetBreakItem() const override;
|
|
virtual const SwFormatPageDesc& GetPageDescItem() const override;
|
|
|
|
css::uno::Sequence< css::style::TabStop > GetTabStopInfo( SwTwips CurrentPos ) override;
|
|
|
|
/**
|
|
* This is public, as it needs to be called by some methods in order to save the Prepare
|
|
* USE WITH CAUTION!
|
|
*/
|
|
void Init();
|
|
|
|
/// Is called by DoIdleJob_() and ExecSpellPopup()
|
|
SwRect AutoSpell_(SwTextNode &, sal_Int32);
|
|
|
|
/// Is called by DoIdleJob_()
|
|
SwRect SmartTagScan(SwTextNode &);
|
|
|
|
/// Is called by DoIdleJob_()
|
|
void CollectAutoCmplWrds(SwTextNode &, sal_Int32);
|
|
|
|
/**
|
|
* Returns the view rectangle for the rPos model position. The values are relative to the upper
|
|
* left position of the page frame.
|
|
* Additional information can be obtained by passing an SwCursorMoveState object.
|
|
* Returns false if rPos > number of character is string
|
|
*/
|
|
virtual bool GetCharRect( SwRect& rRect, const SwPosition& rPos,
|
|
SwCursorMoveState* pCMS = nullptr, bool bAllowFarAway = true ) const override;
|
|
|
|
/// A slimmer version of GetCharRect for autopositioning Frames
|
|
bool GetAutoPos( SwRect &, const SwPosition& ) const;
|
|
|
|
/**
|
|
* Determine top of line for given position in the text frame
|
|
*
|
|
* OD 11.11.2003 #i22341#
|
|
* Assumption: given position exists in the text frame or in a follow of it
|
|
* OD 2004-02-02 - adjustment
|
|
* Top of first paragraph line is the top of the paragraph.
|
|
* OD 2004-03-18 #i11860# - Consider upper space amount considered for
|
|
* previous frame and the page grid.
|
|
*
|
|
* @param _onTopOfLine
|
|
* output parameter - top of line, if the given position is found in the
|
|
* text frame.
|
|
*
|
|
* @param _rPos
|
|
* input parameter - reference to the position in the text frame
|
|
*
|
|
* @return boolean indicating, if the top of line for the given position
|
|
* has been determined or not.
|
|
*/
|
|
bool GetTopOfLine( SwTwips& _onTopOfLine,
|
|
const SwPosition& _rPos ) const;
|
|
|
|
virtual bool FillSelection( SwSelectionList& rList, const SwRect& rRect ) const override;
|
|
|
|
/**
|
|
* In nOffset returns the offset of the char within the set
|
|
* text buffer, which is closest to the position provided by
|
|
* aPoint within the layout's SSize.
|
|
*
|
|
* @returns false if the SPoint is outside of the SSize else
|
|
* returns true
|
|
*/
|
|
virtual bool GetModelPositionForViewPoint( SwPosition *, Point&,
|
|
SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override;
|
|
|
|
/**
|
|
* Makes sure that the Frame is not switched (e.g. switched for a
|
|
* character-bound Frame)
|
|
*/
|
|
bool GetKeyCursorOfst(SwPosition *pPos, const Point &rPoint ) const
|
|
{ return GetModelPositionForViewPoint_( pPos, rPoint, false ); }
|
|
|
|
void PaintExtraData( const SwRect & rRect ) const; /// Page number etc.
|
|
SwRect GetPaintSwRect();
|
|
virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, PaintFrameMode mode = PAINT_ALL ) const override;
|
|
|
|
/**
|
|
* Layout oriented cursor travelling:
|
|
* Left border, right border, previous Line, following Line,
|
|
* same horizontal position
|
|
*/
|
|
virtual bool LeftMargin(SwPaM *) const override;
|
|
virtual bool RightMargin(SwPaM *, bool bAPI = false) const override;
|
|
|
|
virtual bool UnitUp(SwPaM *, const SwTwips nOffset,
|
|
bool bSetInReadOnly ) const override;
|
|
virtual bool UnitDown(SwPaM *, const SwTwips nOffset,
|
|
bool bSetInReadOnly ) const override;
|
|
bool UnitUp_(SwPaM *, const SwTwips nOffset,
|
|
bool bSetInReadOnly ) const;
|
|
bool UnitDown_(SwPaM *, const SwTwips nOffset,
|
|
bool bSetInReadOnly ) const;
|
|
|
|
/**
|
|
* Prepares the cursor position for a visual cursor move (BiDi).
|
|
* The behaviour is different for insert and overwrite cursors
|
|
*/
|
|
void PrepareVisualMove( TextFrameIndex& nPos, sal_uInt8& nCursorLevel,
|
|
bool& bRight, bool bInsertCursor );
|
|
|
|
/// Methods to manage the FollowFrame
|
|
void SplitFrame(TextFrameIndex nTextPos);
|
|
SwContentFrame *JoinFrame();
|
|
TextFrameIndex GetOffset() const { return mnOffset; }
|
|
void SetOffset_(TextFrameIndex nNewOfst);
|
|
inline void SetOffset (TextFrameIndex nNewOfst);
|
|
void ManipOfst(TextFrameIndex const nNewOfst) { mnOffset = nNewOfst; }
|
|
SwTextFrame *GetFrameAtPos ( const SwPosition &rPos);
|
|
inline const SwTextFrame *GetFrameAtPos ( const SwPosition &rPos) const;
|
|
SwTextFrame& GetFrameAtOfst(TextFrameIndex nOfst);
|
|
/// If there's a Follow and we don't contain text ourselves
|
|
bool IsEmptyMaster() const
|
|
{ return GetFollow() && !GetFollow()->GetOffset(); }
|
|
|
|
void SetMergedPara(std::unique_ptr<sw::MergedPara> p);
|
|
sw::MergedPara * GetMergedPara() { return m_pMergedPara.get(); }
|
|
sw::MergedPara const* GetMergedPara() const { return m_pMergedPara.get(); }
|
|
|
|
/// Returns the text portion we want to edit (for inline see underneath)
|
|
const OUString& GetText() const;
|
|
SwTextNode const* GetTextNodeForParaProps() const;
|
|
SwTextNode const* GetTextNodeForFirstText() const;
|
|
SwTextNode * GetTextNodeFirst()
|
|
{ return const_cast<SwTextNode*>(const_cast<SwTextFrame const*>(this)->GetTextNodeFirst()); };
|
|
SwTextNode const* GetTextNodeFirst() const;
|
|
SwDoc & GetDoc()
|
|
{ return const_cast<SwDoc &>(const_cast<SwTextFrame const*>(this)->GetDoc()); }
|
|
SwDoc const& GetDoc() const;
|
|
|
|
SwTextFrame(SwTextNode * const, SwFrame*, sw::FrameMode eMode);
|
|
|
|
/**
|
|
* SwContentFrame: the shortcut for the Frames
|
|
* If the void* casts wrongly, it's its own fault!
|
|
* The void* must be checked for 0 in any case!
|
|
*
|
|
* return true if the Portion associated with this SwTextFrame was
|
|
* potentially destroyed and replaced by Prepare
|
|
*/
|
|
virtual bool Prepare( const PrepareHint ePrep = PrepareHint::Clear,
|
|
const void *pVoid = nullptr, bool bNotify = true ) override;
|
|
|
|
/**
|
|
* nMaxHeight is the required height
|
|
* bSplit indicates, that the paragraph has to be split
|
|
* bTst indicates, that we are currently doing a test formatting
|
|
*/
|
|
virtual bool WouldFit(SwTwips &nMaxHeight, bool &bSplit, bool bTst, bool bMoveBwd) override;
|
|
|
|
/**
|
|
* The WouldFit equivalent for temporarily rewired TextFrames
|
|
* nMaxHeight returns the required size here too and bSplit
|
|
* determines whether the paragraph needs to be split.
|
|
* We pass the potential predecessor for the distance calculation
|
|
*/
|
|
bool TestFormat( const SwFrame* pPrv, SwTwips &nMaxHeight, bool &bSplit );
|
|
|
|
/**
|
|
* We format a Line for interactive hyphenation
|
|
* @return found
|
|
*/
|
|
bool Hyphenate(SwInterHyphInfoTextFrame & rInf);
|
|
/// Is a hyphenated word? At selection, Point can be at the end of the word
|
|
bool IsInHyphenatedWord(SwPaM *, bool bSelection) const;
|
|
|
|
/// Test grow
|
|
inline SwTwips GrowTst( const SwTwips nGrow, SwResizeLimitReason& );
|
|
|
|
SwParaPortion *GetPara();
|
|
inline const SwParaPortion *GetPara() const;
|
|
inline bool HasPara() const;
|
|
bool HasPara_() const;
|
|
|
|
/// map position in potentially merged text frame to SwPosition
|
|
std::pair<SwTextNode*, sal_Int32> MapViewToModel(TextFrameIndex nIndex) const;
|
|
SwPosition MapViewToModelPos(TextFrameIndex nIndex) const;
|
|
TextFrameIndex MapModelToView(SwTextNode const* pNode, sal_Int32 nIndex) const;
|
|
TextFrameIndex MapModelToViewPos(SwPosition const& rPos) const;
|
|
|
|
// If there are any hanging punctuation portions in the margin
|
|
// the offset will be returned.
|
|
SwTwips HangingMargin() const;
|
|
|
|
/// Get the amount of lower margin of this frame we need to consider for fly portion purposes.
|
|
SwTwips GetLowerMarginForFlyIntersect() const;
|
|
|
|
// Locking
|
|
bool IsLocked() const { return mbLocked; }
|
|
|
|
bool IsWidow() const { return mbWidow; }
|
|
bool IsJustWidow() const { return mbJustWidow; }
|
|
bool IsEmpty() const { return mbEmpty; }
|
|
bool HasFootnote() const { return mbFootnote; }
|
|
bool IsInFootnoteConnect()const { return mbInFootnoteConnect;}
|
|
bool IsFieldFollow() const { return mbFieldFollow;}
|
|
|
|
inline void SetRepaint() const;
|
|
inline void ResetRepaint() const;
|
|
bool HasRepaint() const { return mbRepaint; }
|
|
void SetHasRotatedPortions(bool bHasRotatedPortions);
|
|
bool GetHasRotatedPortions() const { return mbHasRotatedPortions; }
|
|
void SetAnimation() const
|
|
{ const_cast<SwTextFrame*>(this)->mbHasAnimation = true; }
|
|
bool HasAnimation() const { return mbHasAnimation; }
|
|
|
|
bool IsSwapped() const { return mbIsSwapped; }
|
|
|
|
/// Does the Frame have a local footnote (in this Frame or Follow)?
|
|
#ifdef DBG_UTIL
|
|
void CalcFootnoteFlag(TextFrameIndex nStop = TextFrameIndex(COMPLETE_STRING)); //For testing SplitFrame
|
|
#else
|
|
void CalcFootnoteFlag();
|
|
#endif
|
|
|
|
/// Hidden
|
|
virtual bool IsHiddenNow() const override;
|
|
bool IsHiddenNowImpl() const;
|
|
void HideHidden(); // Remove appendage if Hidden
|
|
void HideFootnotes(TextFrameIndex nStart, TextFrameIndex nEnd);
|
|
|
|
/**
|
|
* Hides respectively shows objects, which are anchored at paragraph,
|
|
* at/as a character of the paragraph, corresponding to the paragraph and
|
|
* paragraph portion visibility.
|
|
*/
|
|
void HideAndShowObjects() override;
|
|
|
|
/// Footnote
|
|
void RemoveFootnote(TextFrameIndex nStart,
|
|
TextFrameIndex nLen = TextFrameIndex(COMPLETE_STRING));
|
|
inline SwTwips GetFootnoteFrameHeight() const;
|
|
SwTextFrame *FindFootnoteRef( const SwTextFootnote *pFootnote );
|
|
const SwTextFrame *FindFootnoteRef( const SwTextFootnote *pFootnote ) const
|
|
{ return const_cast<SwTextFrame *>(this)->FindFootnoteRef( pFootnote ); }
|
|
void ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDeadLine );
|
|
|
|
/**
|
|
* If we're a Footnote that grows towards its reference ...
|
|
* public, because it's needed by SwContentFrame::MakeAll
|
|
*/
|
|
SwTwips GetFootnoteLine( const SwTextFootnote *pFootnote ) const;
|
|
|
|
TextFrameIndex GetDropLen(TextFrameIndex nWishLen) const;
|
|
|
|
LanguageType GetLangOfChar(TextFrameIndex nIndex, sal_uInt16 nScript,
|
|
bool bNoChar = false, bool bNoneIfNoHyphenation = false ) const;
|
|
|
|
virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override;
|
|
virtual void CheckDirection( bool bVert ) override;
|
|
|
|
/// Returns the sum of line height in pLine
|
|
SwTwips GetParHeight() const;
|
|
|
|
inline SwTextFrame *GetFollow();
|
|
inline const SwTextFrame *GetFollow() const;
|
|
|
|
/// Find the page number of ErgoSum and QuoVadis
|
|
SwTextFrame *FindQuoVadisFrame();
|
|
|
|
/**
|
|
* In case the SwLineLayout was cleared out of the s_pTextCache, recreate it
|
|
*
|
|
* #i29062# GetFormatted() can trigger a full formatting
|
|
* of the paragraph, causing other layout frames to become invalid. This
|
|
* has to be avoided during painting. Therefore we need to pass the
|
|
* information that we are currently in the paint process.
|
|
*/
|
|
SwTextFrame* GetFormatted( bool bForceQuickFormat = false );
|
|
|
|
/// Will be moved soon
|
|
void SetFootnote( const bool bNew ) { mbFootnote = bNew; }
|
|
|
|
/// Respect the Follows
|
|
inline bool IsInside(TextFrameIndex nPos) const;
|
|
|
|
/// DropCaps and selections
|
|
bool GetDropRect( SwRect &rRect ) const
|
|
{ return HasPara() && GetDropRect_( rRect ); }
|
|
|
|
static SwCache *GetTextCache() { return s_pTextCache; }
|
|
static void SetTextCache( SwCache *pNew ) { s_pTextCache = pNew; }
|
|
|
|
sal_uInt16 GetCacheIdx() const { return mnCacheIndex; }
|
|
void SetCacheIdx( const sal_uInt16 nNew ) { mnCacheIndex = nNew; }
|
|
|
|
/// Removes the Line information from the Cache but retains the entry itself
|
|
void ClearPara();
|
|
/// Removes this frame completely from the Cache
|
|
void RemoveFromCache();
|
|
|
|
/// Am I a FootnoteFrame, with a number at the start of the paragraph?
|
|
bool IsFootnoteNumFrame() const
|
|
{ return IsInFootnote() && !GetIndPrev() && IsFootnoteNumFrame_(); }
|
|
|
|
/**
|
|
* Simulates a formatting as if there were not right margin or Flys or other
|
|
* obstacles and returns the width
|
|
*/
|
|
SwTwips CalcFitToContent();
|
|
|
|
/**
|
|
* Simulate format for a list item paragraph, whose list level attributes
|
|
* are in LABEL_ALIGNMENT mode, in order to determine additional first
|
|
* line offset for the real text formatting due to the value of label
|
|
* adjustment attribute of the list level.
|
|
*/
|
|
void CalcAdditionalFirstLineOffset();
|
|
|
|
SwTwips GetAdditionalFirstLineOffset() const
|
|
{
|
|
return mnAdditionalFirstLineOffset;
|
|
}
|
|
|
|
/**
|
|
* Returns the additional line spacing for the next paragraph
|
|
* @param _bNoPropLineSpacing: control, whether the value of a
|
|
* proportional line spacing is returned or not
|
|
*/
|
|
tools::Long GetLineSpace( const bool _bNoPropLineSpacing = false ) const;
|
|
|
|
/// Returns the first line height
|
|
SwTwips FirstLineHeight() const;
|
|
|
|
/// Rewires FlyInContentFrame, if nEnd > Index >= nStart
|
|
void MoveFlyInCnt(SwTextFrame *pNew, TextFrameIndex nStart, TextFrameIndex nEnd);
|
|
|
|
/// Calculates the position of FlyInContentFrames
|
|
TextFrameIndex CalcFlyPos( SwFrameFormat const * pSearch );
|
|
|
|
/// Determines the start position and step size of the register
|
|
bool FillRegister( SwTwips& rRegStart, sal_uInt16& rRegDiff );
|
|
|
|
/// Determines the line count
|
|
sal_Int32 GetLineCount(TextFrameIndex nPos);
|
|
|
|
/// For displaying the line numbers
|
|
sal_Int32 GetAllLines() const { return mnAllLines; }
|
|
sal_Int32 GetThisLines() const { return mnThisLines;}
|
|
void RecalcAllLines();
|
|
|
|
/// Stops the animations within numberings
|
|
void StopAnimation( const OutputDevice *pOut );
|
|
|
|
/// Visit all portions for Accessibility
|
|
void VisitPortions( SwPortionHandler& rPH ) const;
|
|
|
|
/// Returns the script info stored at the paraportion
|
|
const SwScriptInfo* GetScriptInfo() const;
|
|
SwScriptInfo* GetScriptInfo();
|
|
|
|
/// Swaps width and height of the text frame
|
|
void SwapWidthAndHeight();
|
|
|
|
/**
|
|
* Calculates the coordinates of a rectangle when switching from
|
|
* horizontal to vertical layout
|
|
*/
|
|
void SwitchHorizontalToVertical( SwRect& rRect ) const;
|
|
|
|
/**
|
|
* Calculates the coordinates of a point when switching from
|
|
* horizontal to vertical layout
|
|
*/
|
|
void SwitchHorizontalToVertical( Point& rPoint ) const;
|
|
|
|
/**
|
|
* Calculates the limit value when switching from
|
|
* horizontal to vertical layout
|
|
*/
|
|
tools::Long SwitchHorizontalToVertical( tools::Long nLimit ) const;
|
|
|
|
/**
|
|
* Calculates the coordinates of a rectangle when switching from
|
|
* vertical to horizontal layout
|
|
*/
|
|
void SwitchVerticalToHorizontal( SwRect& rRect ) const;
|
|
|
|
/**
|
|
* Calculates the coordinates of a point when switching from
|
|
* vertical to horizontal layout
|
|
*/
|
|
void SwitchVerticalToHorizontal( Point& rPoint ) const;
|
|
|
|
/**
|
|
* Calculates the a limit value when switching from
|
|
* vertical to horizontal layout
|
|
*/
|
|
tools::Long SwitchVerticalToHorizontal( tools::Long nLimit ) const;
|
|
|
|
/**
|
|
* Calculates the coordinates of a rectangle when switching from
|
|
* LTR to RTL layout
|
|
*/
|
|
void SwitchLTRtoRTL( SwRect& rRect ) const;
|
|
|
|
/**
|
|
* Calculates the coordinates of a point when switching from
|
|
* LTR to RTL layout
|
|
*/
|
|
void SwitchLTRtoRTL( Point& rPoint ) const;
|
|
|
|
/**
|
|
* Calculates the coordinates of a rectangle when switching from
|
|
* RTL to LTR layout
|
|
*/
|
|
void SwitchRTLtoLTR( SwRect& rRect ) const { SwitchLTRtoRTL( rRect ); }
|
|
|
|
/**
|
|
* Calculates the coordinates of a point when switching from
|
|
* RTL to LTR layout
|
|
*/
|
|
void SwitchRTLtoLTR( Point& rPoint ) const { SwitchLTRtoRTL( rPoint ); };
|
|
|
|
bool FollowFormatAllowed() const
|
|
{
|
|
return mbFollowFormatAllowed;
|
|
}
|
|
|
|
void AllowFollowFormat()
|
|
{
|
|
mbFollowFormatAllowed = true;
|
|
}
|
|
|
|
void ForbidFollowFormat()
|
|
{
|
|
mbFollowFormatAllowed = false;
|
|
}
|
|
|
|
SwTwips GetBaseOffsetForFly( bool bIgnoreFlysAnchoredAtThisFrame ) const
|
|
{
|
|
return ( bIgnoreFlysAnchoredAtThisFrame ?
|
|
mnFlyAnchorOfst :
|
|
mnFlyAnchorOfstNoWrap );
|
|
}
|
|
|
|
SwTwips GetBaseVertOffsetForFly(bool bIgnoreFlysAnchoredAtThisFrame) const;
|
|
|
|
SwTwips GetHeightOfLastLine() const
|
|
{
|
|
return mnHeightOfLastLine;
|
|
}
|
|
|
|
static void repaintTextFrames( const SwTextNode& rNode );
|
|
|
|
void RegisterToNode(SwTextNode &, bool isForceNodeAsFirst = false);
|
|
|
|
bool IsSymbolAt(TextFrameIndex) const;
|
|
OUString GetCurWord(SwPosition const&) const;
|
|
sal_uInt16 GetScalingOfSelectedText(TextFrameIndex nStt, TextFrameIndex nEnd);
|
|
|
|
/// This text frame may have a split fly frames anchored to it. Is any of them a frame that has
|
|
/// a follow, i.e. not the last in a master -> follow 1 -> ... -> last follow chain?
|
|
SwFlyAtContentFrame* HasNonLastSplitFlyDrawObj() const;
|
|
|
|
/// This text frame has a follow and the text frame don't contain text. Additionally one split
|
|
/// fly is anchored to the text frame.
|
|
bool IsEmptyMasterWithSplitFly() const;
|
|
|
|
/// This text frame is not split, doesn't fit the upper, has a single split fly anchored to it
|
|
/// with a negative vertical offset. Such frames may need explicit splitting.
|
|
bool IsEmptyWithSplitFly() const;
|
|
|
|
bool HasSplitFlyDrawObjs() const;
|
|
|
|
static SwView* GetView();
|
|
|
|
void dumpAsXml(xmlTextWriterPtr writer = nullptr) const override;
|
|
void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override;
|
|
};
|
|
|
|
//use this to protect a SwTextFrame for a given scope from getting merged with
|
|
//its neighbour and thus deleted
|
|
class TextFrameLockGuard
|
|
{
|
|
private:
|
|
SwTextFrame *m_pTextFrame;
|
|
bool m_bOldLocked;
|
|
public:
|
|
//Lock pFrame for the lifetime of the Cut/Paste call, etc. to avoid
|
|
//SwTextFrame::AdjustFollow_ removing the pFrame we're trying to Make
|
|
TextFrameLockGuard(SwFrame* pFrame)
|
|
{
|
|
m_pTextFrame = pFrame->IsTextFrame() ? static_cast<SwTextFrame*>(pFrame) : nullptr;
|
|
if (m_pTextFrame)
|
|
{
|
|
m_bOldLocked = m_pTextFrame->IsLocked();
|
|
m_pTextFrame->Lock();
|
|
}
|
|
else
|
|
{
|
|
m_bOldLocked = false;
|
|
}
|
|
}
|
|
|
|
~TextFrameLockGuard()
|
|
{
|
|
if (m_pTextFrame && !m_bOldLocked)
|
|
m_pTextFrame->Unlock();
|
|
}
|
|
};
|
|
|
|
inline const SwParaPortion *SwTextFrame::GetPara() const
|
|
{
|
|
return const_cast<SwTextFrame*>(this)->GetPara();
|
|
}
|
|
|
|
inline bool SwTextFrame::HasPara() const
|
|
{
|
|
return mnCacheIndex!=USHRT_MAX && HasPara_();
|
|
}
|
|
|
|
inline SwTwips SwTextFrame::GrowTst(const SwTwips nGrow, SwResizeLimitReason& reason)
|
|
{
|
|
return Grow(nGrow, reason, true, false);
|
|
}
|
|
|
|
inline bool SwTextFrame::IsInside(TextFrameIndex const nPos) const
|
|
{
|
|
bool bRet = true;
|
|
if( nPos < GetOffset() )
|
|
bRet = false;
|
|
else
|
|
{
|
|
const SwTextFrame *pFoll = GetFollow();
|
|
if( pFoll && nPos >= pFoll->GetOffset() )
|
|
bRet = false;
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
inline SwTwips SwTextFrame::GetFootnoteFrameHeight() const
|
|
{
|
|
if( !IsFollow() && IsInFootnote() && HasPara() )
|
|
return GetFootnoteFrameHeight_();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
inline const SwTextFrame *SwTextFrame::GetFollow() const
|
|
{
|
|
return static_cast<const SwTextFrame*>(SwContentFrame::GetFollow());
|
|
}
|
|
inline SwTextFrame *SwTextFrame::GetFollow()
|
|
{
|
|
return static_cast<SwTextFrame*>(SwContentFrame::GetFollow());
|
|
}
|
|
|
|
inline const SwTextFrame *SwTextFrame::GetFrameAtPos( const SwPosition &rPos) const
|
|
{
|
|
return const_cast<SwTextFrame*>(this)->GetFrameAtPos( rPos );
|
|
}
|
|
|
|
inline void SwTextFrame::SetOffset(TextFrameIndex const nNewOfst)
|
|
{
|
|
if ( mnOffset != nNewOfst )
|
|
SetOffset_( nNewOfst );
|
|
}
|
|
|
|
inline void SwTextFrame::SetRepaint() const
|
|
{
|
|
const_cast<SwTextFrame*>(this)->mbRepaint = true;
|
|
}
|
|
inline void SwTextFrame::ResetRepaint() const
|
|
{
|
|
const_cast<SwTextFrame*>(this)->mbRepaint = false;
|
|
}
|
|
|
|
class TemporarySwap {
|
|
protected:
|
|
explicit TemporarySwap(SwTextFrame * frame, bool swap):
|
|
m_frame(frame), m_undo(false)
|
|
{
|
|
if (m_frame->IsVertical() && swap) {
|
|
m_undo = true;
|
|
m_frame->SwapWidthAndHeight();
|
|
}
|
|
}
|
|
|
|
~TemporarySwap() {
|
|
if (m_undo) {
|
|
m_frame->SwapWidthAndHeight();
|
|
}
|
|
}
|
|
|
|
private:
|
|
TemporarySwap(TemporarySwap const &) = delete;
|
|
void operator =(TemporarySwap const &) = delete;
|
|
|
|
SwTextFrame * m_frame;
|
|
bool m_undo;
|
|
};
|
|
|
|
class SwSwapIfSwapped: private TemporarySwap {
|
|
public:
|
|
explicit SwSwapIfSwapped(SwTextFrame* frame):
|
|
TemporarySwap(frame, frame->IsSwapped()) {}
|
|
};
|
|
|
|
class SwSwapIfNotSwapped: private TemporarySwap {
|
|
public:
|
|
explicit SwSwapIfNotSwapped(SwTextFrame* frame):
|
|
TemporarySwap(frame, !frame->IsSwapped()) {}
|
|
};
|
|
|
|
/**
|
|
* Helper class which can be used instead of the macros if a function
|
|
* has too many returns
|
|
*/
|
|
class SwFrameSwapper
|
|
{
|
|
const SwTextFrame* pFrame;
|
|
bool bUndo;
|
|
public:
|
|
SwFrameSwapper( const SwTextFrame* pFrame, bool bSwapIfNotSwapped );
|
|
~SwFrameSwapper();
|
|
};
|
|
|
|
class SwLayoutModeModifier
|
|
{
|
|
const OutputDevice& m_rOut;
|
|
vcl::text::ComplexTextLayoutFlags m_nOldLayoutMode;
|
|
public:
|
|
SwLayoutModeModifier( const OutputDevice& rOutp );
|
|
~SwLayoutModeModifier();
|
|
void Modify( bool bChgToRTL );
|
|
void SetAuto();
|
|
};
|
|
|
|
class SwDigitModeModifier
|
|
{
|
|
const OutputDevice& rOut;
|
|
LanguageType nOldLanguageType;
|
|
public:
|
|
SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang, SvtCTLOptions::TextNumerals eCTlTextNumerals );
|
|
~SwDigitModeModifier();
|
|
};
|
|
|
|
namespace sw {
|
|
|
|
/**
|
|
* Describes parts of multiple text nodes, which will form a text frame, even
|
|
* when redlines are hidden at a layout level.
|
|
*/
|
|
struct MergedPara
|
|
{
|
|
sw::WriterMultiListener listener;
|
|
std::vector<Extent> extents;
|
|
/// note: cannot be const currently to avoid UB because SwTextGuess::Guess
|
|
/// const_casts it and modifies it (also, Update will modify it)
|
|
OUString mergedText;
|
|
/// most paragraph properties are taken from the first non-empty node
|
|
SwTextNode * pParaPropsNode;
|
|
/// except break attributes, those are taken from the first node
|
|
SwTextNode *const pFirstNode;
|
|
/// mainly for sanity checks
|
|
SwTextNode const* pLastNode;
|
|
MergedPara(SwTextFrame & rFrame, std::vector<Extent>&& rExtents,
|
|
OUString aText,
|
|
SwTextNode *const pFirst,
|
|
SwTextNode const*const pLast)
|
|
: listener(rFrame), extents(std::move(rExtents)), mergedText(std::move(aText))
|
|
, pParaPropsNode(nullptr), pFirstNode(pFirst), pLastNode(pLast)
|
|
{
|
|
assert(pFirstNode);
|
|
assert(pLastNode);
|
|
}
|
|
};
|
|
|
|
/// iterate SwTextAttr in potentially merged text frame
|
|
class MergedAttrIterBase
|
|
{
|
|
protected:
|
|
sw::MergedPara const*const m_pMerged;
|
|
SwTextNode const*const m_pNode;
|
|
size_t m_CurrentExtent;
|
|
size_t m_CurrentHint;
|
|
MergedAttrIterBase(SwTextFrame const& rFrame);
|
|
};
|
|
|
|
class MergedAttrIter
|
|
: public MergedAttrIterBase
|
|
{
|
|
public:
|
|
MergedAttrIter(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
|
|
SwTextAttr const* NextAttr(SwTextNode const** ppNode = nullptr);
|
|
};
|
|
|
|
class MergedAttrIterByEnd
|
|
{
|
|
private:
|
|
std::vector<std::pair<SwTextNode const*, SwTextAttr const*>> m_Hints;
|
|
SwTextNode const*const m_pNode;
|
|
size_t m_CurrentHint;
|
|
public:
|
|
MergedAttrIterByEnd(SwTextFrame const& rFrame);
|
|
SwTextAttr const* NextAttr(SwTextNode const*& rpNode);
|
|
void PrevAttr();
|
|
};
|
|
|
|
class MergedAttrIterReverse
|
|
: public MergedAttrIterBase
|
|
{
|
|
public:
|
|
MergedAttrIterReverse(SwTextFrame const& rFrame);
|
|
SwTextAttr const* PrevAttr(SwTextNode const** ppNode = nullptr);
|
|
};
|
|
|
|
|
|
const SwTwips WIDOW_MAGIC = (SAL_MAX_INT32 - 1)/2;
|
|
|
|
} // namespace sw
|
|
|
|
#endif
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|