fpvectorial: Many improvements to the layer system, svg reading of colors and of paths

git-svn-id: trunk@39672 -
This commit is contained in:
sekelsenmat 2012-12-28 10:33:19 +00:00
parent 5b91afa3ef
commit 6c7d1e61e7
3 changed files with 356 additions and 68 deletions

View File

@ -1922,7 +1922,7 @@ begin
{ if Length(AStr) <= 1 then Exit;
Result := StrToFloat(Copy(AStr, 2, Length(AStr) - 1));}
Result := StrToFloat(Copy(AStr, 2, Length(AStr) - 1), FPointSeparator);}
end;
function TvDXFVectorialReader.ConvertDXFStringToUTF8(AStr: string): string;

View File

@ -530,6 +530,8 @@ type
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
{ TvEntityWithSubEntities }
TvEntityWithSubEntities = class(TvEntity)
private
FCurIndex: Integer;
@ -544,8 +546,8 @@ type
function GetNextEntity: TvEntity;
procedure AddEntity(AEntity: TvEntity);
procedure Clear; override;
//
// Never add a Render() procedure to TvBlock, because blocks are invisible!
procedure Render(ADest: TFPCustomCanvas; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
@ -556,6 +558,9 @@ type
{ TvBlock }
TvBlock = class(TvEntityWithSubEntities)
public
procedure Render(ADest: TFPCustomCanvas; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
@ -581,8 +586,6 @@ type
TvLayer = class(TvEntityWithSubEntities)
public
procedure Render(ADest: TFPCustomCanvas; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{ TvVectorialDocument }
@ -676,6 +679,7 @@ type
procedure AddLineToPath(AX, AY: Double; AColor: TFPColor); overload;
procedure AddLineToPath(AX, AY, AZ: Double); overload;
procedure GetCurrentPathPenPos(var AX, AY: Double);
procedure GetTmpPathStartPos(var AX, AY: Double);
procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload;
procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload;
procedure SetBrushColor(AColor: TFPColor);
@ -694,7 +698,9 @@ type
function AddBlock(AName: string; AX, AY, AZ: Double): TvBlock;
function AddInsert(AX, AY, AZ: Double; ABlock: TvBlock): TvInsert;
// Layers
function AddLayer(AName: string): TvLayer;
function AddLayerAndSetAsCurrent(AName: string): TvLayer;
procedure ClearLayerSelection();
// Dimensions
function AddAlignedDimension(BaseLeft, BaseRight, DimLeft, DimRight: T3DPoint; AOnlyCreate: Boolean = False): TvAlignedDimension;
function AddRadialDimension(AIsDiameter: Boolean; ACenter, ADimLeft, ADimRight: T3DPoint; AOnlyCreate: Boolean = False): TvRadialDimension;
@ -2619,6 +2625,27 @@ begin
FElements.Clear;
end;
procedure TvEntityWithSubEntities.Render(ADest: TFPCustomCanvas;
ADestX: Integer; ADestY: Integer; AMulX: Double; AMulY: Double);
var
lEntity: TvEntity;
begin
inherited Render(ADest, ADestX, ADestY, AMulX, AMulY);
lEntity := GetFirstEntity();
while lEntity <> nil do
begin
{$IFDEF FPVECTORIAL_DEBUG_BLOCKS}
//WriteLn(Format('[TvInsert.Render] Name=%s Block=%s Entity=%s EntityXY=%f | %f BlockXY=%f | %f InsertXY=%f | %f',
// [Name, Block.Name, lEntity.ClassName, lEntity.X, lEntity.Y, Block.X, Block.Y, X, Y]));
{$ENDIF}
// Render
lEntity.Render(ADest, ADestX, ADestY, AMulX, AMuly);
lEntity := GetNextEntity();
end;
end;
{ TvInsert }
procedure TvInsert.Render(ADest: TFPCustomCanvas; ADestX: Integer;
@ -2647,27 +2674,12 @@ begin
end;
end;
{ TvLayer }
{ TvBlock }
procedure TvLayer.Render(ADest: TFPCustomCanvas; ADestX: Integer;
procedure TvBlock.Render(ADest: TFPCustomCanvas; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
var
lEntity: TvEntity;
begin
inherited Render(ADest, ADestX, ADestY, AMulX, AMulY);
lEntity := GetFirstEntity();
while lEntity <> nil do
begin
{$IFDEF FPVECTORIAL_DEBUG_BLOCKS}
//WriteLn(Format('[TvInsert.Render] Name=%s Block=%s Entity=%s EntityXY=%f | %f BlockXY=%f | %f InsertXY=%f | %f',
// [Name, Block.Name, lEntity.ClassName, lEntity.X, lEntity.Y, Block.X, Block.Y, X, Y]));
{$ENDIF}
// Render
lEntity.Render(ADest, ADestX, ADestY, AMulX, AMuly);
lEntity := GetNextEntity();
end;
// TvBlock.Render must be empty! Because blocks are invisible by themselves
end;
{ TvVectorialPage }
@ -2796,6 +2808,7 @@ begin
FEntities.ForEachCall(CallbackDeleteEntity, nil);
FEntities.Clear();
ClearTmpPath();
ClearLayerSelection();
end;
{@@
@ -2828,8 +2841,17 @@ end;
}
function TvVectorialPage.AddEntity(AEntity: TvEntity): Integer;
begin
Result := FEntities.Count;
FEntities.Add(Pointer(AEntity));
if FCurrentLayer = nil then
begin
Result := FEntities.Count;
FEntities.Add(Pointer(AEntity));
end
// If a layer is selected as current, add elements to it instead
else
begin
Result := FCurrentLayer.GetSubpartCount();
FCurrentLayer.AddEntity(AEntity);
end;
end;
function TvVectorialPage.AddPathCopyMem(APath: TPath; AOnlyCreate: Boolean = False): TPath;
@ -2944,6 +2966,18 @@ begin
AY := T2DSegment(FTmpPath.PointsEnd).Y;
end;
procedure TvVectorialPage.GetTmpPathStartPos(var AX, AY: Double);
begin
AX := 0;
AY := 0;
if (FTmpPath = nil) or (FTmpPath.GetSubpartCount() <= 0) or (FTmpPath.Points = nil) then Exit;
if FTmpPath.Points is T2DSegment then
begin
AX := T2DSegment(FTmpPath.Points).X;
AY := T2DSegment(FTmpPath.Points).Y;
end;
end;
{@@
Adds a bezier element to the path. It starts where the previous element ended
and it goes throw the control points [AX1, AY1] and [AX2, AY2] and ends
@ -3125,9 +3159,21 @@ begin
Result := lInsert;
end;
function TvVectorialPage.AddLayer(AName: string): TvLayer;
begin
Result := TvLayer.Create;
AddEntity(Result);
end;
function TvVectorialPage.AddLayerAndSetAsCurrent(AName: string): TvLayer;
begin
Result := nil;//TvLayer.Create;
Result := AddLayer(AName);
FCurrentLayer := Result;
end;
procedure TvVectorialPage.ClearLayerSelection;
begin
FCurrentLayer := nil;
end;

View File

@ -18,7 +18,19 @@ uses
fpvectorial, fpvutils;
type
TSVGTokenType = (sttMoveTo, sttLineTo, sttBezierTo, sttFloatValue);
TSVGTokenType = (
// moves
sttMoveTo, sttRelativeMoveTo,
// Close Path
sttClosePath,
// lines
sttLineTo, sttRelativeLineTo,
// cubic beziers
sttBezierTo, sttRelativeBezierTo,
// quadratic beziers
sttQuadraticBezierTo, sttRelativeQuadraticBezierTo,
// numbers
sttFloatValue);
TSVGToken = class
TokenType: TSVGTokenType;
@ -116,12 +128,24 @@ var
begin
lToken := TSVGToken.Create;
lStr := LowerCase(AStr);
lStr := Trim(AStr);
if lStr = '' then Exit;
if lStr[1] = 'm' then lToken.TokenType := sttMoveTo
else if lStr[1] = 'l' then lToken.TokenType := sttLineTo
else if lStr[1] = 'c' then lToken.TokenType := sttBezierTo
// Moves
if lStr[1] = 'M' then lToken.TokenType := sttMoveTo
else if lStr[1] = 'm' then lToken.TokenType := sttRelativeMoveTo
// Close Path
else if lStr[1] = 'Z' then lToken.TokenType := sttClosePath
else if lStr[1] = 'z' then lToken.TokenType := sttClosePath
// Lines
else if lStr[1] = 'L' then lToken.TokenType := sttLineTo
else if lStr[1] = 'l' then lToken.TokenType := sttRelativeLineTo
// cubic Bézier curve commands
else if lStr[1] = 'C' then lToken.TokenType := sttBezierTo
else if lStr[1] = 'c' then lToken.TokenType := sttRelativeBezierTo
// quadratic beziers
else if lStr[1] = 'Q' then lToken.TokenType := sttQuadraticBezierTo
else if lStr[1] = 'q' then lToken.TokenType := sttRelativeQuadraticBezierTo
else
begin
lToken.TokenType := sttFloatValue;
@ -148,7 +172,7 @@ var
i: Integer;
lTmpStr: string = '';
lState: Integer;
lCurChar: Char;
lFirstTmpStrChar, lCurChar: Char;
begin
lState := 0;
@ -171,7 +195,22 @@ begin
lTmpStr := '';
end
else
begin
// Check for a break, from letter to number
if (Length(lTmpStr) >= 1) then
begin
lFirstTmpStrChar := lTmpStr[1];
if ((lFirstTmpStrChar in ['a'..'z', 'A'..'Z']) and not (lCurChar in ['a'..'z', 'A'..'Z'])) or
(not (lFirstTmpStrChar in ['a'..'z', 'A'..'Z']) and (lCurChar in ['a'..'z', 'A'..'Z'])) then
begin
AddToken(lTmpStr);
lTmpStr := '';
Continue;
end;
end;
lTmpStr := lTmpStr + lCurChar;
end;
Inc(i);
end;
@ -182,6 +221,9 @@ begin
end;
end;
end;
// If there is a token still to be added, add it now
if (lState = 0) and (lTmpStr <> '') then AddToken(lTmpStr);
end;
{ Example of a supported SVG image:
@ -256,15 +298,16 @@ begin
if (Length(lValue) > 1) and (lValue[1] = '#') then
begin
lStr := Copy(lValue, 2, 2);
Result.Red := StrToInt('$'+lStr);
Result.Red := StrToInt('$'+lStr)*$101;
lStr := Copy(lValue, 4, 2);
Result.Blue := StrToInt('$'+lStr);
Result.Green := StrToInt('$'+lStr)*$101;
lStr := Copy(lValue, 6, 2);
Result.Green := StrToInt('$'+lStr);
Result.Blue := StrToInt('$'+lStr)*$101;
Exit;
end;
// Support for named colors
// List here: http://www.december.com/html/spec/colorsvghex.html
case lValue of
'black': Result := colBlack;
'navy': Result.Blue := $8080;
@ -336,13 +379,55 @@ begin
Result.Green := $8B8B;
Result.Blue := $5757;
end;
'darkslategray', 'darkslategrey':
begin
Result.Red := $2F2F;
Result.Green := $4F4F;
Result.Blue := $4F4F;
end;
'limegreen':
begin
Result.Red := $3232;
Result.Green := $CDCD;
Result.Blue := $3232;
end;
'mediumseagreen':
begin
Result.Red := $3C3C;
Result.Green := $CBCB;
Result.Blue := $7171;
end;
'turquoise':
begin
Result.Red := $4040;
Result.Green := $E0E0;
Result.Blue := $D0D0;
end;
'royalblue':
begin
Result.Red := $4141;
Result.Green := $6969;
Result.Blue := $E1E1;
end;
'steelblue':
begin
Result.Red := $4646;
Result.Green := $8282;
Result.Blue := $B4B4;
end;
'darkslateblue':
begin
Result.Red := $4848;
Result.Green := $3D3D;
Result.Blue := $8B8B;
end;
'mediumturquoise':
begin
Result.Red := $4848;
Result.Green := $D1D1;
Result.Blue := $CCCC;
end;
{
darkslategray #2F4F4F
darkslategrey #2F4F4F limegreen #32CD32
mediumseagreen #3CB371
turquoise #40E0D0 royalblue #4169E1
steelblue #4682B4
darkslateblue #483D8B mediumturquoise #48D1CC
indigo #4B0082
darkolivegreen #556B2F cadetblue #5F9EA0
cornflowerblue #6495ED
@ -365,10 +450,31 @@ chartreuse #7FFF00
'purple': Result := colPurple;
'olive': Result := colOlive;
'gray', 'grey': Result := colGray;
'skyblue':
begin
Result.Red := $8787;
Result.Green := $CECE;
Result.Blue := $EBEB;
end;
'lightskyblue':
begin
Result.Red := $8787;
Result.Green := $CECE;
Result.Blue := $FAFA;
end;
'blueviolet':
begin
Result.Red := $8A8A;
Result.Green := $2B2B;
Result.Blue := $E2E2;
end;
'darkred': Result.Red := $8B8B;
'darkmagenta':
begin
Result.Red := $8B8B;
Result.Blue := $8B8B;
end;
{
skyblue #87CEEB lightskyblue #87CEFA
blueviolet #8A2BE2
darkred #8B0000 darkmagenta #8B008B
saddlebrown #8B4513
darkseagreen #8FBC8F lightgreen #90EE90
mediumpurple #9370DB
@ -384,8 +490,15 @@ lightsteelblue #B0C4DE
darkgoldenrod #B8860B
mediumorchid #BA55D3 rosybrown #BC8F8F
darkkhaki #BDB76B
silver(16) #C0C0C0 mediumvioletred #C71585
indianred #CD5C5C
}
'silver': Result := colSilver;
'mediumvioletred':
begin
Result.Red := $C7C7;
Result.Green := $1515;
Result.Blue := $8585;
end;
{indianred #CD5C5C
peru #CD853F chocolate #D2691E
tan #D2B48C
lightgray #D3D3D3 lightgrey #D3D3D3
@ -396,11 +509,56 @@ palevioletred #DB7093
plum #DDA0DD
burlywood #DEB887 lightcyan #E0FFFF
lavender #E6E6FA
darksalmon #E9967A violet #EE82EE
palegoldenrod #EEE8AA
lightcoral #F08080 khaki #F0E68C
aliceblue #F0F8FF
honeydew #F0FFF0 azure #F0FFFF
}
'darksalmon':
begin
Result.Red := $E9E9;
Result.Green := $9696;
Result.Blue := $7A7A;
end;
'violet':
begin
Result.Red := $EEEE;
Result.Green := $8282;
Result.Blue := $EEEE;
end;
'palegoldenrod':
begin
Result.Red := $EEEE;
Result.Green := $E8E8;
Result.Blue := $AAAA;
end;
'lightcoral':
begin
Result.Red := $F0F0;
Result.Green := $8080;
Result.Blue := $8080;
end;
'khaki':
begin
Result.Red := $F0F0;
Result.Green := $E6E6;
Result.Blue := $8C8C;
end;
'aliceblue':
begin
Result.Red := $F0F0;
Result.Green := $F8F8;
Result.Blue := $FFFF;
end;
'honeydew':
begin
Result.Red := $F0F0;
Result.Green := $FFFF;
Result.Blue := $F0F0;
end;
'azure':
begin
Result.Red := $F0F0;
Result.Green := $FFFF;
Result.Blue := $FFFF;
end;
{
sandybrown #F4A460
wheat #F5DEB3 beige #F5F5DC
whitesmoke #F5F5F5
@ -710,16 +868,22 @@ begin
AData.StartPath();
ReadPathFromString(UTF8Encode(lDStr), AData, ADoc);
lPath := AData.EndPath();
// Add the pen
// Add default SVG pen/brush
lPath.Pen.Style := psClear;
lPath.Brush.Color := colBlack;
lPath.Brush.Style := bsSolid;
// Add the pen/brush
ReadSVGStyle(lStyleStr, lPath);
end;
// Documentation: http://www.w3.org/TR/SVG/paths.html
procedure TvSVGVectorialReader.ReadPathFromString(AStr: string;
AData: TvVectorialPage; ADoc: TvVectorialDocument);
var
i: Integer;
X, Y, X2, Y2, X3, Y3: Double;
CurX, CurY: Double;
lCurTokenType: TSVGTokenType;
begin
FSVGPathTokenizer.Tokens.Clear;
FSVGPathTokenizer.TokenizePathString(AStr);
@ -729,31 +893,74 @@ begin
i := 0;
while i < FSVGPathTokenizer.Tokens.Count do
begin
if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttMoveTo then
lCurTokenType := FSVGPathTokenizer.Tokens.Items[i].TokenType;
// --------------
// Moves
// --------------
if lCurTokenType in [sttMoveTo, sttRelativeMoveTo] then
begin
CurX := FSVGPathTokenizer.Tokens.Items[i+1].Value;
CurY := FSVGPathTokenizer.Tokens.Items[i+2].Value;
ConvertSVGCoordinatesToFPVCoordinates(AData, CurX, CurY, CurX, CurY);
X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
// take care of relative or absolute
if lCurTokenType = sttRelativeMoveTo then
begin
CurX := CurX + X;
CurY := CurY + Y;
end
else
begin
CurX := X;
CurY := Y;
end;
AData.AddMoveToPath(CurX, CurY);
Inc(i, 3);
end
else if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttLineTo then
// --------------
// Close Path
// --------------
else if lCurTokenType = sttClosePath then
begin
// Get the first point
AData.GetTmpPathStartPos(X, Y);
// And repeat it
CurX := X;
CurY := Y;
AData.AddLineToPath(CurX, CurY);
Inc(i, 3);
end
// --------------
// Lines
// --------------
else if lCurTokenType in [sttLineTo, sttRelativeLineTo] then
begin
X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
// LineTo uses relative coordenates in SVG
CurX := CurX + X;
CurY := CurY + Y;
// "l" LineTo uses relative coordenates in SVG
if lCurTokenType = sttRelativeLineTo then
begin
CurX := CurX + X;
CurY := CurY + Y;
end
else
begin
CurX := X;
CurY := Y;
end;
AData.AddLineToPath(CurX, CurY);
Inc(i, 3);
end
else if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttBezierTo then
// --------------
// Cubic Bezier
// --------------
else if lCurTokenType in [sttBezierTo, sttRelativeBezierTo] then
begin
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
@ -766,14 +973,49 @@ begin
ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3);
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3 + CurX, Y3 + CurY, X + CurX, Y + CurY);
// BezierTo uses relative coordenates in SVG
CurX := CurX + X;
CurY := CurY + Y;
if lCurTokenType = sttRelativeBezierTo then
begin
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3 + CurX, Y3 + CurY, X + CurX, Y + CurY);
CurX := CurX + X;
CurY := CurY + Y;
end
else
begin
AData.AddBezierToPath(X2, Y2, X3, Y3, X, Y);
CurX := X;
CurY := Y;
end;
Inc(i, 7);
end
// --------------
// Quadratic Bezier
// --------------
else if lCurTokenType in [sttQuadraticBezierTo, sttRelativeQuadraticBezierTo] then
begin
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
X := FSVGPathTokenizer.Tokens.Items[i+3].Value;
Y := FSVGPathTokenizer.Tokens.Items[i+4].Value;
ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
if lCurTokenType = sttRelativeQuadraticBezierTo then
begin
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X2 + CurX, Y2 + CurY, X + CurX, Y + CurY);
CurX := CurX + X;
CurY := CurY + Y;
end
else
begin
AData.AddBezierToPath(X2, Y2, X2, Y2, X, Y);
CurX := X;
CurY := Y;
end;
Inc(i, 5);
end
else
begin
Inc(i);
@ -998,7 +1240,7 @@ begin
end
else // If there is no unit, just use StrToFloat
begin
Result := StrToFloat(AStr);
Result := StrToFloat(AStr, FPointSeparator);
end;
end;