{********************************************************************} { } { Developer Express Visual Component Library } { Express Cross Platform Library classes } { } { Copyright (c) 2000-2007 Developer Express Inc. } { ALL RIGHTS RESERVED } { } { The entire contents of this file is protected by U.S. and } { International Copyright Laws. Unauthorized reproduction, } { reverse-engineering, and distribution of all or any portion of } { the code contained in this file is strictly prohibited and may } { result in severe civil and criminal penalties and will be } { prosecuted to the maximum extent possible under the law. } { } { RESTRICTIONS } { } { THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES } { (DCU, OBJ, DLL, ETC.) ARE CONFIDENTIAL AND PROPRIETARY TRADE } { SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS } { LICENSED TO DISTRIBUTE THE EXPRESSCROSSPLATFORMLIBRARY AND ALL } { ACCOMPANYING VCL CONTROLS AS PART OF AN EXECUTABLE PROGRAM } { ONLY. } { } { THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED } { FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE } { COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE } { AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT } { AND PERMISSION FROM DEVELOPER EXPRESS INC. } { } { CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON } { ADDITIONAL RESTRICTIONS. } { } {********************************************************************} unit cxDrawTextUtils; {$I cxVer.inc} interface uses Windows, Controls, {$IFDEF DELPHI6} Types, {$ELSE} Classes, {$ENDIF} SysUtils, Graphics; const CXTO_LEFT = $00000000; CXTO_CENTER_HORIZONTALLY = $00000001; CXTO_RIGHT = $00000002; CXTO_JUSTIFY_HORIZONTALLY = $00000003; CXTO_DISTRIBUTE_HORIZONTALLY = $00000004; CXTO_TOP = $00000000; CXTO_CENTER_VERTICALLY = $00000010; CXTO_BOTTOM = $00000020; CXTO_DISTRIBUTE_VERTICALLY = $00000030; CXTO_PREVENT_LEFT_EXCEED = $00000040; CXTO_PREVENT_TOP_EXCEED = $00000080; CXTO_WORDBREAK = $00000100; CXTO_SINGLELINE = $00000200; CXTO_EXPANDTABS = $00000400; CXTO_END_ELLIPSIS = $00000800; CXTO_PATTERNEDTEXT = $00001000; CXTO_EXTERNALLEADING = $00002000; CXTO_EDITCONTROL = $00004000; CXTO_NOCLIP = $00008000; CXTO_AUTOINDENTS = $00010000; CXTO_CHARBREAK = $00020000; CXTO_CALCRECT = $00100000; CXTO_CALCROWCOUNT = $00200000; CXTO_DEFAULT_FORMAT = CXTO_LEFT or CXTO_TOP or CXTO_SINGLELINE; cxTextSpace = 2; type TCaptionChar = Char; TcxCaptionChar = TCaptionChar; PcxCaptionChar = PChar; TcxTextOutFormat = DWORD; TcxTextAlignX = (taLeft, taCenterX, taRight, taJustifyX, taDistributeX); TcxTextAlignY = (taTop, taCenterY, taBottom, taDistributeY); PcxTextParams = ^TcxTextParams; TcxTextParams = packed record RowHeight: Integer; tmExternalLeading: Integer; FullRowHeight: Integer; EndEllipsisWidth: Integer; BreakChar: TcxCaptionChar; TextAlignX: TcxTextAlignX; TextAlignY: TcxTextAlignY; WordBreak: Boolean; SingleLine: Boolean; ExpandTabs: Boolean; EndEllipsis: Boolean; ExternalLeading: Boolean; EditControl: Boolean; NoClip: Boolean; AutoIndents: Boolean; PreventLeftExceed: Boolean; PreventTopExceed: Boolean; CharBreak: Boolean; CalcRowCount: Boolean; CalcRect: Boolean; MaxCharWidth: Integer; CharSet: Byte; end; TcxTextRow = record Text: PcxCaptionChar; TextLength: Integer; TextExtents: TSize; BreakCount: Integer; TextOriginX: Integer; TextOriginY: Integer; StartOffset: Integer; end; TcxTextRows = array of TcxTextRow; TCanvasHandle = HDC; function cxCalcTextParams(AHandle: TCanvasHandle; AFormat: DWORD): TcxTextParams; overload; function cxCalcTextParams(ACanvas: TCanvas; AFormat: DWORD): TcxTextParams; overload; function cxCalcTextExtents(AHandle: TCanvasHandle; AText: PcxCaptionChar; ATextLength: Integer; AExpandTabs: Boolean): TSize; function cxGetLongestTextRowWidth(const ATextRows: TcxTextRows; ARowCount: Integer): Integer; procedure cxCalcTextRowExtents(AHandle: TCanvasHandle; var ATextRow: TcxTextRow; const ATextParams: TcxTextParams); function cxMakeTextRows(AHandle: TCanvasHandle; AText: PcxCaptionChar; const R: TRect; const ATextParams: TcxTextParams; out ATextRows: TcxTextRows; out ACount: Integer; AMaxLineCount: Integer = 0): Boolean; overload; function cxMakeTextRows(ACanvas: TCanvas; AText: PcxCaptionChar; const R: TRect; const ATextParams: TcxTextParams; out ATextRows: TcxTextRows; out ACount: Integer; AMaxLineCount: Integer = 0): Boolean; overload; procedure cxPlaceTextRows(AHandle: TCanvasHandle; const R: TRect; var ATextParams: TcxTextParams; const ATextRows: TcxTextRows; ARowCount: Integer); function cxMakeFormat(ATextAlignX: TcxTextAlignX; ATextAlignY: TcxTextAlignY): DWORD; function cxPrepareRect(const R: TRect; const ATextParams: TcxTextParams; ALeftIndent, ARightIndent: Integer): TRect; function cxTextOut(AHandle: TCanvasHandle; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat = CXTO_DEFAULT_FORMAT; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; overload; function cxTextOut(AHandle: TCanvasHandle; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat; ASelStart, ASelLength: Integer; AColor, ATextColor: TColor; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; overload; // for TCanvas function cxTextOut(ACanvas: TCanvas; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat = CXTO_DEFAULT_FORMAT; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; overload; function cxTextOut(ACanvas: TCanvas; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat; ASelStart, ASelLength: Integer; AColor, ATextColor: TColor; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; overload; procedure cxTextRowsOutHighlight(AHandle: TCanvasHandle; const R: TRect; const ATextParams: TcxTextParams; const ATextRows: TcxTextRows; ARowCount, ASelStart, ASelLength: Integer; AColor, ATextColor: TColor; AForceEndEllipsis: Boolean); implementation uses Math, cxClasses, cxGraphics; type TCanvasAccess = class(TControlCanvas); const CXTO_VERT_ALIGN_OFFSET = 4; CXTO_HORZ_ALIGN_MASK = CXTO_CENTER_HORIZONTALLY or CXTO_RIGHT or CXTO_JUSTIFY_HORIZONTALLY or CXTO_DISTRIBUTE_HORIZONTALLY; CXTO_VERT_ALIGN_MASK = CXTO_CENTER_VERTICALLY or CXTO_BOTTOM or CXTO_DISTRIBUTE_VERTICALLY; Tab: TcxCaptionChar = #9; LF: TcxCaptionChar = #10; CR: TcxCaptionChar = #13; cxEndEllipsisChars: PcxCaptionChar = '...'; var cxEndEllipsisCharsLength: Integer; function cxMakeFormat(ATextAlignX: TcxTextAlignX; ATextAlignY: TcxTextAlignY): DWORD; begin Result := Byte(ATextAlignX) or (Byte(ATextAlignY) shl CXTO_VERT_ALIGN_OFFSET); end; function cxCalcTextExtents(AHandle: TCanvasHandle; AText: PcxCaptionChar; ATextLength: Integer; AExpandTabs: Boolean): TSize; var ATextExtent: DWORD; R: TRect; begin if AExpandTabs then begin if ATextLength <= 4096 then begin ATextExtent := GetTabbedTextExtent(AHandle, AText, ATextLength, 0, Result); Result.cx := LoWord(ATextExtent); Result.cy := HiWord(ATextExtent); end else begin R := cxEmptyRect; Result.cy := DrawText(AHandle, AText, ATextLength, R, DT_SINGLELINE or DT_NOPREFIX or DT_CALCRECT or DT_EXPANDTABS); Result.cx := R.Right - R.Left; end; end else GetTextExtentPoint32(AHandle, AText, ATextLength, Result); end; function IntersectClipRect(AHandle: TCanvasHandle; const R: TRect): HRGN; begin Result := CreateRectRgn(0, 0, 0, 0); if GetClipRgn(AHandle, Result) <> 1 then begin DeleteObject(Result); Result := 0; end; with R do Windows.IntersectClipRect(AHandle, Left, Top, Right, Bottom); end; procedure RestoreClipRgn(AHandle: TCanvasHandle; var ARgn: HRGN); begin SelectClipRgn(AHandle, ARgn); if ARgn <> 0 then begin DeleteObject(ARgn); ARgn := 0 end; end; procedure cxCalcTextRowExtents(AHandle: TCanvasHandle; var ATextRow: TcxTextRow; const ATextParams: TcxTextParams); begin with ATextRow do TextExtents := cxCalcTextExtents(AHandle, Text, TextLength, ATextParams.ExpandTabs); end; function cxCalcMinStringLength(AHandle: TCanvasHandle; const ATextParams: TcxTextParams; AText: PcxCaptionChar; ATextLength, AWidth: Integer; var ATotalLength: Integer): Integer; var L, H, C: Integer; begin Result := 1; H := ATextLength; L := H; ATotalLength := cxCalcTextExtents(AHandle, AText, L, False).cx; if ATotalLength / AWidth > 8 then begin Result := 1; ATotalLength := 0;; Exit; end else ATotalLength := cxCalcTextExtents(AHandle, AText, L, ATextParams.ExpandTabs).cx; while (L > 1) and (ATotalLength > AWidth) do begin L := L div 2; ATotalLength := cxCalcTextExtents(AHandle, AText, L, ATextParams.ExpandTabs).cx; end; while L <= H do begin Result := (L + H) shr 1; ATotalLength := cxCalcTextExtents(AHandle, AText, Result, ATextParams.ExpandTabs).cx; if L = H then C := 0 else C := ATotalLength - AWidth; if C < 0 then L := Result + 1 else begin H := Result - 1; if C = 0 then Break; end; end; if (ATotalLength > AWidth) and (Result > 1) then begin Dec(Result); ATotalLength := cxCalcTextExtents(AHandle, AText, Result, ATextParams.ExpandTabs).cx; end; end; procedure cxGetTextRow(AHandle: TCanvasHandle; var S: PcxCaptionChar; var ALength: Integer; AWidth: Integer; AIsLastRow: Boolean; const ATextParams: TcxTextParams; var ATextRow: TcxTextRow); var AMinStrLen, ATotalTextWidth, I, ABreakPos: Integer; ASize: TSize; ABreakByWords, ABreakByChars, AIsSpecialProcessedRow, ACRExists: Boolean; ACharLen: Integer; AChar: PChar; begin FillChar(ATextRow, SizeOf(ATextRow), 0); if S = nil then Exit; ABreakPos := -1; AMinStrLen := cxCalcMinStringLength(AHandle, ATextParams, S, ALength, Max(AWidth, 1), ATotalTextWidth); with ATextParams do begin ABreakByWords := not SingleLine and (WordBreak or (TextAlignX in [taJustifyX, taDistributeX])); ABreakByChars := ABreakByWords and CharBreak; AIsSpecialProcessedRow := not EditControl and not NoClip and not (TextAlignX in [taJustifyX, taDistributeX]) and AIsLastRow and EndEllipsis; end; AChar := S; with ATextRow do begin for I := 0 to ALength - 1 do begin if SysLocale.FarEast then begin ACharLen := cxStrCharLength(AChar); if StrByteType(S, I) = mbTrailByte then Continue; Inc(AChar, ACharLen); end else ACharLen := 1; TextLength := I + ACharLen; if (S[I] = (ATextParams.BreakChar)) or (S[I] = Tab) then begin Inc(BreakCount); ABreakPos := I; end; if ((S[I] = CR) or (S[I] = LF)) and not AIsSpecialProcessedRow and not ATextParams.SingleLine then begin Dec(TextLength); if ATextParams.TextAlignX = taJustifyX then BreakCount := 0; Break; end; if ABreakByWords or AIsSpecialProcessedRow or ATextParams.SingleLine then begin if I >= AMinStrLen then begin if ATextParams.ExpandTabs then ATotalTextWidth := cxCalcTextExtents(AHandle, S, TextLength, True).cx else begin ASize := cxCalcTextExtents(AHandle, @S[I], ACharLen, False); Inc(ATotalTextWidth, ASize.cx); end; end; if ATotalTextWidth > AWidth then begin if AIsSpecialProcessedRow or ATextParams.SingleLine then Break else begin if ABreakPos <> -1 then begin TextLength := ABreakPos + 1; Break; end else if ABreakByChars then begin if TextLength > cxStrCharLength(S) then Dec(TextLength, ACharLen); Break; end; end; end; end; end; ATextRow.Text := S; // truncate trailing spaces if ATextParams.TextAlignX in [taJustifyX, taDistributeX] then while (TextLength > 0) and ((S[TextLength - 1] = (ATextParams.BreakChar)) or (S[TextLength - 1] = Tab)) do begin if BreakCount > 0 then Dec(BreakCount); Dec(TextLength); end; end; cxCalcTextRowExtents(AHandle, ATextRow, ATextParams); ACRExists := False; // correct source string Inc(S, ATextRow.TextLength); Dec(ALength, ATextRow.TextLength); I := 0; while (I < ALength) and (S[I] = ATextParams.BreakChar) do Inc(I); if (I < ALength) and (S[I] = Tab) then Inc(I); if (I < ALength) and (S[I] = CR) then begin Inc(I); ACRExists := True; end; if (I < ALength) and (S[I] = LF) then Inc(I); Inc(S, I); Dec(ALength, I); // doesn't justify last row (like Excel ?) if ((ALength = 0) or ACRExists) and (ATextParams.TextAlignX = taJustifyX) then ATextRow.BreakCount := 0; end; function cxCalcTextParams(AHandle: TCanvasHandle; AFormat: TcxTextOutFormat): TcxTextParams; var ATextMetric: TTextMetric; begin FillChar(Result, SizeOf(Result), 0); with Result do begin GetTextMetrics(AHandle, ATextMetric); BreakChar := ATextMetric.tmBreakChar; MaxCharWidth := ATextMetric.tmMaxCharWidth; RowHeight := ATextMetric.tmHeight; CharSet := ATextMetric.tmCharSet; TextAlignX := TcxTextAlignX(AFormat and CXTO_HORZ_ALIGN_MASK); TextAlignY := TcxTextAlignY(AFormat and CXTO_VERT_ALIGN_MASK shr CXTO_VERT_ALIGN_OFFSET); AutoIndents := AFormat and CXTO_AUTOINDENTS <> 0; CalcRect := AFormat and CXTO_CALCRECT <> 0; CalcRowCount := AFormat and CXTO_CALCROWCOUNT <> 0; CharBreak := (AFormat and CXTO_CHARBREAK <> 0) or SysLocale.FarEast; EditControl := AFormat and CXTO_EDITCONTROL <> 0; EndEllipsis := AFormat and CXTO_END_ELLIPSIS <> 0; ExternalLeading := AFormat and CXTO_EXTERNALLEADING <> 0; ExpandTabs := AFormat and CXTO_EXPANDTABS <> 0; NoClip := AFormat and CXTO_NOCLIP <> 0; PreventLeftExceed := AFormat and CXTO_PREVENT_LEFT_EXCEED <> 0; PreventTopExceed := AFormat and CXTO_PREVENT_TOP_EXCEED <> 0; SingleLine := AFormat and CXTO_SINGLELINE <> 0; WordBreak := AFormat and CXTO_WORDBREAK <> 0; if ExternalLeading then tmExternalLeading := ATextMetric.tmExternalLeading; FullRowHeight := RowHeight + tmExternalLeading; if AFormat and CXTO_END_ELLIPSIS = CXTO_END_ELLIPSIS then EndEllipsisWidth := cxCalcTextExtents(AHandle, cxEndEllipsisChars, cxEndEllipsisCharsLength, False).cX else EndEllipsisWidth := 0; end; end; function cxCalcTextParams(ACanvas: TCanvas; AFormat: DWORD): TcxTextParams; begin TCanvasAccess(ACanvas).RequiredState([csHandleValid, csFontValid]); Result := cxCalcTextParams(ACanvas.Handle, AFormat); end; function cxMakeTextRows(AHandle: TCanvasHandle; AText: PcxCaptionChar; const R: TRect; const ATextParams: TcxTextParams; out ATextRows: TcxTextRows; out ACount: Integer; AMaxLineCount: Integer = 0): Boolean; function CheckIsLastRow(ATotalHeight, H: Integer): Boolean; begin with ATextParams do if SingleLine then Result := True else if (TextAlignY = taTop) and not CalcRect then begin if (EditControl{ or EndEllipsis}) and not NoClip then Result := ATotalHeight + FullRowHeight > H else Result := ATotalHeight > H end else Result := (AMaxLineCount > 0) and (ACount = AMaxLineCount); end; procedure ExpandTextRows(var ACapacity: Integer); const Delta: Integer = 4; begin if ACount > ACapacity then begin Inc(ACapacity, Delta); SetLength(ATextRows, ACapacity); end; end; var P: PcxCaptionChar; AIsLastRow, ARectIsSmall: Boolean; ATotalHeight, ACapacity, H, W, L, AOffset: Integer; begin ARectIsSmall := False; ATextRows := nil; ACount := 0; if AText <> nil then begin P := AText; AOffset := 0; if ATextParams.CalcRect and ATextParams.SingleLine then begin ACount := 1; SetLength(ATextRows, ACount); ATextRows[0].Text := P; ATextRows[0].TextLength := Length(P); cxCalcTextRowExtents(AHandle, ATextRows[0], ATextParams); end else begin AIsLastRow := False; ATotalHeight := 0; ACapacity := 0; L := Length(P); W := R.Right - R.Left; H := R.Bottom - R.Top; while (Length(P) <> 0) and not AIsLastRow do begin Inc(ACount); ExpandTextRows(ACapacity); Inc(ATotalHeight, ATextParams.FullRowHeight); AIsLastRow := CheckIsLastRow(ATotalHeight, H); cxGetTextRow(AHandle, P, L, W, AIsLastRow, ATextParams, ATextRows[ACount - 1]); ATextRows[ACount - 1].StartOffset := AOffset; AOffset := Integer(Pointer(P)) - Integer(Pointer(AText)); if not AIsLastRow then AIsLastRow := L = 0; if (AMaxLineCount > 0) and (ACount = AMaxLineCount) then Break; end; with ATextParams do ARectIsSmall := not SingleLine and not CalcRect and (AMaxLineCount > 0) and (ACount = AMaxLineCount) and (L > 0); if ACapacity > ACount then SetLength(ATextRows, ACount); end; end; Result := not ARectIsSmall; end; function cxMakeTextRows(ACanvas: TCanvas; AText: PcxCaptionChar; const R: TRect; const ATextParams: TcxTextParams; out ATextRows: TcxTextRows; out ACount: Integer; AMaxLineCount: Integer = 0): Boolean; begin Result := cxMakeTextRows(ACanvas.Handle, AText, R, ATextParams, ATextRows, ACount, AMaxLineCount); end; procedure cxPlaceTextRows(AHandle: TCanvasHandle; const R: TRect; var ATextParams: TcxTextParams; const ATextRows: TcxTextRows; ARowCount: Integer); procedure CalcExtraAndTopRowOffset(out AExtra, ATopRowOffset: Integer); var H: Integer; begin AExtra := 0; with ATextParams do begin if (ARowCount > 1) and (TextAlignY = taDistributeY) then begin H := R.Bottom - R.Top; Dec(H, RowHeight); if H / (ARowCount - 1) > RowHeight then begin FullRowHeight := H div (ARowCount - 1); AExtra := H mod (ARowCount - 1); end; end; case TextAlignY of taCenterY: ATopRowOffset := R.Top + (R.Bottom - R.Top - ARowCount * FullRowHeight) div 2; taBottom: ATopRowOffset := R.Bottom - ARowCount * FullRowHeight + tmExternalLeading; else ATopRowOffset := R.Top; end; if PreventTopExceed and (ATopRowOffset < R.Top) then ATopRowOffset := R.Top; end; end; procedure PlaceRows(AExtra, ATopRowOffset: Integer); var I: Integer; begin for I := 0 to ARowCount - 1 do with ATextRows[I] do begin // Horizontally case ATextParams.TextAlignX of taCenterX: TextOriginX := R.Left + (R.Right - R.Left - TextExtents.cx) div 2; taRight: TextOriginX := R.Right - TextExtents.cx; else TextOriginX := R.Left; end; if ATextParams.PreventLeftExceed and (TextOriginX < R.Left) then TextOriginX := R.Left; // Vertically TextOriginY := ATopRowOffset; Inc(ATopRowOffset, ATextParams.FullRowHeight); if AExtra > 0 then begin Inc(ATopRowOffset); Dec(AExtra); end; end; end; var Extra, TopRowOffset: Integer; begin CalcExtraAndTopRowOffset(Extra, TopRowOffset); PlaceRows(Extra, TopRowOffset); end; function cxPrepareRect(const R: TRect; const ATextParams: TcxTextParams; ALeftIndent, ARightIndent: Integer): TRect; begin Result := R; with Result do begin Inc(Left, ALeftIndent); Dec(Right, ARightIndent); end; end; function cxUnprepareRect(const R: TRect; const ATextParams: TcxTextParams; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): TRect; begin Result := R; with Result do begin Dec(Left, ALeftIndent); Inc(Right, ARightIndent); end; end; {$WARNINGS OFF} procedure cxTextRowsOutHighlight(AHandle: TCanvasHandle; const R: TRect; const ATextParams: TcxTextParams; const ATextRows: TcxTextRows; ARowCount, ASelStart, ASelLength: Integer; AColor, ATextColor: TColor; AForceEndEllipsis: Boolean); procedure OutTextRow(const ATextRow: TcxTextRow); var Stub: Integer; begin with ATextRow do if ATextParams.ExpandTabs then begin Stub := 0; TabbedTextOut(AHandle, TextOriginX, TextOriginY, Text, TextLength, 0, Stub, TextOriginX); end else TextOut(AHandle, TextOriginX, TextOriginY, Text, TextLength); end; procedure PrepareEndEllipsis(var ATextRow: TcxTextRow; var AWidth: Integer); var ACharNumber: Integer; ASize: TSize; begin Dec(AWidth, ATextParams.EndEllipsisWidth); if AWidth < 0 then AWidth := 0; with ATextRow do begin if not GetTextExtentExPoint(AHandle, Text, TextLength, AWidth, @ACharNumber, nil, ASize) then ACharNumber := 0; if ACharNumber = 0 then ACharNumber := cxStrCharLength(Text); TextLength := ACharNumber; end; cxCalcTextRowExtents(AHandle, ATextRow, ATextParams); end; procedure OutEndEllipsis(const ATextRow: TcxTextRow; var ARowRect: TRect); const ClipTexts: array[Boolean] of UINT = (0, ETO_CLIPPED); var fuOptions: UINT; begin Inc(ARowRect.Left, ATextRow.TextExtents.cx); if ARowRect.Left < ARowRect.Right then begin fuOptions := ClipTexts[not ATextParams.NoClip and (ARowRect.Left + ATextParams.EndEllipsisWidth > ARowRect.Right)]; ExtTextOut(AHandle, ARowRect.Left, ATextRow.TextOriginY, fuOptions, @ARowRect, PChar(cxEndEllipsisChars), Length(cxEndEllipsisChars), nil); end; end; function GetSubstringWidth(AText: PcxCaptionChar; ATextLength, ASubstringLength: Integer): Integer; var ACharExtents: array of Integer; ATextSize: TSize; begin if ASubstringLength = 0 then Result := 0 else begin SetLength(ACharExtents, ATextLength); GetTextExtentExPoint(AHandle, AText, ATextLength, 0, nil, @ACharExtents[0], ATextSize); Result := ACharExtents[ASubstringLength - 1]; end; end; var ARowRect, AHighlightRect: TRect; W, I, F, L: Integer; ABreakExtra: Integer; APrevBkMode: Integer; ASaveTextColor: TColor; ANeedClip, ANeedEndEllipsis: Boolean; ATextRow: TcxTextRow; AHighlightStart, AHighlightEnd: Integer; ARgn, ASaveClipRgn: HRGN; ABrush: HBRUSH; begin AColor := ColorToRGB(AColor); ATextColor := ColorToRGB(ATextColor); W := R.Right - R.Left; ARowRect := R; APrevBkMode := SetBkMode(AHandle, Windows.TRANSPARENT); if (ASelLength > 0) and (AColor <> clNone) then ABrush := CreateSolidBrush(AColor); for I := 0 to ARowCount - 1 do begin ATextRow := ATextRows[I]; with ATextRow do begin if TextLength <> 0 then begin ARowRect.Top := TextOriginY; ARowRect.Bottom := ARowRect.Top + ATextParams.FullRowHeight; ANeedEndEllipsis := ATextParams.EndEllipsis and (I = ARowCount - 1) and ((TextExtents.cx > W) or AForceEndEllipsis); ABreakExtra := 0; if (ATextParams.TextAlignX in [taJustifyX, taDistributeX]) and not ANeedEndEllipsis then begin ABreakExtra := W - TextExtents.cX; if (BreakCount <> 0) and (ABreakExtra > 0) then SetTextJustification(AHandle, ABreakExtra, BreakCount); end; ANeedClip := not ATextParams.NoClip and ((TextExtents.cX > W) or (ARowRect.Top < R.Top) or (ARowRect.Bottom > R.Bottom)); if ANeedClip then begin if ARowRect.Top < R.Top then ARowRect.Top := R.Top; if ARowRect.Bottom > R.Bottom then ARowRect.Bottom := R.Bottom; ARgn := IntersectClipRect(AHandle, ARowRect); end; if ANeedEndEllipsis then PrepareEndEllipsis(ATextRow, W); if ASelLength > 0 then begin if not ((ASelStart >= StartOffset + TextLength) or (ASelStart + ASelLength <= StartOffset)) then begin F := Max(ASelStart, StartOffset); L := Min(ASelStart + ASelLength, StartOffset + TextLength); Dec(F, StartOffset); Dec(L, StartOffset); if L > F then begin AHighlightStart := GetSubstringWidth(Text, TextLength, F); AHighlightEnd := GetSubstringWidth(Text, TextLength, L); if cxGetWritingDirection(ATextParams.CharSet, Text) = coRightToLeft then begin AHighlightRect.Left := TextOriginX + TextExtents.cx - AHighlightEnd; AHighlightRect.Right := TextOriginX + TextExtents.cx - AHighlightStart; end else begin AHighlightRect.Left := TextOriginX + AHighlightStart; AHighlightRect.Right := TextOriginX + AHighlightEnd; end; AHighlightRect.Top := ARowRect.Top; AHighlightRect.Bottom := ARowRect.Bottom; if not IsRectEmpty(AHighlightRect) then begin ASaveClipRgn := IntersectClipRect(AHandle, AHighlightRect); if AColor <> clNone then FillRect(AHandle, AHighlightRect, ABrush); ASaveTextColor := SetTextColor(AHandle, ATextColor); OutTextRow(ATextRow); SetTextColor(AHandle, ASaveTextColor); RestoreClipRgn(AHandle, ASaveClipRgn); with AHighlightRect do ExcludeClipRect(AHandle, Left, Top, Right, Bottom); if ANeedEndEllipsis and (ASelStart + ASelLength >= StartOffset + TextLength) then begin ANeedEndEllipsis := False; ASaveTextColor := SetTextColor(AHandle, ATextColor); OutEndEllipsis(ATextRow, ARowRect); SetTextColor(AHandle, ASaveTextColor); end; end; end; end end; OutTextRow(ATextRow); if ANeedEndEllipsis then OutEndEllipsis(ATextRow, ARowRect); if ANeedClip then RestoreClipRgn(AHandle, ARgn); if ABreakExtra > 0 then SetTextJustification(AHandle, 0, 0); end else if AForceEndEllipsis and ATextParams.EndEllipsis and (I = ARowCount - 1) then begin ARowRect.Top := TextOriginY; ARowRect.Bottom := ARowRect.Top + ATextParams.FullRowHeight; PrepareEndEllipsis(ATextRow, W); OutEndEllipsis(ATextRow, ARowRect); end; end; end; if (ASelLength > 0) and (AColor <> clNone) then DeleteObject(ABrush); SetBkMode(AHandle, APrevBkMode); end; {$WARNINGS ON} function cxGetLongestTextRowWidth(const ATextRows: TcxTextRows; ARowCount: Integer): Integer; var I, V: Integer; begin if ARowCount > Length(ATextRows) then ARowCount := Length(ATextRows); if ARowCount = 0 then Result := 0 else begin Result := ATextRows[0].TextExtents.cx; for I := 1 to ARowCount - 1 do begin V := ATextRows[I].TextExtents.cx; if V > Result then Result := V; end; end; end; function cxTextOut(AHandle: TCanvasHandle; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat; ASelStart, ASelLength: Integer; AColor, ATextColor: TColor; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; function ProcessText(const ATextParams: TcxTextParams; const ATextRect: TRect): Boolean; begin if ATextParams.CalcRect then Result := (ATextRect.Right - ATextRect.Left) > 0 else Result := not IsRectEmpty(ATextRect); end; var ATextHeight, ARowCount, ATextLength: Integer; ATextParams: TcxTextParams; ATextRect: TRect; ATextRows: TcxTextRows; AForceEndEllipsis: Boolean; begin Result := 0; ATextLength := StrLen(AText); if ATextLength = 0 then Exit; ATextParams := cxCalcTextParams(AHandle, AFormat); ATextRect := cxPrepareRect(R, ATextParams, ALeftIndent, ARightIndent); ATextHeight := 0; if ProcessText(ATextParams, ATextRect) then begin ATextRows := nil; AForceEndEllipsis := not cxMakeTextRows(AHandle, (AText), ATextRect, ATextParams, ATextRows, ARowCount, AMaxLineCount); if ARowCount <> 0 then try if ATextParams.CalcRect then begin if (AMaxLineCount > 0) and (AMaxLineCount < ARowCount) then ARowCount := AMaxLineCount; ATextRect.Right := ATextRect.Left + cxGetLongestTextRowWidth(ATextRows, ARowCount); if not ATextParams.SingleLine then begin ATextRows := nil; cxMakeTextRows(AHandle, (AText), ATextRect, ATextParams, ATextRows, ARowCount, AMaxLineCount); end; cxPlaceTextRows(AHandle, ATextRect, ATextParams, ATextRows, ARowCount); ATextRect.Bottom := ATextRows[ARowCount - 1].TextOriginY + ATextParams.RowHeight; R := cxUnprepareRect(ATextRect, ATextParams, ALeftIndent, ARightIndent); end else begin if (AMaxLineCount > 0) and (ARowCount >= AMaxLineCount) then begin ARowCount := AMaxLineCount; with ATextParams do AForceEndEllipsis := AForceEndEllipsis and EndEllipsis and EditControl and not SingleLine; end else AForceEndEllipsis := False; cxPlaceTextRows(AHandle, ATextRect, ATextParams, ATextRows, ARowCount); if (ASelStart < 0) or (ASelStart >= ATextLength) then ASelLength := 0 else if (ASelLength + ASelStart) > ATextLength then ASelLength := ATextLength - ASelStart; cxTextRowsOutHighlight(AHandle, ATextRect, ATextParams, ATextRows, ARowCount, ASelStart, ASelLength, AColor, ATextColor, AForceEndEllipsis); end; ATextHeight := ATextRows[ARowCount - 1].TextOriginY + ATextParams.RowHeight - ATextRect.Top; finally ATextRows := nil; end; end; if ATextParams.CalcRowCount or (ATextHeight = 0) then Result := ARowCount else Result := ATextHeight; end; function cxTextOut(ACanvas: TCanvas; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat; ASelStart, ASelLength: Integer; AColor, ATextColor: TColor; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; begin Result := cxTextOut(ACanvas.Handle, AText, R, AFormat, ASelStart, ASelLength, AColor, ATextColor, AMaxLineCount, ALeftIndent, ARightIndent); end; function cxTextOut(ACanvas: TCanvas; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat = CXTO_DEFAULT_FORMAT; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; begin Result := cxTextOut(ACanvas, AText, R, AFormat, 0, 0, ACanvas.Font.Color, clBlack, AMaxLineCount, ALeftIndent, ARightIndent); end; function cxTextOut(AHandle: TCanvasHandle; AText: PcxCaptionChar; var R: TRect; AFormat: TcxTextOutFormat = CXTO_DEFAULT_FORMAT; AMaxLineCount: Integer = 0; ALeftIndent: Integer = 0; ARightIndent: Integer = 0): Integer; begin Result := cxTextOut(AHandle, AText, R, AFormat, 0, 0, GetTextColor(AHandle), clBlack, AMaxLineCount, ALeftIndent, ARightIndent); end; initialization cxEndEllipsisCharsLength := Length(cxEndEllipsisChars); end.