unit fpsPatterns; {$mode objfpc}{$H+} interface uses SysUtils, Classes, Contnrs, Math, fpsTypes, fpsChart; { Fill patterns } type TsLineFillPatternMultiplier = (lfpmSingle, lfpmDouble, lfpmTriple); TsLineFillPattern = class Distance: Single; // Distance of lines, in mm if > 0, in px if < 0 Angle: Single; // Rotation angle of pattern, in degrees LineWidth: Single; // Line width of pattern, in mm if > 0, in px if < 0 Multiplier: TsLineFillPatternMultiplier; end; TsDotFillPattern = array[0..7] of Byte; const SOLID_DOT_FILL_PATTERN: TsDotFillPattern = ($FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF); type { TsRawFillPattern Combines the same visual pattern as a dot matrix and as vector strokes. Vector strokes are supported only by ODS. "Raw" patterns do not carry any color - this is added by TsChartFillPattern. Used by charts only. } TsRawFillPattern = class private FName: String; FExcelName: string; // style name used in Excel xlsx FDotPattern: TsDotFillPattern; FLinePattern: TsLineFillPattern; procedure SetLinePattern(AValue: TsLineFillPattern); public constructor Create(AName: String; ADotPattern: TsDotFillPattern; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier); constructor Create(AName: String; ADotPattern: TsDotFillPattern); constructor Create(AName: String; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier); destructor Destroy; override; procedure CopyFrom(ASource: TsRawFillPattern); virtual; property ExcelName: String read FExcelName; property Name: String read FName; property DotPattern: TsDotFillPattern read FDotPattern write FDotPattern; property LinePattern: TsLineFillPattern read FLinePattern write SetLinePattern; end; TsRawFillPatternList = class(TFPObjectlist) private function GetItem(AIndex: Integer): TsRawFillPattern; procedure SetItem(AIndex: Integer; AValue: TsRawFillPattern); protected function AddOrReplace(APattern: TsRawFillPattern): Integer; function FindSimilarDotPattern(ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): TsDotFillPattern; public procedure AddBuiltinPatterns; function AddFillPattern(AName, AExcelName: String; ADotPattern: TsDotFillPattern; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; function AddDotFillPattern(AName, AExcelName: String; APattern: TsDotFillPattern): Integer; overload; function AddLineFillPattern(AName, AExcelName: String; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; function ClonePattern(AIndex: Integer; AName: String): Integer; function FindByName(AName: String): TsRawFillPattern; function FindLinePatternIndex(ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; function IndexOfName(AName: String): Integer; property Items[AIndex: Integer]: TsRawFillPattern read GetItem write SetItem; default; end; { Line patterns } TsChartLengthUnit = (cluMillimeters, cluPercentage); TsRawLinePatternElement = record Length: Single; // mm or % of linewidth Count: Integer; end; TsRawLinePattern = class Name: String; ExcelName: String; Element1: TsRawLinePatternElement; Element2: TsRawLinePatternElement; DistanceLength: Single; // Space between elements, mm or % of linewidth LengthUnit: TsChartLengthUnit; constructor Create(AName: String; AElement1Length: Single; AElement1Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit); overload; constructor Create(AName: String; AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit); overload; procedure CopyFrom(ASource: TsRawLinePattern); end; TsRawLinePatternList = class(TFPObjectList) private function GetItem(AIndex: Integer): TsRawLinePattern; procedure SetItem(AIndex: Integer; AValue: TsRawLinePattern); protected function AddOrReplace(APattern: TsRawLinePattern): Integer; public procedure AddBuiltinPatterns; function AddPattern(AName: String; AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; ALengthUnit: TsChartLengthUnit): Integer; function FindPatternIndex(AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; ALengthUnit: TsChartLengthUnit): Integer; function IndexOfName(AName: String): Integer; property Items[AIndex: Integer]: TsRawLinePattern read GetItem write SetItem; default; end; { global fill pattern procedures } procedure CreateRawFillPatterns; procedure DestroyRawFillPatterns; function GetRawFillPattern(APatternIndex: Integer): TsRawFillPattern; function GetRawFillPatternCount: Integer; function GetRawFillPatternIndex(ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; function GetRawFillPatternName(APatternStyle: TsChartFillPatternStyle): String; function RegisterRawFillPattern(AName: String; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; { global line pattern procedures } procedure CreateRawLinePatterns; procedure DestroyRawLinePatterns; function GetRawLinePattern(APatternIndex: Integer): TsRawLinePattern; function GetRawLinePatternCount: Integer; function GetRawLinePatternIndex(APatternName: String): Integer; function GetRawLinePatternIndex(AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit): Integer; function GetRawLinePatternName(APatternStyle: TsChartLinePatternStyle): String; function RegisterRawLinePattern(AName: String; AElementLength: Single; ADistanceLength: Single; AUnit: TsChartLengthUnit): Integer; function RegisterRawLinePattern(AName: String; AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit): Integer; implementation const WIDE_DISTANCE = 3.0; NARROW_DISTANCE = 1.5; THIN_LINE = 0.1; THICK_LINE = 0.3; {$include fpspatterns_dotpatterns.inc} function StringToDotPattern(APattern: String): TsDotFillPattern; const w = 8; h = 8; var i, j, n, b: Integer; begin n := Length(APattern); if n > w*h then n := w*h; FillChar(Result, Sizeof(Result), 0); b := 0; i := 0; for j := 1 to n do begin if APattern[j] in ['x', 'X'] then Result[i] := Result[i] or (1 shl b); inc(b); if b = w then begin inc(i); b := 0; end; end; end; { TsRawFillPattern } constructor TsRawFillPattern.Create(AName: String; ADotPattern: TsDotFillPattern); begin inherited Create; FName := AName; FDotPattern := ADotPattern; FLinePattern := nil; // no line pattern end; constructor TsRawFillPattern.Create(AName: String; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier); begin inherited Create; FName := AName; FDotPattern := SOLID_DOT_FILL_PATTERN; FLinePattern := TsLineFillPattern.Create; FLinePattern.Distance := ALineDistance; FLinePattern.Angle := ALineAngle; FLinePattern.LineWidth := ALineWidth; FLinePattern.Multiplier := AMultiplier; end; constructor TsRawFillPattern.Create(AName: String; ADotPattern: TsDotFillPattern; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier); begin inherited Create; FName := AName; FDotPattern := ADotPattern; FLinePattern := TsLineFillPattern.Create; FLinePattern.Distance := ALineDistance; FLinePattern.Angle := ALineAngle; FLinePattern.LineWidth := ALineWidth; FLinePattern.Multiplier := AMultiplier; end; destructor TsRawFillPattern.Destroy; begin FName := ''; FLinePattern.Free; inherited; end; procedure TsRawFillPattern.CopyFrom(ASource: TsRawFillPattern); begin FName := ASource.Name; FExcelName := ASource.ExcelName; FDotPattern := ASource.DotPattern; SetLinePattern(ASource.LinePattern); end; procedure TsRawFillPattern.SetLinePattern(AValue: TsLineFillPattern); begin if AValue = nil then begin FLinePattern.Free; FLinePattern := nil; end else begin if FLinePattern = nil then FLinePattern := TsLineFillPattern.Create; FLinePattern.Distance := AValue.Distance; FLinePattern.Angle := AValue.Angle; FLinePattern.LineWidth := AValue.LineWidth; FLinePattern.Multiplier := AValue.Multiplier; end; end; (* { TsLineFillPatternStyle } constructor TsLineFillPatternStyle.Create(AName: String; ALineDistance, ALineAngle, ALineWidth: Single); begin inherited Create(AName); FLineDistance := ALineDistance; FLineAngle := ALineAngle; FLineWidth := ALineWidth; end; procedure TsLineFillPatternStyle.CopyFrom(ASource: TsFillPatternStyle); begin if (ASource is TsLineFillPatternStyle) then begin FLineDistance := TsLineFillPatternStyle(ASource).LineDistance; FLineAngle := TsLineFillPatternStyle(ASource).LineAngle; FLineWidth := TsLineFillPatternStyle(ASource).LineWidth; end; inherited CopyFrom(ASource); end; // The pattern itself goes to infinity, limited by size of area to be filled. function TsLineFillPatternStyle.GetHeight: Integer; begin Result := -1; end; function TsLineFillPatternStyle.GetWidth: Integer; begin Result := -1; end; { TsDotFillPatternStyle } constructor TsDotFillPatternStyle.Create(AName: String; APattern: String); begin inherited Create(AName); StringToPattern(APattern); end; constructor TsDotFillPatternStyle.Create(AName: String; APattern: TBrushPattern); begin inherited Create(AName); FPattern := APattern; end; procedure TsDotFillPatternStyle.CopyFrom(ASource: TsFillPatternStyle); begin if ASource is TsDotFillPatternStyle then FPattern := TsDotFillPatternStyle(ASource).Pattern; inherited CopyFrom(ASource); end; function TsDotFillPatternStyle.GetWidth: Integer; begin Result := SizeOf(TPenPattern); end; function TsDotFillPatternStyle.GetHeight: Integer; begin Result := SizeOf(TBrushPattern) div SizeOf(TPenPattern); end; procedure TsDotFillPatternStyle.StringToPattern(APattern: String); var L: TStrings; w, h, i, j, x, y: Integer; patt: String; begin L := TStringList.Create; try L.Text := APattern; h := L.Count; w := Length(L[0]); for i := 1 to L.Count-1 do if Length(L[i]) <> w then raise Exception.Create('The lines in the pattern string must have the same lengths.'); FillChar(FPattern, Sizeof(FPattern), 0); y := 0; i := 0; while y < SizeOf(TPenPattern) do begin if i >= L.Count then i := 0; patt := L[i]; x := 0; j := 1; while x < SizeOf(TPenPattern) do begin if j > w then j := 1; if (patt[j] in ['x', 'X']) then FPattern[i] := FPattern[i] or (1 shl x); inc(x); inc(j); end; inc(y); inc(i); end; finally L.Free; end; end; *) { TsRawFillPatternList } { Creates the built-in dot patterns as used by Excel. Not all of them are available in ODS. } procedure TsRawFillPatternList.AddBuiltinPatterns; var i: Integer; fps: TsChartFillpatternStyle; begin // IMPORTANT: Add all predefined fill patterns in correct order as defined // by the type TsChartfillPatternStyle. AddDotFillPattern(GetRawFillPatternName(fpsGray05), 'pct5', // 0 StringToDotPattern(GRAY05_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray10), 'pct10', StringToDotPattern(GRAY20_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray20), 'pct20', StringToDotPattern(GRAY20_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray25), 'pct25', StringToDotPattern(GRAY25_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray30), 'pct30', StringToDotPattern(GRAY30_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray40), 'pct40', // 5 StringToDotPattern(GRAY40_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray50), 'pct50', StringToDotPattern(GRAY50_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray60), 'pct60', StringToDotPattern(GRAY60_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray70), 'pct70', StringToDotPattern(GRAY70_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray75), 'pct75', StringToDotPattern(GRAY75_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray80), 'pct80', // 10 StringToDotPattern(GRAY80_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsGray90), 'pct90', StringToDotPattern(GRAY90_PATTERN)); AddFillPattern(GetRawFillPatternName(fpsHorThick), 'dkHorz', // 12 StringToDotPattern(HOR_PATTERN_THICK), WIDE_DISTANCE, 0.0, THICK_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsVertThick), 'dkVert', // 13 StringToDotPattern(VERT_PATTERN_THICK), WIDE_DISTANCE, 90.0, THICK_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsDiagUpThick), 'dkUpDiag', // 14 StringToDotPattern(DIAG_UP_PATTERN_THICK), WIDE_DISTANCE, 45.0, THICK_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsDiagDownThick), 'dkDnDiag', // 15 StringToDotPattern(DIAG_DOWN_PATTERN_THICK), WIDE_DISTANCE, -45.0, THICK_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsHatchThick), 'openDmnd', // 16 -- replacement StringToDotPattern(HATCH_PATTERN_THICK), WIDE_DISTANCE, 45.0, THICK_LINE, lfpmDouble); AddFillPattern(GetRawFillPatternName(fpsCrossThick), 'smGrid', // 17 -- replacement StringToDotPattern(CROSS_PATTERN_THICK), WIDE_DISTANCE, 0.0, THICK_LINE, lfpmDouble); AddFillPattern(GetRawFillPatternName(fpsHorThin), 'ltHorz', // 18 StringToDotPattern(HOR_PATTERN_THIN), WIDE_DISTANCE, 0.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsVertThin), 'ltVert', // 19 StringToDotPattern(VERT_PATTERN_THIN), WIDE_DISTANCE, 90.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsDiagUpThin), 'wdUpDiag', // 20 StringToDotPattern(DIAG_UP_PATTERN_THIN), WIDE_DISTANCE, 45.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsDiagDownThin), 'wdDnDiag', // 21 StringToDotPattern(DIAG_DOWN_PATTERN_THIN), WIDE_DISTANCE, -45.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsHatchThin), 'openDmnd', // 22 StringToDotPattern(HATCH_PATTERN_THIN), WIDE_DISTANCE, 45.0, THIN_LINE, lfpmDouble); AddFillPattern(GetRawFillPatternName(fpsCrossThin), 'lgGrid', // 23 StringToDotPattern(CROSS_PATTERN_THIN), WIDE_DISTANCE, 0.0, THIN_LINE, lfpmDouble); AddFillPattern(GetRawFillPatternName(fpsHorNarrow), 'narHorz', // 24 StringToDotPattern(HOR_PATTERN_NARROW), NARROW_DISTANCE, 0.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsVertNarrow), 'narVert', // 25 StringToDotPattern(VERT_PATTERN_NARROW), NARROW_DISTANCE, 90.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsDiagUpNarrow), 'ltUpDiag', // 26 StringToDotPattern(DIAG_UP_PATTERN_NARROW), NARROW_DISTANCE, 45.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsDiagDownNarrow), 'ltDnDiag', // 27 StringToDotPattern(DIAG_DOWN_PATTERN_NARROW), NARROW_DISTANCE, -45.0, THIN_LINE, lfpmSingle); AddFillPattern(GetRawFillPatternName(fpsHatchNarrow), 'openDmnd', // 28 -- replacement StringToDotPattern(HATCH_PATTERN_NARROW), NARROW_DISTANCE, 45.0, THIN_LINE, lfpmDouble); AddFillPattern(GetRawFillPatternName(fpsCrossNarrow), 'smGrid', // 29 StringToDotPattern(CROSS_PATTERN_NARROW), NARROW_DISTANCE, 0.0, THIN_LINE, lfpmDouble); AddDotFillPattern(GetRawFillPatternName(fpsHorDash), 'dashHorz', // 30 StringToDotPattern(HOR_DASH_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsVertDash), 'dashVert', // 31 StringToDotPattern(VERT_DASH_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsDiagUpDash), 'dashUpDiag', // 32 StringToDotPattern(DIAG_UP_DASH_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsDiagDownDash), 'dashDnDiag', // 33 StringToDotPattern(DIAG_DOWN_DASH_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsCrossDot), 'dotGrid', // 34 StringToDotPattern(CROSS_PATTERN_DOT)); AddDotFillPattern(GetRawFillPatternName(fpsHatchDot), 'dotDmnd', // 35 StringToDotPattern(HATCH_PATTERN_DOT)); AddDotFillPattern(GetRawFillPatternName(fpsBrickHor), 'horzBrick', // 36 StringToDotPattern(BRICK_HOR_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsBrickDiag), 'diagBrick', // 37 StringToDotPattern(BRICK_DIAG_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsCheckerboardLarge), 'lgCheck', // 38 StringToDotPattern(CHECKERBOARD_LARGE_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsCheckerboardSmall), 'smCheck', // 39 StringToDotPattern(CHECKERBOARD_SMALL_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsConfettiLarge), 'lgConfetti', // 40 StringToDotPattern(CONFETTI_LARGE_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsConfettiSmall), 'smConfetti', // 41 StringToDotPattern(CONFETTI_SMALL_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsDiamond), 'solidDmnd', // 42 StringToDotPattern(DIAMOND_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsDivot), 'divot', // 43 StringToDotPattern(DIVOT_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsPlaid), 'plaid', // 44 StringToDotPattern(PLAID_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsShingle), 'shingle', // 45 StringToDotPattern(SHINGLE_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsSphere), 'sphere', // 46 StringToDotPattern(SPHERE_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsTrellis), 'trellis', // 47 StringToDotPattern(TRELLIS_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsWave), 'wave', // 48 StringToDotPattern(WAVE_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsWeave), 'weave', // 49 StringToDotPattern(WEAVE_PATTERN)); AddDotFillPattern(GetRawFillPatternName(fpsZigZag), 'zigZag', // 50 StringToDotPattern(ZIGZAG_PATTERN)); { // Check for completeness for i := 0 to Count - 1 do with Items[i] do WriteLn( 'TsRawFillPatternList.AddBuiltinPatterns] i=', i, ' style=', TsChartFillPatternStyle(i), ' Name=', Name, ' ExcelName=', ExcelName ); } end; function TsRawFillPatternList.AddDotFillPattern(AName, AExcelName: String; APattern: TsDotFillPattern): Integer; var patt: TsRawFillPattern; begin patt := TsRawFillPattern.Create(AName, APattern); patt.FExcelName := AExcelName; Result := AddOrReplace(patt); end; function TsRawFillPatternList.AddFillPattern(AName, AExcelName: String; ADotPattern: TsDotFillPattern; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; var patt: TsRawFillPattern; begin patt := TsRawFillPattern.Create(AName, ADotPattern, ALineDistance, ALineAngle, ALineWidth, AMultiplier ); patt.FExcelName := AExcelName; Result := AddOrReplace(patt); end; function TsRawFillPatternList.AddLineFillPattern(AName, AExcelName: String; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; var patt: TsRawFillPattern; begin patt := TsRawFillPattern.Create(AName, ALineDistance, ALineAngle, ALineWidth, AMultiplier); patt.DotPattern := FindSimilarDotPattern(ALineDistance, ALineAngle, ALineWidth, AMultiplier); patt.FExcelName := AExcelName; Result := AddOrReplace(patt); end; function TsRawFillPatternList.AddOrReplace(APattern: TsRawFillPattern): Integer; var idx: Integer; begin idx := IndexOfName(APattern.Name); if idx = -1 then Result := Add(APattern) else begin Items[idx].CopyFrom(APattern); // Insert(idx, APattern); Result := idx; end; end; { Adds a copy of the pattern at the given index and gives it a new name. } function TsRawFillPatternList.ClonePattern(AIndex: Integer; AName: String): Integer; var patt: TsRawFillPattern; begin patt := Items[AIndex]; Result := AddFillPattern(AName, '', patt.DotPattern, patt.LinePattern.Distance, patt.LinePattern.Angle, patt.LinePattern.LineWidth, patt.LinePattern.Multiplier ); end; function TsRawFillPatternList.FindByName(AName: String): TsRawFillPattern; var i: Integer; begin for i := 0 to Count-1 do if Items[i].Name = AName then begin Result := Items[i]; exit; end; Result := nil; end; function TsRawFillPatternList.FindLinePatternIndex( ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; var i: Integer; patt: TsRawFillPattern; begin for i := 0 to Count-1 do begin patt := Items[i]; if patt.LinePattern = nil then Continue; if SameValue(ALineDistance, patt.LinePattern.Distance, 0.1) and SameValue(ALineAngle, patt.LinePattern.Angle, 0.1) and SameValue(ALineWidth, patt.LinePattern.LineWidth, 0.1) and (AMultiplier = patt.LinePattern.Multiplier) then begin Result := i; exit; end; end; Result := -1; end; function TsRawFillPatternList.FindSimilarDotPattern(ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): TsDotFillPattern; var sinAngle, cosAngle: Single; similarIdx: Integer; singleLine: Boolean; begin similarIdx := -1; SinCos(DegToRad(ALineAngle), sinAngle, cosAngle); singleLine := (AMultiplier = lfpmSingle); if SameValue(sinAngle, 0.0, 0.2) then // about +/- 12° begin if (ALineDistance >= WIDE_DISTANCE) or (ALineDistance <= -8) then // wider than 3mm or 8px similarIdx := IfThen(singleLine, ord(fpsHorThin), ord(fpsCrossThin)) else similarIdx := IfThen(singleLine, ord(fpsHorNarrow), ord(fpsCrossNarrow)); end else if SameValue(cosAngle, 0.0, 0.2) then // about 90 +/- 12° begin if (ALineDistance >= WIDE_DISTANCE) or (ALineDistance <= -8) then similarIdx := IfThen(singleLine, ord(fpsVertThin), ord(fpsCrossThin)) else similarIdx := IfThen(singleLine, ord(fpsVertNarrow), ord(fpsCrossNarrow)); end else if ALineAngle > 0 then begin if (ALineDistance >= WIDE_DISTANCE) or (ALineDistance <= -8) then similarIdx := IfThen(singleLine, ord(fpsDiagUpThin), ord(fpsHatchThin)) else similarIdx := IfThen(singleLine, ord(fpsDiagUpNarrow), ord(fpsHatchNarrow)); end else begin if (ALineDistance >= WIDE_DISTANCE) or (ALineDistance <= -8) then similarIdx := IfThen(singleLine, ord(fpsDiagDownThin), ord(fpsHatchThin)) else similarIdx := IfThen(singleLine, ord(fpsDiagDownNarrow), ord(fpsHatchNarrow)); end; if similarIdx > -1 then Result := Items[similarIdx].DotPattern else Result := SOLID_DOT_FILL_PATTERN; end; function TsRawFillPatternList.IndexOfName(AName: String): Integer; begin for Result := 0 to Count-1 do if Items[Result].Name = AName then exit; Result := -1; end; function TsRawFillPatternList.GetItem(AIndex: Integer): TsRawFillPattern; begin Result := TsRawFillPattern(inherited Items[AIndex]); end; procedure TsRawFillPatternList.SetItem(AIndex: Integer; AValue: TsRawFillPattern); begin TsRawFillPattern(inherited Items[AIndex]).CopyFrom(AValue); end; { ------------------------------------------------------------------------------ global fill pattern procedures -------------------------------------------------------------------------------} var RawFillPatterns: TsRawFillPatternList = nil; RawFillPatterns_ReferenceCounter: Integer = 0; procedure CreateRawFillPatterns; begin if RawFillPatterns_ReferenceCounter = 0 then begin RawFillPatterns := TsRawFillPatternList.Create; RawFillPatterns.AddBuiltinPatterns; end; inc(RawFillPatterns_ReferenceCounter); end; procedure DestroyRawFillPatterns; begin dec(RawFillPatterns_ReferenceCounter); if RawFillPatterns_ReferenceCounter <= 0 then begin FreeAndNil(RawfillPatterns); RawFillPatterns_ReferenceCounter := 0; end; end; function GetRawFillPattern(APatternIndex: Integer): TsRawFillPattern; begin if Assigned(RawFillPatterns) then Result := RawFillPatterns.Items[APatternIndex] else Result := nil; end; { Finds the index of the line fill pattern having the specified parameters. Returns -1 if not found. } function GetRawFillPatternIndex(ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; begin if Assigned(RawFillPatterns) then Result := RawFillPatterns.FindLinePatternIndex(ALineDistance, ALineAngle, ALineWidth, AMultiplier) else Result := -1; end; function GetRawFillPatternName(APatternStyle: TsChartFillPatternStyle): String; const PatternName: Array[TsChartFillPatternStyle] of string = ( 'GRAY05', 'GRAY10', 'GRAY20', 'GRAY25', 'GRAY30', 'GRAY40', 'GRAY50', 'GRAY60', 'GRAY70', 'GRAY75', 'GRAY80', 'GRAY90', 'HOR_THICK', 'VERT_THICK', 'DIAG_UP_THICK', 'DIAG_DOWN_THICK', 'HATCH_THICK', 'CROSS_THICK', 'HOR_THIN', 'VERT_THIN', 'DIAG_UP_THIN', 'DIAG_DOWN_THIN', 'HATCH_THIN', 'CROSS_THIN', 'HOR_NARROW', 'VERT_NARROW', 'DIAG_UP_NARROW', 'DIAG_DOWN_NARROW', 'HATCH_NARROW', 'CROSS_NARROW', 'HOR_DASH', 'VERT_DASH', 'DIAG_UP_DASH', 'DIAG_DOWN_DASH', 'CROSS_DOT', 'HATCH_DOT', 'BRICK_HOR', 'BRICK_DIAG', 'CHECKERBOARD_LARGE', 'CHECKERBOARD_SMALL', 'CONFETTI_LARGE', 'CONFETTI_SMALL', 'DIAMOND', 'DIVOT', 'PLAID', 'SHINGLE', 'SPHERE', 'TRELLIS', 'WAVE', 'WEAVE', 'ZIGZAG' ); begin Result := PatternName[APatternStyle]; end; function GetRawFillPatternCount: Integer; begin if Assigned(RawFillPatterns) then Result := RawFillPatterns.Count else Result := 0; end; function RegisterRawFillPattern(AName: String; ALineDistance, ALineAngle, ALineWidth: Single; AMultiplier: TsLineFillPatternMultiplier): Integer; begin if not Assigned(RawFillPatterns) then CreateRawFillPatterns; Result := RawFillPatterns.AddLineFillPattern(AName, '', ALineDistance, ALineAngle, ALineWidth, AMultiplier) end; {=============================================================================== Line patterns ===============================================================================} constructor TsRawLinePattern.Create(AName: String; AElement1Length: Single; AElement1Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit); begin inherited Create; Name := AName; Element1.Length := AElement1Length; Element1.Count := AElement1Count; Element2.Length := 0; Element2.Count := 0; DistanceLength := ADistanceLength; LengthUnit := AUnit; end; constructor TsRawLinePattern.Create(AName: String; AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit); begin inherited Create; Name := AName; Element1.Length := AElement1Length; Element1.Count := AElement1Count; Element2.Length := AElement2Length; Element2.Count := AElement2Count; DistanceLength := ADistanceLength; LengthUnit := AUnit; end; procedure TsRawLinePattern.CopyFrom(ASource: TsRawLinePattern); begin Name := ASource.Name; Element1 := ASource.Element1; Element2 := ASource.Element2; DistanceLength := ASource.DistanceLength; LengthUnit := ASource.LengthUnit; end; { TsRawLinePatternList } procedure TsRawLinePatternList.AddBuiltinPatterns; begin // solid line AddPattern(GetRawLinePatternName(clsSolid), 10, 1, 0, 0, 0, cluMillimeters); // no line AddPattern(GetRawLinePatternName(clsNoLine), 0, 0, 0, 0, 0, cluMillimeters); // fine dots AddPattern(GetRawLinePatternName(clsFineDot), 120, 1, 0, 0, 160, cluPercentage); // dotted AddPattern(GetRawLinePatternName(clsDot), 120, 1, 0, 0, 340, cluPercentage); // dashed (- - - - ) AddPattern(GetRawLinePatternName(clsDash), 400, 1, 0, 0, 340, cluPercentage); // dash-dot (- . - . - ) AddPattern(GetRawLinePatternName(clsDashDot), 400, 1, 90, 1, 340, cluPercentage); // long dash (-- -- --) AddPattern(GetRawLinePatternName(clsLongDash), 820, 1, 0, 0, 340, cluPercentage); // long dash-dot (-- . -- . -- . ) AddPattern(GetRawLinePatternName(clsLongDashDot), 820, 1, 90, 1, 340, cluPercentage); // long dash-dot-dot (-- . . -- . . ) AddPattern(GetRawLinePatternName(clsLongDashDotDot), 820, 1, 90, 2, 340, cluPercentage); (* // dashed (- - - - ) AddPattern(GetRawLinePatternName(clsDash), 600, 1, 0, 0, 500, cluPercentage); // dash-dot (- . - . - ) AddPattern(GetRawLinePatternName(clsDashDot), 600, 1, 120, 1, 500, cluPercentage); // long dash (-- -- --) AddPattern(GetRawLinePatternName(clsLongDash), 1200, 1, 0, 0, 600, cluPercentage); // long dash-dot (-- . -- . -- . ) AddPattern(GetRawLinePatternName(clsLongDashDot), 1200, 1, 120, 1, 600, cluPercentage); // long dash-dot-dot (-- . . -- . . ) AddPattern(GetRawLinePatternName(clsLongDashDotDot), 1200, 1, 120, 2, 600, cluPercentage); *) end; function TsRawLinePatternList.AddOrReplace(APattern: TsRawLinePattern): Integer; var idx: Integer; begin idx := IndexOfName(APattern.Name); if idx = -1 then Result := Add(APattern) else begin Items[idx].CopyFrom(APattern); // Insert(idx, APattern); Result := idx; end; end; function TsRawLinePatternList.AddPattern(AName: String; AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; ALengthUnit: TsChartLengthUnit): Integer; var patt: TsRawLinePattern; begin patt := TsRawLinePattern.Create(AName, AElement1Length, AElement1Count, AElement2Length, AElement2Count, ADistanceLength, ALengthUnit ); // patt.FExcelName := AExcelName; Result := AddOrReplace(patt); end; function TsRawLinePatternList.FindPatternIndex( AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; ALengthUnit: TsChartlengthUnit): Integer; var i: Integer; patt: TsRawLinePattern; begin for i := 0 to Count-1 do begin patt := Items[i]; if SameValue(AElement1Length, patt.Element1.Length, 0.1) and (AElement1Count = patt.Element1.Count) and SameValue(AElement2Length, patt.Element2.Length, 0.1) and (AElement2Count = patt.Element2.Count) and SameValue(ADistanceLength, patt.DistanceLength, 0.1) and (ALengthUnit = patt.LengthUnit) then begin Result := i; exit; end; end; Result := -1; end; function TsRawLinePatternList.GetItem(AIndex: Integer): TsRawLinePattern; begin Result := TsRawLinePattern(inherited Items[AIndex]); end; function TsRawLinePatternList.IndexOfName(AName: String): Integer; begin for Result := 0 to Count-1 do if Items[Result].Name = AName then exit; Result := -1; end; procedure TsRawLinePatternList.SetItem(AIndex: Integer; AValue: TsRawLinePattern); begin TsRawLinePattern(inherited Items[AIndex]).CopyFrom(AValue); end; {------------------------------------------------------------------------------- global line pattern procedures -------------------------------------------------------------------------------} var RawLinePatterns: TsRawLinePatternList = nil; RawLinePatterns_ReferenceCounter: Integer = 0; procedure CreateRawLinePatterns; begin if RawLinePatterns_ReferenceCounter = 0 then begin RawLinePatterns := TsRawLinePatternList.Create; RawLinePatterns.AddBuiltinPatterns; end; inc(RawLinePatterns_ReferenceCounter); end; procedure DestroyRawLinePatterns; begin dec(RawLinePatterns_ReferenceCounter); if RawLinePatterns_ReferenceCounter <= 0 then begin FreeAndNil(RawLinePatterns); RawLinePatterns_ReferenceCounter := 0; end; end; function GetRawLinePattern(APatternIndex: Integer): TsRawLinePattern; begin if Assigned(RawLinePatterns) then Result := RawLinePatterns.Items[APatternIndex] else Result := nil; end; function GetRawLinePatternCount: Integer; begin if Assigned(RawLinePatterns) then Result := RawLinePatterns.Count else Result := 0; end; {@@ Returns the index of the line pattern with the given pattern name. } function GetRawLinePatternIndex(APatternName: String): Integer; begin if Assigned(RawLinePatterns) then Result := RawLinePatterns.IndexOfName(APatternName) else Result := -1; end; {@@ Finds the index of the line pattern having the specified parameters. Returns -1 if not found. } function GetRawLinePatternIndex(AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit): Integer; begin if Assigned(RawLinePatterns) then Result := RawLinePatterns.FindPatternIndex( AElement1Length, AElement1Count, AElement2Length, AElement2Count, ADistanceLength, AUnit ) else Result := -1; end; function GetRawLinePatternName(APatternStyle: TsChartLinePatternStyle): String; const PatternName: Array[TsChartLinePatternStyle] of string = ( 'SOLID', 'NO_LINE', 'FINE_DOT', 'DOT', 'DASH', 'DASH_DOT', 'LONG_DASH', 'LONG_DASH_DOT', 'LONG_DASH_DOT_DOT', 'CUSTOM' ); begin Result := PatternName[APatternStyle]; end; function RegisterRawLinePattern(AName: String; AElementLength: Single; ADistanceLength: Single; AUnit: TsChartLengthUnit): Integer; begin Result := RegisterRawLinePattern(AName, AElementLength, 1, 0, 0, ADistanceLength, AUnit); end; function RegisterRawLinePattern(AName: String; AElement1Length: Single; AElement1Count: Integer; AElement2Length: Single; AElement2Count: Integer; ADistanceLength: Single; AUnit: TsChartLengthUnit): Integer; begin if not Assigned(RawLinePatterns) then CreateRawLinePatterns; Result := RawLinePatterns.AddPattern(AName, AElement1Length, AElement1Count, AElement2Length, AElement2Count, ADistanceLength, AUnit); end; finalization FreeAndNil(RawFillPatterns); FreeAndNil(RawLinePatterns); end.