fpvectorial-svg: Rewrites the transformation matrix support, it much more flexible and robust now, but does not yet work in some corner cases like transformations in layers and inserts

git-svn-id: trunk@42296 -
This commit is contained in:
sekelsenmat 2013-08-04 07:16:14 +00:00
parent cf4f0b577b
commit 3b079a52cc
4 changed files with 208 additions and 38 deletions

View File

@ -3063,8 +3063,8 @@ begin
Exit(True);
end;
CurrentGraphicState.ScaleX := Param2.FloatValue;
CurrentGraphicState.ScaleY := Param1.FloatValue;
CurrentGraphicState.ScaleX := CurrentGraphicState.ScaleX * Param2.FloatValue;
CurrentGraphicState.ScaleY := CurrentGraphicState.ScaleY * Param1.FloatValue;
{$ifdef FPVECTORIALDEBUG_PATHS}
WriteLn(Format('[TvEPSVectorialReader.ExecuteGraphicStateOperatorsDI] scale %f %f',
[CurrentGraphicState.ScaleX, CurrentGraphicState.ScaleY]));

View File

@ -290,6 +290,7 @@ type
procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); virtual;
function GetSubpartCount: Integer; virtual;
procedure PositionSubparts(ADest: TFPCustomCanvas; ABaseX, ABaseY: Double); virtual;
procedure Scale(ADeltaScaleX, ADeltaScaleY: Double); virtual;
procedure Rotate(AAngle: Double; ABase: T3DPoint); virtual; // Angle in radians
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); virtual;
@ -351,6 +352,7 @@ type
constructor Create; override;
procedure ApplyFontToCanvas(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; AMulX: Double = 1.0);
procedure AssignFont(AFont: TvFont);
procedure Scale(ADeltaScaleX, ADeltaScaleY: Double); override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
@ -1528,6 +1530,11 @@ begin
end;
procedure TvEntity.Scale(ADeltaScaleX, ADeltaScaleY: Double);
begin
end;
procedure TvEntity.Rotate(AAngle: Double; ABase: T3DPoint);
begin
@ -1710,6 +1717,13 @@ begin
Font.StrikeThrough := AFont.StrikeThrough;
end;
procedure TvEntityWithPenBrushAndFont.Scale(ADeltaScaleX, ADeltaScaleY: Double);
begin
inherited Scale(ADeltaScaleX, ADeltaScaleY);
Font.Size := Round(Font.Size * ADeltaScaleX);
end;
procedure TvEntityWithPenBrushAndFont.Render(ADest: TFPCustomCanvas;
ARenderInfo: TvRenderInfo; ADestX: Integer; ADestY: Integer; AMulX: Double;
AMulY: Double);

View File

@ -348,6 +348,11 @@ end;
// skewY(TY) => a=1 b=tan(TY) c=0 d=1 rest=0
// skew(TX,TY) => a=1 b=tan(TY) c=tan(TX) d=1 rest=0
// rotate(T) => a=cos(T) b=sin(T) c=-sin(T) d=cos(T) rest=0
//
// Example:
// 0.860815 0 -0 1.07602 339.302 489.171
// Which has a Scale and Translate
//
procedure ConvertTransformationMatrixToOperations(AA, AB, AC, AD, AE,
AF: Double; out ATranslateX, ATranslateY, AScaleX, AScaleY, ASkewX, ASkewY,
ARotate: Double);
@ -360,6 +365,7 @@ begin
ASkewY := 0;
ARotate := 0;
// This is valid if AB=AC=0
ATranslateX := AE;
ATranslateY := AF;
AScaleX := AA;

View File

@ -19,6 +19,8 @@ uses
fpvectorial, fpvutils, lazutf8, TypInfo;
type
TDoubleArray = array of Double;
TSVGTokenType = (
// moves
sttMoveTo, sttRelativeMoveTo,
@ -39,6 +41,7 @@ type
TSVGToken = class
TokenType: TSVGTokenType;
Value: Float;
StrValue: string; // filled only by TokenizeFunctions
end;
TSVGTokenList = specialize TFPGList<TSVGToken>;
@ -54,6 +57,7 @@ type
Destructor Destroy; override;
procedure AddToken(AStr: string);
procedure TokenizePathString(AStr: string);
procedure TokenizeFunctions(AStr: string);
function DebugOutTokensAsString: string;
end;
@ -74,9 +78,11 @@ type
function ReadSVGPenStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPen): TvSetPenBrushAndFontElements;
function ReadSVGBrushStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPenAndBrush): TvSetPenBrushAndFontElements;
function ReadSVGFontStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPenBrushAndFont): TvSetPenBrushAndFontElements;
function ReadSVGGeneralStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPen): TvSetPenBrushAndFontElements;
function IsAttributeFromStyle(AStr: string): Boolean;
procedure ApplyLayerStyles(ADestEntity: TvEntity);
procedure ReadAndApplySVGTransformationMatrix(AMatrix: string; ADestEntity: TvEntity);
function ReadSpaceSeparatedFloats(AInput: string): TDoubleArray;
procedure ReadSVGTransformationMatrix(AMatrix: string; out AA, AB, AC, AD, AE, AF: Double);
//
procedure ReadDefsFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
//
@ -182,7 +188,7 @@ begin
begin
lToken.TokenType := sttFloatValue;
try
lToken.Value := StrToFloat(AStr, FPointSeparator);
lToken.Value := StrToFloat(AStr, FPointSeparator);
except
on MyException: Exception do
begin
@ -269,6 +275,57 @@ begin
if (lState = 0) and (lTmpStr <> '') then AddToken(lTmpStr);
end;
procedure TSVGPathTokenizer.TokenizeFunctions(AStr: string);
const
Str_Space: Char = ' ';
Str_Start_Params: Char = '(';
Str_End_Params: Char = ')';
ListOfCommandLetters: set of Char = ['a'..'d', 'f'..'z', 'A'..'D', 'F'..'Z'];
var
i: Integer;
lTmpStr: string = '';
lState: Integer;
lFirstTmpStrChar, lCurChar: Char;
lToken: TSVGToken;
begin
lState := 0;
i := 1;
while i <= Length(AStr) do
begin
case lState of
0: // Adding to the tmp string
begin
lCurChar := AStr[i];
if lCurChar in [Str_Space, Str_Start_Params, Str_End_Params] then
begin
lState := 1;
// Add the token
lToken := TSVGToken.Create;
lToken.StrValue := lTmpStr;
Tokens.Add(lToken);
//
lTmpStr := '';
end
else
begin
lTmpStr := lTmpStr + lCurChar;
end;
Inc(i);
end;
1: // Removing spaces
begin
if AStr[i] <> Str_Space then lState := 0
else Inc(i);
end;
end;
end;
// If there is a token still to be added, add it now
if (lState = 0) and (lTmpStr <> '') then AddToken(lTmpStr);
end;
function TSVGPathTokenizer.DebugOutTokensAsString: string;
var
i: Integer;
@ -711,6 +768,7 @@ begin
lStyleKeyStr := Copy(lStr, 0, lPosEqual-1);
lStyleValueStr := Copy(lStr, lPosEqual+1, Length(lStr));
ReadSVGPenStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, ADestEntity);
ReadSVGGeneralStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, ADestEntity);
if AUseFillAsPen and (lStyleKeyStr = 'fill') then
Result := Result + ReadSVGPenStyleWithKeyAndValue('stroke', lStyleValueStr, ADestEntity)
else if ADestEntity is TvText then
@ -864,11 +922,75 @@ begin
end;
end;
function TvSVGVectorialReader.ReadSVGGeneralStyleWithKeyAndValue(AKey,
AValue: string; ADestEntity: TvEntityWithPen): TvSetPenBrushAndFontElements;
var
// transform
MA, MB, MC, MD, ME, MF: Double;
lMTranslateX, lMTranslateY, lMScaleX, lMScaleY, lMSkewX, lMSkewY, lMRotate: Double;
lTokenizer: TSVGPathTokenizer;
i: Integer;
lFunctionName, lParamStr: string;
var
lMatrixElements: array of Double;
begin
// Examples:
// transform="matrix(0.860815 0 -0 1.07602 339.302 489.171)"
// transform="scale(0.24) translate(0, 35)"
// transform="rotate(90)"
if AKey = 'transform' then
begin
lTokenizer := TSVGPathTokenizer.Create;
try
lTokenizer.TokenizeFunctions(AValue);
i := 0;
while i < lTokenizer.Tokens.Count-1 do
begin
lFunctionName := lTokenizer.Tokens.Items[i].StrValue;
lParamStr := lTokenizer.Tokens.Items[i+1].StrValue;
lMatrixElements := ReadSpaceSeparatedFloats(lParamStr);
if lFunctionName = 'matrix' then
begin
ReadSVGTransformationMatrix(lParamStr, MA, MB, MC, MD, ME, MF);
ConvertTransformationMatrixToOperations(MA, MB, MC, MD, ME, MF,
lMTranslateX, lMTranslateY, lMScaleX, lMScaleY, lMSkewX, lMSkewY, lMRotate);
ADestEntity.Move(lMTranslateX, lMTranslateY);
ADestEntity.Scale(lMScaleX, lMScaleY);
end
else if lFunctionName = 'scale' then
begin
;
end
else if lFunctionName = 'translate' then
begin
ADestEntity.Move(lMatrixElements[0], lMatrixElements[1]);
end
else if lFunctionName = 'rotate' then
begin
ADestEntity.Rotate(lMatrixElements[0], Make3DPoint(0, 0, 0));
end;
Inc(i, 2);
end;
finally
lTokenizer.Free;
end;
end;
end;
function TvSVGVectorialReader.IsAttributeFromStyle(AStr: string): Boolean;
begin
Result := (AStr = 'stroke') or (AStr = 'stroke-width') or
Result :=
// pen
(AStr = 'stroke') or (AStr = 'stroke-width') or
(AStr = 'stroke-dasharray') or (AStr = 'stroke-opacity') or
(AStr = 'stroke-linecap') or
// general
(AStr = 'transform') or
// brush
(AStr = 'fill') or (AStr = 'fill-opacity') or
// font
@ -902,35 +1024,46 @@ begin
end;
end;
// transform="matrix(0.860815 0 -0 1.07602 354.095 482.177)"=>matrix(a, b, c, d, e, f)
// See http://apike.ca/prog_svg_transform.html
procedure TvSVGVectorialReader.ReadAndApplySVGTransformationMatrix(
AMatrix: string; ADestEntity: TvEntity);
function TvSVGVectorialReader.ReadSpaceSeparatedFloats(AInput: string
): TDoubleArray;
var
lStrings: TStringList;
lMatrixElements: array[0..5] of Double;
lStr: string;
lMatrixElements: array of Double;
i: Integer;
begin
lStrings := TStringList.Create;
try
lStrings.Delimiter := ' ';
lStr := Copy(AMatrix, 8, Length(AMatrix)-9);
lStrings.DelimitedText := lStr;
for i := 0 to 5 do
lStrings.DelimitedText := AInput;
SetLength(lMatrixElements, lStrings.Count);
for i := 0 to lStrings.Count-1 do
begin
lMatrixElements[i] := 0;
if i<=lStrings.Count-1 then
lMatrixElements[i] := StringWithUnitToFloat(lStrings.Strings[i]);
lMatrixElements[i] := StringWithUnitToFloat(lStrings.Strings[i]);
end;
ADestEntity.X := ADestEntity.X + lMatrixElements[4];
ADestEntity.Y := ADestEntity.Y + lMatrixElements[5];
Result := lMatrixElements;
finally
lStrings.Free;
end;
end;
// transform="matrix(0.860815 0 -0 1.07602 354.095 482.177)"=>matrix(a, b, c, d, e, f)
// See http://apike.ca/prog_svg_transform.html
procedure TvSVGVectorialReader.ReadSVGTransformationMatrix(
AMatrix: string; out AA, AB, AC, AD, AE, AF: Double);
var
lMatrixElements: array of Double;
begin
lMatrixElements := ReadSpaceSeparatedFloats(AMatrix);
AA := lMatrixElements[0];
AB := lMatrixElements[1];
AC := lMatrixElements[2];
AD := lMatrixElements[3];
AE := lMatrixElements[4];
AF := lMatrixElements[5];
end;
procedure TvSVGVectorialReader.ReadDefsFromNode(ANode: TDOMNode;
AData: TvVectorialPage; ADoc: TvVectorialDocument);
var
@ -1091,6 +1224,7 @@ begin
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lCircle);
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lCircle);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lCircle);
end;
end;
@ -1143,6 +1277,7 @@ begin
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lEllipse);
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lEllipse);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lEllipse);
end;
end;
@ -1242,7 +1377,7 @@ var
i: Integer;
lNodeName: DOMString;
lPath: TPath;
lStyleStr, lStrokeStr, lStrokeWidthStr: DOMString;
lStyleStr: DOMString;
begin
x1 := 0.0;
y1 := 0.0;
@ -1260,13 +1395,7 @@ begin
else if lNodeName = 'x2' then
x2 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
else if lNodeName = 'y2' then
y2 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
else if lNodeName = 'style' then
lStyleStr := ANode.Attributes.Item[i].NodeValue
else if lNodeName = 'stroke' then
lStrokeStr := ANode.Attributes.Item[i].NodeValue
else if lNodeName = 'stroke-width' then
lStrokeWidthStr := ANode.Attributes.Item[i].NodeValue;
y2 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue);
end;
ConvertSVGCoordinatesToFPVCoordinates(
@ -1278,12 +1407,22 @@ begin
AData.AddMoveToPath(vx1, vy1);
AData.AddLineToPath(vx2, vy2);
lPath := AData.EndPath(True);
// Apply the layer style
ApplyLayerStyles(lPath);
// Add the pen/brush
ReadSVGStyle(lStyleStr, lPath);
ReadSVGStyle(lStrokeStr, lPath);
ReadSVGStyle(lStrokeWidthStr, lPath);
// Add the entity styles
for i := 0 to ANode.Attributes.Length - 1 do
begin
lNodeName := ANode.Attributes.Item[i].NodeName;
if lNodeName = 'style' then
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lPath)
else if IsAttributeFromStyle(lNodeName) then
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
end;
end;
//
Result := lPath;
end;
@ -1327,6 +1466,7 @@ begin
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
end;
end;
end;
@ -1745,6 +1885,7 @@ begin
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
end;
end;
end;
@ -1797,6 +1938,7 @@ begin
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lRect);
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lRect);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lRect);
end;
end;
@ -1841,16 +1983,13 @@ begin
ly := ly + StringWithUnitToFloat(lNodeValue)
else if lNodeName = 'id' then
lText.Name := lNodeValue
else if lNodeName = 'transform' then
begin
ReadAndApplySVGTransformationMatrix(lNodeValue, lText);
lx := lx + lText.X;
ly := ly + lText.Y;
end
else if lNodeName = 'style' then
ReadSVGStyle(lNodeValue, lText)
else if IsAttributeFromStyle(lNodeName) then
begin
ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
end;
end;
// The text contents are inside as a child text, not as a attribute
@ -1860,6 +1999,10 @@ begin
// Add the first line
lText.Value.Add(lTextStr);
// Recover the position if there was a transformation matrix
lx := lx + lText.X;
ly := lx + lText.Y;
// Set the coordinates
ConvertSVGCoordinatesToFPVCoordinates(
AData, lx, ly, lText.X, lText.Y);
@ -2036,6 +2179,7 @@ var
ANode: TDOMElement;
i: Integer;
lCurEntity: TvEntity;
lViewBox: TDoubleArray;
begin
FPathNumber := 0;
@ -2056,7 +2200,13 @@ begin
for i := 0 to ANode.Attributes.Length - 1 do
begin
lNodeName := ANode.Attributes.Item[i].NodeName;
if lNodeName = 'style' then
if lNodeName = 'viewBox' then
begin
lViewBox := ReadSpaceSeparatedFloats(ANode.Attributes.Item[i].NodeValue);
AData.Width := lViewBox[2] - lViewBox[0];
AData.Height := lViewBox[3] - lViewBox[1];
end
else if lNodeName = 'style' then
begin
{$ifdef SVG_MERGE_LAYER_STYLES}
ReadSVGStyleToStyleLists(ANode.Attributes.Item[i].NodeValue, lLayerStyleKeys, lLayerStyleValues);