diff --git a/components/fpvectorial/fpvutils.pas b/components/fpvectorial/fpvutils.pas index 8df49c0c33..b87f8cbf73 100644 --- a/components/fpvectorial/fpvutils.pas +++ b/components/fpvectorial/fpvutils.pas @@ -26,6 +26,7 @@ uses {$ifdef USE_LCL_CANVAS} Graphics, LCLIntf, LCLType, {$endif} + base64, fpvectorial, fpimage, zstream; type @@ -65,8 +66,11 @@ function SolveNumericallyAngle(ANumericalEquation: TNumericalEquation; // Compression/Decompression procedure DeflateBytes(var ASource, ADest: TFPVUByteArray); procedure DeflateStream(ASource, ADest: TStream); -// ASCII85 +// Binary to Text encodings procedure DecodeASCII85(ASource: string; var ADest: TFPVUByteArray); +procedure DecodeBase64(ASource: string; ADest: TStream); +// Byte array to stream conversion +procedure ByteArrayToStream(ASource: TFPVUByteArray; ADest: TStream); // Debug procedure FPVUDebug(AStr: string); procedure FPVUDebugLn(AStr: string); @@ -577,6 +581,29 @@ begin end; end; +procedure DecodeBase64(ASource: string; ADest: TStream); +var + lSourceStream: TStringStream; + lDecoder: TBase64DecodingStream; +begin + lSourceStream := TStringStream.Create(ASource); + lDecoder := TBase64DecodingStream.Create(lSourceStream); + try + ADest.CopyFrom(lDecoder, lDecoder.Size); + finally + lDecoder.Free; + lSourceStream.Free; + end; +end; + +procedure ByteArrayToStream(ASource: TFPVUByteArray; ADest: TStream); +var + i: Integer; +begin + for i := 0 to Length(ASource)-1 do + ADest.WriteByte(ASource[i]); +end; + procedure FPVUDebug(AStr: string); begin FPVDebugBuffer := FPVDebugBuffer + AStr; diff --git a/components/fpvectorial/svgvectorialreader.pas b/components/fpvectorial/svgvectorialreader.pas index 63e347698f..514882fd0f 100644 --- a/components/fpvectorial/svgvectorialreader.pas +++ b/components/fpvectorial/svgvectorialreader.pas @@ -16,6 +16,8 @@ interface uses Classes, SysUtils, math, fpimage, fpcanvas, laz2_xmlread, laz2_dom, fgl, + // image data formats + fpreadpng, fpvectorial, fpvutils, lazutf8, TypInfo; type @@ -78,10 +80,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 ReadSVGGeneralStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntity): TvSetPenBrushAndFontElements; function IsAttributeFromStyle(AStr: string): Boolean; procedure ApplyLayerStyles(ADestEntity: TvEntity); - function ReadSpaceSeparatedFloats(AInput: string): TDoubleArray; + function ReadSpaceSeparatedFloats(AInput: string; AOtherSeparators: string): TDoubleArray; + function ReadSpaceSeparatedStrings(AInput: string; AOtherSeparators: string): TStringList; procedure ReadSVGTransformationMatrix(AMatrix: string; out AA, AB, AC, AD, AE, AF: Double); // procedure ReadDefsFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument); @@ -89,6 +92,7 @@ type function ReadEntityFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; function ReadCircleFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; function ReadEllipseFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; + function ReadImageFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; procedure ReadLayerFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument); function ReadLineFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; function ReadPathFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; @@ -923,7 +927,7 @@ begin end; function TvSVGVectorialReader.ReadSVGGeneralStyleWithKeyAndValue(AKey, - AValue: string; ADestEntity: TvEntityWithPen): TvSetPenBrushAndFontElements; + AValue: string; ADestEntity: TvEntity): TvSetPenBrushAndFontElements; var // transform MA, MB, MC, MD, ME, MF: Double; @@ -949,7 +953,7 @@ begin begin lFunctionName := lTokenizer.Tokens.Items[i].StrValue; lParamStr := lTokenizer.Tokens.Items[i+1].StrValue; - lMatrixElements := ReadSpaceSeparatedFloats(lParamStr); + lMatrixElements := ReadSpaceSeparatedFloats(lParamStr, ','); if lFunctionName = 'matrix' then begin @@ -1024,17 +1028,25 @@ begin end; end; -function TvSVGVectorialReader.ReadSpaceSeparatedFloats(AInput: string - ): TDoubleArray; +function TvSVGVectorialReader.ReadSpaceSeparatedFloats(AInput: string; + AOtherSeparators: string): TDoubleArray; var lStrings: TStringList; + lInputStr: string; lMatrixElements: array of Double; i: Integer; begin lStrings := TStringList.Create; try lStrings.Delimiter := ' '; - lStrings.DelimitedText := AInput; + // now other separator too + lInputStr := AInput; + for i := 1 to Length(AOtherSeparators) do + begin + lInputStr := StringReplace(lInputStr, AOtherSeparators[i], ' ', [rfReplaceAll]); + end; + // + lStrings.DelimitedText := lInputStr; SetLength(lMatrixElements, lStrings.Count); for i := 0 to lStrings.Count-1 do begin @@ -1047,6 +1059,24 @@ begin end; end; +function TvSVGVectorialReader.ReadSpaceSeparatedStrings(AInput: string; + AOtherSeparators: string): TStringList; +var + i: Integer; + lInputStr: String; +begin + Result := TStringList.Create; + Result.Delimiter := ' '; + // now other separator too + lInputStr := AInput; + for i := 1 to Length(AOtherSeparators) do + begin + lInputStr := StringReplace(lInputStr, AOtherSeparators[i], ' ', [rfReplaceAll]); + end; + // + Result.DelimitedText := lInputStr; +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( @@ -1054,7 +1084,7 @@ procedure TvSVGVectorialReader.ReadSVGTransformationMatrix( var lMatrixElements: array of Double; begin - lMatrixElements := ReadSpaceSeparatedFloats(AMatrix); + lMatrixElements := ReadSpaceSeparatedFloats(AMatrix, ''); AA := lMatrixElements[0]; AB := lMatrixElements[1]; @@ -1177,6 +1207,7 @@ begin 'circle': Result := ReadCircleFromNode(ANode, AData, ADoc); 'ellipse': Result := ReadEllipseFromNode(ANode, AData, ADoc); 'g': ReadLayerFromNode(ANode, AData, ADoc); + 'image': Result := ReadImageFromNode(ANode, AData, ADoc); 'line': Result := ReadLineFromNode(ANode, AData, ADoc); 'path': Result := ReadPathFromNode(ANode, AData, ADoc); 'polygon', 'polyline': Result := ReadPolyFromNode(ANode, AData, ADoc); @@ -1192,7 +1223,7 @@ var cx, cy, cr, dtmp: double; lCircle: TvCircle; i: Integer; - lNodeName: DOMString; + lNodeName, lNodeValue: DOMString; begin cx := 0.0; cy := 0.0; @@ -1210,21 +1241,22 @@ begin for i := 0 to ANode.Attributes.Length - 1 do begin lNodeName := ANode.Attributes.Item[i].NodeName; + lNodeValue := ANode.Attributes.Item[i].NodeName; if lNodeName = 'cx' then - cx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + cx := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'cy' then - cy := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + cy := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'r' then - cr := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + cr := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'id' then - lCircle.Name := ANode.Attributes.Item[i].NodeValue + lCircle.Name := lNodeValue else if lNodeName = 'style' then - ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lCircle) + ReadSVGStyle(lNodeValue, lCircle) else if IsAttributeFromStyle(lNodeName) then 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); + ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lCircle); + ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lCircle); + ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lCircle); end; end; @@ -1242,7 +1274,7 @@ var cx, cy, crx, cry: double; lEllipse: TvEllipse; i: Integer; - lNodeName: DOMString; + lNodeName, lNodeValue: DOMString; begin cx := 0.0; cy := 0.0; @@ -1261,23 +1293,24 @@ begin for i := 0 to ANode.Attributes.Length - 1 do begin lNodeName := ANode.Attributes.Item[i].NodeName; + lNodeValue := ANode.Attributes.Item[i].NodeName; if lNodeName = 'cx' then - cx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + cx := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'cy' then - cy := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + cy := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'rx' then - crx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + crx := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'ry' then - cry := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue) + cry := StringWithUnitToFloat(lNodeValue) else if lNodeName = 'id' then lEllipse.Name := ANode.Attributes.Item[i].NodeValue else if lNodeName = 'style' then - ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lEllipse) + ReadSVGStyle(lNodeValue, lEllipse) else if IsAttributeFromStyle(lNodeName) then 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); + ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lEllipse); + ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lEllipse); + ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lEllipse); end; end; @@ -1289,6 +1322,95 @@ begin Result := lEllipse; end; +// +function TvSVGVectorialReader.ReadImageFromNode(ANode: TDOMNode; + AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity; +var + lImage: TvRasterImage; + lx, ly, lw, lh: Double; + i: Integer; + lNodeName, lNodeValue: DOMString; + lImageDataParts: TStringList; + lImageDataBase64: string; + lImageData: array of Byte; + lImageDataStream: TMemoryStream; + lImageReader: TFPCustomImageReader; +begin + lImage := TvRasterImage.Create; + lx := 0; + ly := 0; + lw := 0; + lh := 0; + + // Apply the layer style + //ApplyLayerStyles(lEllipse); + + // read the attributes + for i := 0 to ANode.Attributes.Length - 1 do + begin + lNodeName := ANode.Attributes.Item[i].NodeName; + lNodeValue := ANode.Attributes.Item[i].NodeValue; + if lNodeName = 'x' then + lx := StringWithUnitToFloat(lNodeValue) + else if lNodeName = 'y' then + ly := StringWithUnitToFloat(lNodeValue) + else if lNodeName = 'width' then + lw := StringWithUnitToFloat(lNodeValue) + else if lNodeName = 'height' then + lh := StringWithUnitToFloat(lNodeValue) + else if lNodeName = 'xlink:href' then + begin + lImageDataParts := ReadSpaceSeparatedStrings(lNodeValue, ':;,'); + try + if (lImageDataParts.Strings[0] = 'data') and + (lImageDataParts.Strings[1] = 'image/png') and + (lImageDataParts.Strings[2] = 'base64') then + begin + lImageReader := TFPReaderPNG.Create; + lImageDataStream := TMemoryStream.Create; + try + lImageDataBase64 := lImageDataParts.Strings[3]; + DecodeBase64(lImageDataBase64, lImageDataStream); + lImageDataStream.Position := 0; + lImage.CreateRGB888Image(10, 10); + lImage.RasterImage.LoadFromStream(lImageDataStream, lImageReader); + finally + lImageDataStream.Free; + lImageReader.Free; + end; + end + else + raise Exception.Create('[TvSVGVectorialReader.ReadImageFromNode] Unimplemented image format'); + finally + lImageDataParts.Free; + end; + end + else if lNodeName = 'id' then + lImage.Name := lNodeValue +{ else if lNodeName = 'style' then + ReadSVGStyle(lNodeValue, lImage)} + else if IsAttributeFromStyle(lNodeName) then + begin + //ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lImage); + //ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lImage); + ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lImage); + end; + end; + + // Record translate data + lx := lx + lImage.X; + ly := ly + lImage.Y; + + ConvertSVGCoordinatesToFPVCoordinates( + AData, lx, ly, lImage.X, lImage.Y); + ConvertSVGDeltaToFPVDelta( + AData, lw, lh, lImage.Width, lImage.Height); + + Result := lImage; +end; + procedure TvSVGVectorialReader.ReadLayerFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument); var @@ -2037,7 +2159,7 @@ var lXLink: DOMString = ''; lInsertedEntity: TvEntity; i: Integer; - lNodeName: DOMString; + lNodeName, lNodeValue: DOMString; begin Result := nil; @@ -2061,6 +2183,24 @@ begin lInsert := TvInsert.Create; lInsert.InsertEntity := lInsertedEntity; + + // Apply the styles + // read the attributes + for i := 0 to ANode.Attributes.Length - 1 do + begin + lNodeName := ANode.Attributes.Item[i].NodeName; + lNodeValue := ANode.Attributes.Item[i].NodeValue; + {if lNodeName = 'style' then + ReadSVGStyle(lNodeValue, lText) + else} if IsAttributeFromStyle(lNodeName) then + begin + {ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lText); + ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lText); + ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lText);} + ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lInsert); + end; + end; + Result := lInsert; end; @@ -2175,7 +2315,7 @@ var {$ifdef SVG_MERGE_LAYER_STYLES} lLayerStyleKeys, lLayerStyleValues: TStringList; {$endif} - lNodeName: DOMString; + lNodeName, lNodeValue: DOMString; ANode: TDOMElement; i: Integer; lCurEntity: TvEntity; @@ -2200,23 +2340,24 @@ begin for i := 0 to ANode.Attributes.Length - 1 do begin lNodeName := ANode.Attributes.Item[i].NodeName; + lNodeValue := ANode.Attributes.Item[i].NodeValue; if lNodeName = 'viewBox' then begin - lViewBox := ReadSpaceSeparatedFloats(ANode.Attributes.Item[i].NodeValue); + lViewBox := ReadSpaceSeparatedFloats(lNodeValue, ''); 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); + ReadSVGStyleToStyleLists(lNodeValue, lLayerStyleKeys, lLayerStyleValues); {$endif} end else if IsAttributeFromStyle(lNodeName) then begin {$ifdef SVG_MERGE_LAYER_STYLES} lLayerStyleKeys.Add(lNodeName); - lLayerStyleValues.Add(ANode.Attributes.Item[i].NodeValue); + lLayerStyleValues.Add(lNodeValue); {$endif} end; end;