svg-tree 0.5.1.1 → 0.5.1.2
raw patch · 9 files changed
+3787/−3779 lines, 9 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Graphics.Svg.Types: aspectRatioAlign :: HasPreserveAspectRatio c_azP5 => Lens' c_azP5 Alignment
+ Graphics.Svg.Types: aspectRatioAlign :: HasPreserveAspectRatio c_azPm => Lens' c_azPm Alignment
- Graphics.Svg.Types: aspectRatioDefer :: HasPreserveAspectRatio c_azP5 => Lens' c_azP5 Bool
+ Graphics.Svg.Types: aspectRatioDefer :: HasPreserveAspectRatio c_azPm => Lens' c_azPm Bool
- Graphics.Svg.Types: aspectRatioMeetSlice :: HasPreserveAspectRatio c_azP5 => Lens' c_azP5 (Maybe MeetSlice)
+ Graphics.Svg.Types: aspectRatioMeetSlice :: HasPreserveAspectRatio c_azPm => Lens' c_azPm (Maybe MeetSlice)
- Graphics.Svg.Types: attrClass :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ [Text]
+ Graphics.Svg.Types: attrClass :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 [Text]
- Graphics.Svg.Types: attrId :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Maybe String)
+ Graphics.Svg.Types: attrId :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Maybe String)
- Graphics.Svg.Types: circle :: HasCircle c_aBUZ => Lens' c_aBUZ Circle
+ Graphics.Svg.Types: circle :: HasCircle c_aBVg => Lens' c_aBVg Circle
- Graphics.Svg.Types: circleCenter :: HasCircle c_aBUZ => Lens' c_aBUZ Point
+ Graphics.Svg.Types: circleCenter :: HasCircle c_aBVg => Lens' c_aBVg Point
- Graphics.Svg.Types: circleDrawAttributes :: HasCircle c_aBUZ => Lens' c_aBUZ DrawAttributes
+ Graphics.Svg.Types: circleDrawAttributes :: HasCircle c_aBVg => Lens' c_aBVg DrawAttributes
- Graphics.Svg.Types: circleRadius :: HasCircle c_aBUZ => Lens' c_aBUZ Number
+ Graphics.Svg.Types: circleRadius :: HasCircle c_aBVg => Lens' c_aBVg Number
- Graphics.Svg.Types: class HasCircle c_aBUZ where circleCenter = (.) circle circleCenter circleDrawAttributes = (.) circle circleDrawAttributes circleRadius = (.) circle circleRadius
+ Graphics.Svg.Types: class HasCircle c_aBVg where circleCenter = (.) circle circleCenter circleDrawAttributes = (.) circle circleDrawAttributes circleRadius = (.) circle circleRadius
- Graphics.Svg.Types: class HasClipPath c_aE99 where clipPathContent = (.) clipPath clipPathContent clipPathDrawAttributes = (.) clipPath clipPathDrawAttributes clipPathUnits = (.) clipPath clipPathUnits
+ Graphics.Svg.Types: class HasClipPath c_aE9q where clipPathContent = (.) clipPath clipPathContent clipPathDrawAttributes = (.) clipPath clipPathDrawAttributes clipPathUnits = (.) clipPath clipPathUnits
- Graphics.Svg.Types: class HasDocument c_aEuC where definitions = (.) document definitions description = (.) document description documentLocation = (.) document documentLocation elements = (.) document elements height = (.) document height styleRules = (.) document styleRules viewBox = (.) document viewBox width = (.) document width
+ Graphics.Svg.Types: class HasDocument c_aEuT where definitions = (.) document definitions description = (.) document description documentLocation = (.) document documentLocation elements = (.) document elements height = (.) document height styleRules = (.) document styleRules viewBox = (.) document viewBox width = (.) document width
- Graphics.Svg.Types: class HasDrawAttributes c_aAjQ where attrClass = (.) drawAttributes attrClass attrId = (.) drawAttributes attrId clipPathRef = (.) drawAttributes clipPathRef clipRule = (.) drawAttributes clipRule fillColor = (.) drawAttributes fillColor fillOpacity = (.) drawAttributes fillOpacity fillRule = (.) drawAttributes fillRule fontFamily = (.) drawAttributes fontFamily fontSize = (.) drawAttributes fontSize fontStyle = (.) drawAttributes fontStyle groupOpacity = (.) drawAttributes groupOpacity markerEnd = (.) drawAttributes markerEnd markerMid = (.) drawAttributes markerMid markerStart = (.) drawAttributes markerStart maskRef = (.) drawAttributes maskRef strokeColor = (.) drawAttributes strokeColor strokeDashArray = (.) drawAttributes strokeDashArray strokeLineCap = (.) drawAttributes strokeLineCap strokeLineJoin = (.) drawAttributes strokeLineJoin strokeMiterLimit = (.) drawAttributes strokeMiterLimit strokeOffset = (.) drawAttributes strokeOffset strokeOpacity = (.) drawAttributes strokeOpacity strokeWidth = (.) drawAttributes strokeWidth textAnchor = (.) drawAttributes textAnchor transform = (.) drawAttributes transform
+ Graphics.Svg.Types: class HasDrawAttributes c_aAk7 where attrClass = (.) drawAttributes attrClass attrId = (.) drawAttributes attrId clipPathRef = (.) drawAttributes clipPathRef clipRule = (.) drawAttributes clipRule fillColor = (.) drawAttributes fillColor fillOpacity = (.) drawAttributes fillOpacity fillRule = (.) drawAttributes fillRule fontFamily = (.) drawAttributes fontFamily fontSize = (.) drawAttributes fontSize fontStyle = (.) drawAttributes fontStyle groupOpacity = (.) drawAttributes groupOpacity markerEnd = (.) drawAttributes markerEnd markerMid = (.) drawAttributes markerMid markerStart = (.) drawAttributes markerStart maskRef = (.) drawAttributes maskRef strokeColor = (.) drawAttributes strokeColor strokeDashArray = (.) drawAttributes strokeDashArray strokeLineCap = (.) drawAttributes strokeLineCap strokeLineJoin = (.) drawAttributes strokeLineJoin strokeMiterLimit = (.) drawAttributes strokeMiterLimit strokeOffset = (.) drawAttributes strokeOffset strokeOpacity = (.) drawAttributes strokeOpacity strokeWidth = (.) drawAttributes strokeWidth textAnchor = (.) drawAttributes textAnchor transform = (.) drawAttributes transform
- Graphics.Svg.Types: class HasEllipse c_aC0H where ellipseCenter = (.) ellipse ellipseCenter ellipseDrawAttributes = (.) ellipse ellipseDrawAttributes ellipseXRadius = (.) ellipse ellipseXRadius ellipseYRadius = (.) ellipse ellipseYRadius
+ Graphics.Svg.Types: class HasEllipse c_aC0Y where ellipseCenter = (.) ellipse ellipseCenter ellipseDrawAttributes = (.) ellipse ellipseDrawAttributes ellipseXRadius = (.) ellipse ellipseXRadius ellipseYRadius = (.) ellipse ellipseYRadius
- Graphics.Svg.Types: class HasGradientStop c_aDAd where gradientColor = (.) gradientStop gradientColor gradientOffset = (.) gradientStop gradientOffset
+ Graphics.Svg.Types: class HasGradientStop c_aDAu where gradientColor = (.) gradientStop gradientColor gradientOffset = (.) gradientStop gradientOffset
- Graphics.Svg.Types: class HasGroup c_aBHD a_aBaf | c_aBHD -> a_aBaf where groupAspectRatio = (.) group groupAspectRatio groupChildren = (.) group groupChildren groupDrawAttributes = (.) group groupDrawAttributes groupViewBox = (.) group groupViewBox
+ Graphics.Svg.Types: class HasGroup c_aBHU a_aBaw | c_aBHU -> a_aBaw where groupAspectRatio = (.) group groupAspectRatio groupChildren = (.) group groupChildren groupDrawAttributes = (.) group groupDrawAttributes groupViewBox = (.) group groupViewBox
- Graphics.Svg.Types: class HasImage c_aC81 where imageAspectRatio = (.) image imageAspectRatio imageCornerUpperLeft = (.) image imageCornerUpperLeft imageDrawAttributes = (.) image imageDrawAttributes imageHeight = (.) image imageHeight imageHref = (.) image imageHref imageWidth = (.) image imageWidth
+ Graphics.Svg.Types: class HasImage c_aC8i where imageAspectRatio = (.) image imageAspectRatio imageCornerUpperLeft = (.) image imageCornerUpperLeft imageDrawAttributes = (.) image imageDrawAttributes imageHeight = (.) image imageHeight imageHref = (.) image imageHref imageWidth = (.) image imageWidth
- Graphics.Svg.Types: class HasLine c_aAX2 where lineDrawAttributes = (.) line lineDrawAttributes linePoint1 = (.) line linePoint1 linePoint2 = (.) line linePoint2
+ Graphics.Svg.Types: class HasLine c_aAXj where lineDrawAttributes = (.) line lineDrawAttributes linePoint1 = (.) line linePoint1 linePoint2 = (.) line linePoint2
- Graphics.Svg.Types: class HasLinearGradient c_aDFH where linearGradientSpread = (.) linearGradient linearGradientSpread linearGradientStart = (.) linearGradient linearGradientStart linearGradientStop = (.) linearGradient linearGradientStop linearGradientStops = (.) linearGradient linearGradientStops linearGradientTransform = (.) linearGradient linearGradientTransform linearGradientUnits = (.) linearGradient linearGradientUnits
+ Graphics.Svg.Types: class HasLinearGradient c_aDFY where linearGradientSpread = (.) linearGradient linearGradientSpread linearGradientStart = (.) linearGradient linearGradientStart linearGradientStop = (.) linearGradient linearGradientStop linearGradientStops = (.) linearGradient linearGradientStops linearGradientTransform = (.) linearGradient linearGradientTransform linearGradientUnits = (.) linearGradient linearGradientUnits
- Graphics.Svg.Types: class HasMarker c_aD5K where markerAspectRatio = (.) marker markerAspectRatio markerDrawAttributes = (.) marker markerDrawAttributes markerElements = (.) marker markerElements markerHeight = (.) marker markerHeight markerOrient = (.) marker markerOrient markerOverflow = (.) marker markerOverflow markerRefPoint = (.) marker markerRefPoint markerUnits = (.) marker markerUnits markerViewBox = (.) marker markerViewBox markerWidth = (.) marker markerWidth
+ Graphics.Svg.Types: class HasMarker c_aD61 where markerAspectRatio = (.) marker markerAspectRatio markerDrawAttributes = (.) marker markerDrawAttributes markerElements = (.) marker markerElements markerHeight = (.) marker markerHeight markerOrient = (.) marker markerOrient markerOverflow = (.) marker markerOverflow markerRefPoint = (.) marker markerRefPoint markerUnits = (.) marker markerUnits markerViewBox = (.) marker markerViewBox markerWidth = (.) marker markerWidth
- Graphics.Svg.Types: class HasMask c_aE0d where maskContent = (.) mask maskContent maskContentUnits = (.) mask maskContentUnits maskDrawAttributes = (.) mask maskDrawAttributes maskHeight = (.) mask maskHeight maskPosition = (.) mask maskPosition maskUnits = (.) mask maskUnits maskWidth = (.) mask maskWidth
+ Graphics.Svg.Types: class HasMask c_aE0u where maskContent = (.) mask maskContent maskContentUnits = (.) mask maskContentUnits maskDrawAttributes = (.) mask maskDrawAttributes maskHeight = (.) mask maskHeight maskPosition = (.) mask maskPosition maskUnits = (.) mask maskUnits maskWidth = (.) mask maskWidth
- Graphics.Svg.Types: class HasPath c_aB9W where pathDefinition = (.) path pathDefinition pathDrawAttributes = (.) path pathDrawAttributes
+ Graphics.Svg.Types: class HasPath c_aBad where pathDefinition = (.) path pathDefinition pathDrawAttributes = (.) path pathDrawAttributes
- Graphics.Svg.Types: class HasPattern c_aEfu where patternAspectRatio = (.) pattern patternAspectRatio patternDrawAttributes = (.) pattern patternDrawAttributes patternElements = (.) pattern patternElements patternHeight = (.) pattern patternHeight patternPos = (.) pattern patternPos patternUnit = (.) pattern patternUnit patternViewBox = (.) pattern patternViewBox patternWidth = (.) pattern patternWidth
+ Graphics.Svg.Types: class HasPattern c_aEfL where patternAspectRatio = (.) pattern patternAspectRatio patternDrawAttributes = (.) pattern patternDrawAttributes patternElements = (.) pattern patternElements patternHeight = (.) pattern patternHeight patternPos = (.) pattern patternPos patternUnit = (.) pattern patternUnit patternViewBox = (.) pattern patternViewBox patternWidth = (.) pattern patternWidth
- Graphics.Svg.Types: class HasPolyLine c_aAOl where polyLineDrawAttributes = (.) polyLine polyLineDrawAttributes polyLinePoints = (.) polyLine polyLinePoints
+ Graphics.Svg.Types: class HasPolyLine c_aAOC where polyLineDrawAttributes = (.) polyLine polyLineDrawAttributes polyLinePoints = (.) polyLine polyLinePoints
- Graphics.Svg.Types: class HasPolygon c_aASv where polygonDrawAttributes = (.) polygon polygonDrawAttributes polygonPoints = (.) polygon polygonPoints
+ Graphics.Svg.Types: class HasPolygon c_aASM where polygonDrawAttributes = (.) polygon polygonDrawAttributes polygonPoints = (.) polygon polygonPoints
- Graphics.Svg.Types: class HasPreserveAspectRatio c_azP5 where aspectRatioAlign = (.) preserveAspectRatio aspectRatioAlign aspectRatioDefer = (.) preserveAspectRatio aspectRatioDefer aspectRatioMeetSlice = (.) preserveAspectRatio aspectRatioMeetSlice
+ Graphics.Svg.Types: class HasPreserveAspectRatio c_azPm where aspectRatioAlign = (.) preserveAspectRatio aspectRatioAlign aspectRatioDefer = (.) preserveAspectRatio aspectRatioDefer aspectRatioMeetSlice = (.) preserveAspectRatio aspectRatioMeetSlice
- Graphics.Svg.Types: class HasRadialGradient c_aDPb where radialGradientCenter = (.) radialGradient radialGradientCenter radialGradientFocusX = (.) radialGradient radialGradientFocusX radialGradientFocusY = (.) radialGradient radialGradientFocusY radialGradientRadius = (.) radialGradient radialGradientRadius radialGradientSpread = (.) radialGradient radialGradientSpread radialGradientStops = (.) radialGradient radialGradientStops radialGradientTransform = (.) radialGradient radialGradientTransform radialGradientUnits = (.) radialGradient radialGradientUnits
+ Graphics.Svg.Types: class HasRadialGradient c_aDPs where radialGradientCenter = (.) radialGradient radialGradientCenter radialGradientFocusX = (.) radialGradient radialGradientFocusX radialGradientFocusY = (.) radialGradient radialGradientFocusY radialGradientRadius = (.) radialGradient radialGradientRadius radialGradientSpread = (.) radialGradient radialGradientSpread radialGradientStops = (.) radialGradient radialGradientStops radialGradientTransform = (.) radialGradient radialGradientTransform radialGradientUnits = (.) radialGradient radialGradientUnits
- Graphics.Svg.Types: class HasRectangle c_aB3i where rectCornerRadius = (.) rectangle rectCornerRadius rectDrawAttributes = (.) rectangle rectDrawAttributes rectHeight = (.) rectangle rectHeight rectUpperLeftCorner = (.) rectangle rectUpperLeftCorner rectWidth = (.) rectangle rectWidth
+ Graphics.Svg.Types: class HasRectangle c_aB3z where rectCornerRadius = (.) rectangle rectCornerRadius rectDrawAttributes = (.) rectangle rectDrawAttributes rectHeight = (.) rectangle rectHeight rectUpperLeftCorner = (.) rectangle rectUpperLeftCorner rectWidth = (.) rectangle rectWidth
- Graphics.Svg.Types: class HasText c_aCOo where textAdjust = (.) text textAdjust textRoot = (.) text textRoot
+ Graphics.Svg.Types: class HasText c_aCOF where textAdjust = (.) text textAdjust textRoot = (.) text textRoot
- Graphics.Svg.Types: class HasTextInfo c_aCpX where textInfoDX = (.) textInfo textInfoDX textInfoDY = (.) textInfo textInfoDY textInfoLength = (.) textInfo textInfoLength textInfoRotate = (.) textInfo textInfoRotate textInfoX = (.) textInfo textInfoX textInfoY = (.) textInfo textInfoY
+ Graphics.Svg.Types: class HasTextInfo c_aCqe where textInfoDX = (.) textInfo textInfoDX textInfoDY = (.) textInfo textInfoDY textInfoLength = (.) textInfo textInfoLength textInfoRotate = (.) textInfo textInfoRotate textInfoX = (.) textInfo textInfoX textInfoY = (.) textInfo textInfoY
- Graphics.Svg.Types: class HasTextPath c_aCH9 where textPathData = (.) textPath textPathData textPathMethod = (.) textPath textPathMethod textPathName = (.) textPath textPathName textPathSpacing = (.) textPath textPathSpacing textPathStartOffset = (.) textPath textPathStartOffset
+ Graphics.Svg.Types: class HasTextPath c_aCHq where textPathData = (.) textPath textPathData textPathMethod = (.) textPath textPathMethod textPathName = (.) textPath textPathName textPathSpacing = (.) textPath textPathSpacing textPathStartOffset = (.) textPath textPathStartOffset
- Graphics.Svg.Types: class HasTextSpan c_aCzw where spanContent = (.) textSpan spanContent spanDrawAttributes = (.) textSpan spanDrawAttributes spanInfo = (.) textSpan spanInfo
+ Graphics.Svg.Types: class HasTextSpan c_aCzN where spanContent = (.) textSpan spanContent spanDrawAttributes = (.) textSpan spanDrawAttributes spanInfo = (.) textSpan spanInfo
- Graphics.Svg.Types: class HasUse c_aCgH where useBase = (.) use useBase useDrawAttributes = (.) use useDrawAttributes useHeight = (.) use useHeight useName = (.) use useName useWidth = (.) use useWidth
+ Graphics.Svg.Types: class HasUse c_aCgY where useBase = (.) use useBase useDrawAttributes = (.) use useDrawAttributes useHeight = (.) use useHeight useName = (.) use useName useWidth = (.) use useWidth
- Graphics.Svg.Types: clipPath :: HasClipPath c_aE99 => Lens' c_aE99 ClipPath
+ Graphics.Svg.Types: clipPath :: HasClipPath c_aE9q => Lens' c_aE9q ClipPath
- Graphics.Svg.Types: clipPathContent :: HasClipPath c_aE99 => Lens' c_aE99 [Tree]
+ Graphics.Svg.Types: clipPathContent :: HasClipPath c_aE9q => Lens' c_aE9q [Tree]
- Graphics.Svg.Types: clipPathDrawAttributes :: HasClipPath c_aE99 => Lens' c_aE99 DrawAttributes
+ Graphics.Svg.Types: clipPathDrawAttributes :: HasClipPath c_aE9q => Lens' c_aE9q DrawAttributes
- Graphics.Svg.Types: clipPathRef :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last ElementRef)
+ Graphics.Svg.Types: clipPathRef :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last ElementRef)
- Graphics.Svg.Types: clipPathUnits :: HasClipPath c_aE99 => Lens' c_aE99 CoordinateUnits
+ Graphics.Svg.Types: clipPathUnits :: HasClipPath c_aE9q => Lens' c_aE9q CoordinateUnits
- Graphics.Svg.Types: clipRule :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last FillRule)
+ Graphics.Svg.Types: clipRule :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last FillRule)
- Graphics.Svg.Types: definitions :: HasDocument c_aEuC => Lens' c_aEuC (Map String Element)
+ Graphics.Svg.Types: definitions :: HasDocument c_aEuT => Lens' c_aEuT (Map String Element)
- Graphics.Svg.Types: description :: HasDocument c_aEuC => Lens' c_aEuC String
+ Graphics.Svg.Types: description :: HasDocument c_aEuT => Lens' c_aEuT String
- Graphics.Svg.Types: document :: HasDocument c_aEuC => Lens' c_aEuC Document
+ Graphics.Svg.Types: document :: HasDocument c_aEuT => Lens' c_aEuT Document
- Graphics.Svg.Types: documentLocation :: HasDocument c_aEuC => Lens' c_aEuC FilePath
+ Graphics.Svg.Types: documentLocation :: HasDocument c_aEuT => Lens' c_aEuT FilePath
- Graphics.Svg.Types: drawAttributes :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ DrawAttributes
+ Graphics.Svg.Types: drawAttributes :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 DrawAttributes
- Graphics.Svg.Types: elements :: HasDocument c_aEuC => Lens' c_aEuC [Tree]
+ Graphics.Svg.Types: elements :: HasDocument c_aEuT => Lens' c_aEuT [Tree]
- Graphics.Svg.Types: ellipse :: HasEllipse c_aC0H => Lens' c_aC0H Ellipse
+ Graphics.Svg.Types: ellipse :: HasEllipse c_aC0Y => Lens' c_aC0Y Ellipse
- Graphics.Svg.Types: ellipseCenter :: HasEllipse c_aC0H => Lens' c_aC0H Point
+ Graphics.Svg.Types: ellipseCenter :: HasEllipse c_aC0Y => Lens' c_aC0Y Point
- Graphics.Svg.Types: ellipseDrawAttributes :: HasEllipse c_aC0H => Lens' c_aC0H DrawAttributes
+ Graphics.Svg.Types: ellipseDrawAttributes :: HasEllipse c_aC0Y => Lens' c_aC0Y DrawAttributes
- Graphics.Svg.Types: ellipseXRadius :: HasEllipse c_aC0H => Lens' c_aC0H Number
+ Graphics.Svg.Types: ellipseXRadius :: HasEllipse c_aC0Y => Lens' c_aC0Y Number
- Graphics.Svg.Types: ellipseYRadius :: HasEllipse c_aC0H => Lens' c_aC0H Number
+ Graphics.Svg.Types: ellipseYRadius :: HasEllipse c_aC0Y => Lens' c_aC0Y Number
- Graphics.Svg.Types: fillColor :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Texture)
+ Graphics.Svg.Types: fillColor :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Texture)
- Graphics.Svg.Types: fillOpacity :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Maybe Float)
+ Graphics.Svg.Types: fillOpacity :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Maybe Float)
- Graphics.Svg.Types: fillRule :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last FillRule)
+ Graphics.Svg.Types: fillRule :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last FillRule)
- Graphics.Svg.Types: fontFamily :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last [String])
+ Graphics.Svg.Types: fontFamily :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last [String])
- Graphics.Svg.Types: fontSize :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Number)
+ Graphics.Svg.Types: fontSize :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Number)
- Graphics.Svg.Types: fontStyle :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last FontStyle)
+ Graphics.Svg.Types: fontStyle :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last FontStyle)
- Graphics.Svg.Types: gradientColor :: HasGradientStop c_aDAd => Lens' c_aDAd PixelRGBA8
+ Graphics.Svg.Types: gradientColor :: HasGradientStop c_aDAu => Lens' c_aDAu PixelRGBA8
- Graphics.Svg.Types: gradientOffset :: HasGradientStop c_aDAd => Lens' c_aDAd Float
+ Graphics.Svg.Types: gradientOffset :: HasGradientStop c_aDAu => Lens' c_aDAu Float
- Graphics.Svg.Types: gradientStop :: HasGradientStop c_aDAd => Lens' c_aDAd GradientStop
+ Graphics.Svg.Types: gradientStop :: HasGradientStop c_aDAu => Lens' c_aDAu GradientStop
- Graphics.Svg.Types: group :: HasGroup c_aBHD a_aBaf => Lens' c_aBHD (Group a_aBaf)
+ Graphics.Svg.Types: group :: HasGroup c_aBHU a_aBaw => Lens' c_aBHU (Group a_aBaw)
- Graphics.Svg.Types: groupAspectRatio :: HasGroup c_aBHD a_aBaf => Lens' c_aBHD PreserveAspectRatio
+ Graphics.Svg.Types: groupAspectRatio :: HasGroup c_aBHU a_aBaw => Lens' c_aBHU PreserveAspectRatio
- Graphics.Svg.Types: groupChildren :: HasGroup c_aBHD a_aBaf => Lens' c_aBHD [a_aBaf]
+ Graphics.Svg.Types: groupChildren :: HasGroup c_aBHU a_aBaw => Lens' c_aBHU [a_aBaw]
- Graphics.Svg.Types: groupDrawAttributes :: HasGroup c_aBHD a_aBaf => Lens' c_aBHD DrawAttributes
+ Graphics.Svg.Types: groupDrawAttributes :: HasGroup c_aBHU a_aBaw => Lens' c_aBHU DrawAttributes
- Graphics.Svg.Types: groupOfSymbol :: forall a_aBIb a_aBP0. Iso (Symbol a_aBIb) (Symbol a_aBP0) (Group a_aBIb) (Group a_aBP0)
+ Graphics.Svg.Types: groupOfSymbol :: forall a_aBIs a_aBPh. Iso (Symbol a_aBIs) (Symbol a_aBPh) (Group a_aBIs) (Group a_aBPh)
- Graphics.Svg.Types: groupOpacity :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Maybe Float)
+ Graphics.Svg.Types: groupOpacity :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Maybe Float)
- Graphics.Svg.Types: groupViewBox :: HasGroup c_aBHD a_aBaf => Lens' c_aBHD (Maybe (Double, Double, Double, Double))
+ Graphics.Svg.Types: groupViewBox :: HasGroup c_aBHU a_aBaw => Lens' c_aBHU (Maybe (Double, Double, Double, Double))
- Graphics.Svg.Types: height :: HasDocument c_aEuC => Lens' c_aEuC (Maybe Number)
+ Graphics.Svg.Types: height :: HasDocument c_aEuT => Lens' c_aEuT (Maybe Number)
- Graphics.Svg.Types: image :: HasImage c_aC81 => Lens' c_aC81 Image
+ Graphics.Svg.Types: image :: HasImage c_aC8i => Lens' c_aC8i Image
- Graphics.Svg.Types: imageAspectRatio :: HasImage c_aC81 => Lens' c_aC81 PreserveAspectRatio
+ Graphics.Svg.Types: imageAspectRatio :: HasImage c_aC8i => Lens' c_aC8i PreserveAspectRatio
- Graphics.Svg.Types: imageCornerUpperLeft :: HasImage c_aC81 => Lens' c_aC81 Point
+ Graphics.Svg.Types: imageCornerUpperLeft :: HasImage c_aC8i => Lens' c_aC8i Point
- Graphics.Svg.Types: imageDrawAttributes :: HasImage c_aC81 => Lens' c_aC81 DrawAttributes
+ Graphics.Svg.Types: imageDrawAttributes :: HasImage c_aC8i => Lens' c_aC8i DrawAttributes
- Graphics.Svg.Types: imageHeight :: HasImage c_aC81 => Lens' c_aC81 Number
+ Graphics.Svg.Types: imageHeight :: HasImage c_aC8i => Lens' c_aC8i Number
- Graphics.Svg.Types: imageHref :: HasImage c_aC81 => Lens' c_aC81 String
+ Graphics.Svg.Types: imageHref :: HasImage c_aC8i => Lens' c_aC8i String
- Graphics.Svg.Types: imageWidth :: HasImage c_aC81 => Lens' c_aC81 Number
+ Graphics.Svg.Types: imageWidth :: HasImage c_aC8i => Lens' c_aC8i Number
- Graphics.Svg.Types: line :: HasLine c_aAX2 => Lens' c_aAX2 Line
+ Graphics.Svg.Types: line :: HasLine c_aAXj => Lens' c_aAXj Line
- Graphics.Svg.Types: lineDrawAttributes :: HasLine c_aAX2 => Lens' c_aAX2 DrawAttributes
+ Graphics.Svg.Types: lineDrawAttributes :: HasLine c_aAXj => Lens' c_aAXj DrawAttributes
- Graphics.Svg.Types: linePoint1 :: HasLine c_aAX2 => Lens' c_aAX2 Point
+ Graphics.Svg.Types: linePoint1 :: HasLine c_aAXj => Lens' c_aAXj Point
- Graphics.Svg.Types: linePoint2 :: HasLine c_aAX2 => Lens' c_aAX2 Point
+ Graphics.Svg.Types: linePoint2 :: HasLine c_aAXj => Lens' c_aAXj Point
- Graphics.Svg.Types: linearGradient :: HasLinearGradient c_aDFH => Lens' c_aDFH LinearGradient
+ Graphics.Svg.Types: linearGradient :: HasLinearGradient c_aDFY => Lens' c_aDFY LinearGradient
- Graphics.Svg.Types: linearGradientSpread :: HasLinearGradient c_aDFH => Lens' c_aDFH Spread
+ Graphics.Svg.Types: linearGradientSpread :: HasLinearGradient c_aDFY => Lens' c_aDFY Spread
- Graphics.Svg.Types: linearGradientStart :: HasLinearGradient c_aDFH => Lens' c_aDFH Point
+ Graphics.Svg.Types: linearGradientStart :: HasLinearGradient c_aDFY => Lens' c_aDFY Point
- Graphics.Svg.Types: linearGradientStop :: HasLinearGradient c_aDFH => Lens' c_aDFH Point
+ Graphics.Svg.Types: linearGradientStop :: HasLinearGradient c_aDFY => Lens' c_aDFY Point
- Graphics.Svg.Types: linearGradientStops :: HasLinearGradient c_aDFH => Lens' c_aDFH [GradientStop]
+ Graphics.Svg.Types: linearGradientStops :: HasLinearGradient c_aDFY => Lens' c_aDFY [GradientStop]
- Graphics.Svg.Types: linearGradientTransform :: HasLinearGradient c_aDFH => Lens' c_aDFH [Transformation]
+ Graphics.Svg.Types: linearGradientTransform :: HasLinearGradient c_aDFY => Lens' c_aDFY [Transformation]
- Graphics.Svg.Types: linearGradientUnits :: HasLinearGradient c_aDFH => Lens' c_aDFH CoordinateUnits
+ Graphics.Svg.Types: linearGradientUnits :: HasLinearGradient c_aDFY => Lens' c_aDFY CoordinateUnits
- Graphics.Svg.Types: marker :: HasMarker c_aD5K => Lens' c_aD5K Marker
+ Graphics.Svg.Types: marker :: HasMarker c_aD61 => Lens' c_aD61 Marker
- Graphics.Svg.Types: markerAspectRatio :: HasMarker c_aD5K => Lens' c_aD5K PreserveAspectRatio
+ Graphics.Svg.Types: markerAspectRatio :: HasMarker c_aD61 => Lens' c_aD61 PreserveAspectRatio
- Graphics.Svg.Types: markerDrawAttributes :: HasMarker c_aD5K => Lens' c_aD5K DrawAttributes
+ Graphics.Svg.Types: markerDrawAttributes :: HasMarker c_aD61 => Lens' c_aD61 DrawAttributes
- Graphics.Svg.Types: markerElements :: HasMarker c_aD5K => Lens' c_aD5K [Tree]
+ Graphics.Svg.Types: markerElements :: HasMarker c_aD61 => Lens' c_aD61 [Tree]
- Graphics.Svg.Types: markerEnd :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last ElementRef)
+ Graphics.Svg.Types: markerEnd :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last ElementRef)
- Graphics.Svg.Types: markerHeight :: HasMarker c_aD5K => Lens' c_aD5K (Maybe Number)
+ Graphics.Svg.Types: markerHeight :: HasMarker c_aD61 => Lens' c_aD61 (Maybe Number)
- Graphics.Svg.Types: markerMid :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last ElementRef)
+ Graphics.Svg.Types: markerMid :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last ElementRef)
- Graphics.Svg.Types: markerOrient :: HasMarker c_aD5K => Lens' c_aD5K (Maybe MarkerOrientation)
+ Graphics.Svg.Types: markerOrient :: HasMarker c_aD61 => Lens' c_aD61 (Maybe MarkerOrientation)
- Graphics.Svg.Types: markerOverflow :: HasMarker c_aD5K => Lens' c_aD5K (Maybe Overflow)
+ Graphics.Svg.Types: markerOverflow :: HasMarker c_aD61 => Lens' c_aD61 (Maybe Overflow)
- Graphics.Svg.Types: markerRefPoint :: HasMarker c_aD5K => Lens' c_aD5K (Number, Number)
+ Graphics.Svg.Types: markerRefPoint :: HasMarker c_aD61 => Lens' c_aD61 (Number, Number)
- Graphics.Svg.Types: markerStart :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last ElementRef)
+ Graphics.Svg.Types: markerStart :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last ElementRef)
- Graphics.Svg.Types: markerUnits :: HasMarker c_aD5K => Lens' c_aD5K (Maybe MarkerUnit)
+ Graphics.Svg.Types: markerUnits :: HasMarker c_aD61 => Lens' c_aD61 (Maybe MarkerUnit)
- Graphics.Svg.Types: markerViewBox :: HasMarker c_aD5K => Lens' c_aD5K (Maybe (Double, Double, Double, Double))
+ Graphics.Svg.Types: markerViewBox :: HasMarker c_aD61 => Lens' c_aD61 (Maybe (Double, Double, Double, Double))
- Graphics.Svg.Types: markerWidth :: HasMarker c_aD5K => Lens' c_aD5K (Maybe Number)
+ Graphics.Svg.Types: markerWidth :: HasMarker c_aD61 => Lens' c_aD61 (Maybe Number)
- Graphics.Svg.Types: mask :: HasMask c_aE0d => Lens' c_aE0d Mask
+ Graphics.Svg.Types: mask :: HasMask c_aE0u => Lens' c_aE0u Mask
- Graphics.Svg.Types: maskContent :: HasMask c_aE0d => Lens' c_aE0d [Tree]
+ Graphics.Svg.Types: maskContent :: HasMask c_aE0u => Lens' c_aE0u [Tree]
- Graphics.Svg.Types: maskContentUnits :: HasMask c_aE0d => Lens' c_aE0d CoordinateUnits
+ Graphics.Svg.Types: maskContentUnits :: HasMask c_aE0u => Lens' c_aE0u CoordinateUnits
- Graphics.Svg.Types: maskDrawAttributes :: HasMask c_aE0d => Lens' c_aE0d DrawAttributes
+ Graphics.Svg.Types: maskDrawAttributes :: HasMask c_aE0u => Lens' c_aE0u DrawAttributes
- Graphics.Svg.Types: maskHeight :: HasMask c_aE0d => Lens' c_aE0d Number
+ Graphics.Svg.Types: maskHeight :: HasMask c_aE0u => Lens' c_aE0u Number
- Graphics.Svg.Types: maskPosition :: HasMask c_aE0d => Lens' c_aE0d Point
+ Graphics.Svg.Types: maskPosition :: HasMask c_aE0u => Lens' c_aE0u Point
- Graphics.Svg.Types: maskRef :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last ElementRef)
+ Graphics.Svg.Types: maskRef :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last ElementRef)
- Graphics.Svg.Types: maskUnits :: HasMask c_aE0d => Lens' c_aE0d CoordinateUnits
+ Graphics.Svg.Types: maskUnits :: HasMask c_aE0u => Lens' c_aE0u CoordinateUnits
- Graphics.Svg.Types: maskWidth :: HasMask c_aE0d => Lens' c_aE0d Number
+ Graphics.Svg.Types: maskWidth :: HasMask c_aE0u => Lens' c_aE0u Number
- Graphics.Svg.Types: path :: HasPath c_aB9W => Lens' c_aB9W Path
+ Graphics.Svg.Types: path :: HasPath c_aBad => Lens' c_aBad Path
- Graphics.Svg.Types: pathDefinition :: HasPath c_aB9W => Lens' c_aB9W [PathCommand]
+ Graphics.Svg.Types: pathDefinition :: HasPath c_aBad => Lens' c_aBad [PathCommand]
- Graphics.Svg.Types: pathDrawAttributes :: HasPath c_aB9W => Lens' c_aB9W DrawAttributes
+ Graphics.Svg.Types: pathDrawAttributes :: HasPath c_aBad => Lens' c_aBad DrawAttributes
- Graphics.Svg.Types: pattern :: HasPattern c_aEfu => Lens' c_aEfu Pattern
+ Graphics.Svg.Types: pattern :: HasPattern c_aEfL => Lens' c_aEfL Pattern
- Graphics.Svg.Types: patternAspectRatio :: HasPattern c_aEfu => Lens' c_aEfu PreserveAspectRatio
+ Graphics.Svg.Types: patternAspectRatio :: HasPattern c_aEfL => Lens' c_aEfL PreserveAspectRatio
- Graphics.Svg.Types: patternDrawAttributes :: HasPattern c_aEfu => Lens' c_aEfu DrawAttributes
+ Graphics.Svg.Types: patternDrawAttributes :: HasPattern c_aEfL => Lens' c_aEfL DrawAttributes
- Graphics.Svg.Types: patternElements :: HasPattern c_aEfu => Lens' c_aEfu [Tree]
+ Graphics.Svg.Types: patternElements :: HasPattern c_aEfL => Lens' c_aEfL [Tree]
- Graphics.Svg.Types: patternHeight :: HasPattern c_aEfu => Lens' c_aEfu Number
+ Graphics.Svg.Types: patternHeight :: HasPattern c_aEfL => Lens' c_aEfL Number
- Graphics.Svg.Types: patternPos :: HasPattern c_aEfu => Lens' c_aEfu Point
+ Graphics.Svg.Types: patternPos :: HasPattern c_aEfL => Lens' c_aEfL Point
- Graphics.Svg.Types: patternUnit :: HasPattern c_aEfu => Lens' c_aEfu CoordinateUnits
+ Graphics.Svg.Types: patternUnit :: HasPattern c_aEfL => Lens' c_aEfL CoordinateUnits
- Graphics.Svg.Types: patternViewBox :: HasPattern c_aEfu => Lens' c_aEfu (Maybe (Double, Double, Double, Double))
+ Graphics.Svg.Types: patternViewBox :: HasPattern c_aEfL => Lens' c_aEfL (Maybe (Double, Double, Double, Double))
- Graphics.Svg.Types: patternWidth :: HasPattern c_aEfu => Lens' c_aEfu Number
+ Graphics.Svg.Types: patternWidth :: HasPattern c_aEfL => Lens' c_aEfL Number
- Graphics.Svg.Types: polyLine :: HasPolyLine c_aAOl => Lens' c_aAOl PolyLine
+ Graphics.Svg.Types: polyLine :: HasPolyLine c_aAOC => Lens' c_aAOC PolyLine
- Graphics.Svg.Types: polyLineDrawAttributes :: HasPolyLine c_aAOl => Lens' c_aAOl DrawAttributes
+ Graphics.Svg.Types: polyLineDrawAttributes :: HasPolyLine c_aAOC => Lens' c_aAOC DrawAttributes
- Graphics.Svg.Types: polyLinePoints :: HasPolyLine c_aAOl => Lens' c_aAOl [RPoint]
+ Graphics.Svg.Types: polyLinePoints :: HasPolyLine c_aAOC => Lens' c_aAOC [RPoint]
- Graphics.Svg.Types: polygon :: HasPolygon c_aASv => Lens' c_aASv Polygon
+ Graphics.Svg.Types: polygon :: HasPolygon c_aASM => Lens' c_aASM Polygon
- Graphics.Svg.Types: polygonDrawAttributes :: HasPolygon c_aASv => Lens' c_aASv DrawAttributes
+ Graphics.Svg.Types: polygonDrawAttributes :: HasPolygon c_aASM => Lens' c_aASM DrawAttributes
- Graphics.Svg.Types: polygonPoints :: HasPolygon c_aASv => Lens' c_aASv [RPoint]
+ Graphics.Svg.Types: polygonPoints :: HasPolygon c_aASM => Lens' c_aASM [RPoint]
- Graphics.Svg.Types: preserveAspectRatio :: HasPreserveAspectRatio c_azP5 => Lens' c_azP5 PreserveAspectRatio
+ Graphics.Svg.Types: preserveAspectRatio :: HasPreserveAspectRatio c_azPm => Lens' c_azPm PreserveAspectRatio
- Graphics.Svg.Types: radialGradient :: HasRadialGradient c_aDPb => Lens' c_aDPb RadialGradient
+ Graphics.Svg.Types: radialGradient :: HasRadialGradient c_aDPs => Lens' c_aDPs RadialGradient
- Graphics.Svg.Types: radialGradientCenter :: HasRadialGradient c_aDPb => Lens' c_aDPb Point
+ Graphics.Svg.Types: radialGradientCenter :: HasRadialGradient c_aDPs => Lens' c_aDPs Point
- Graphics.Svg.Types: radialGradientFocusX :: HasRadialGradient c_aDPb => Lens' c_aDPb (Maybe Number)
+ Graphics.Svg.Types: radialGradientFocusX :: HasRadialGradient c_aDPs => Lens' c_aDPs (Maybe Number)
- Graphics.Svg.Types: radialGradientFocusY :: HasRadialGradient c_aDPb => Lens' c_aDPb (Maybe Number)
+ Graphics.Svg.Types: radialGradientFocusY :: HasRadialGradient c_aDPs => Lens' c_aDPs (Maybe Number)
- Graphics.Svg.Types: radialGradientRadius :: HasRadialGradient c_aDPb => Lens' c_aDPb Number
+ Graphics.Svg.Types: radialGradientRadius :: HasRadialGradient c_aDPs => Lens' c_aDPs Number
- Graphics.Svg.Types: radialGradientSpread :: HasRadialGradient c_aDPb => Lens' c_aDPb Spread
+ Graphics.Svg.Types: radialGradientSpread :: HasRadialGradient c_aDPs => Lens' c_aDPs Spread
- Graphics.Svg.Types: radialGradientStops :: HasRadialGradient c_aDPb => Lens' c_aDPb [GradientStop]
+ Graphics.Svg.Types: radialGradientStops :: HasRadialGradient c_aDPs => Lens' c_aDPs [GradientStop]
- Graphics.Svg.Types: radialGradientTransform :: HasRadialGradient c_aDPb => Lens' c_aDPb [Transformation]
+ Graphics.Svg.Types: radialGradientTransform :: HasRadialGradient c_aDPs => Lens' c_aDPs [Transformation]
- Graphics.Svg.Types: radialGradientUnits :: HasRadialGradient c_aDPb => Lens' c_aDPb CoordinateUnits
+ Graphics.Svg.Types: radialGradientUnits :: HasRadialGradient c_aDPs => Lens' c_aDPs CoordinateUnits
- Graphics.Svg.Types: rectCornerRadius :: HasRectangle c_aB3i => Lens' c_aB3i (Number, Number)
+ Graphics.Svg.Types: rectCornerRadius :: HasRectangle c_aB3z => Lens' c_aB3z (Number, Number)
- Graphics.Svg.Types: rectDrawAttributes :: HasRectangle c_aB3i => Lens' c_aB3i DrawAttributes
+ Graphics.Svg.Types: rectDrawAttributes :: HasRectangle c_aB3z => Lens' c_aB3z DrawAttributes
- Graphics.Svg.Types: rectHeight :: HasRectangle c_aB3i => Lens' c_aB3i Number
+ Graphics.Svg.Types: rectHeight :: HasRectangle c_aB3z => Lens' c_aB3z Number
- Graphics.Svg.Types: rectUpperLeftCorner :: HasRectangle c_aB3i => Lens' c_aB3i Point
+ Graphics.Svg.Types: rectUpperLeftCorner :: HasRectangle c_aB3z => Lens' c_aB3z Point
- Graphics.Svg.Types: rectWidth :: HasRectangle c_aB3i => Lens' c_aB3i Number
+ Graphics.Svg.Types: rectWidth :: HasRectangle c_aB3z => Lens' c_aB3z Number
- Graphics.Svg.Types: rectangle :: HasRectangle c_aB3i => Lens' c_aB3i Rectangle
+ Graphics.Svg.Types: rectangle :: HasRectangle c_aB3z => Lens' c_aB3z Rectangle
- Graphics.Svg.Types: spanContent :: HasTextSpan c_aCzw => Lens' c_aCzw [TextSpanContent]
+ Graphics.Svg.Types: spanContent :: HasTextSpan c_aCzN => Lens' c_aCzN [TextSpanContent]
- Graphics.Svg.Types: spanDrawAttributes :: HasTextSpan c_aCzw => Lens' c_aCzw DrawAttributes
+ Graphics.Svg.Types: spanDrawAttributes :: HasTextSpan c_aCzN => Lens' c_aCzN DrawAttributes
- Graphics.Svg.Types: spanInfo :: HasTextSpan c_aCzw => Lens' c_aCzw TextInfo
+ Graphics.Svg.Types: spanInfo :: HasTextSpan c_aCzN => Lens' c_aCzN TextInfo
- Graphics.Svg.Types: strokeColor :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Texture)
+ Graphics.Svg.Types: strokeColor :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Texture)
- Graphics.Svg.Types: strokeDashArray :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last [Number])
+ Graphics.Svg.Types: strokeDashArray :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last [Number])
- Graphics.Svg.Types: strokeLineCap :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Cap)
+ Graphics.Svg.Types: strokeLineCap :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Cap)
- Graphics.Svg.Types: strokeLineJoin :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last LineJoin)
+ Graphics.Svg.Types: strokeLineJoin :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last LineJoin)
- Graphics.Svg.Types: strokeMiterLimit :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Double)
+ Graphics.Svg.Types: strokeMiterLimit :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Double)
- Graphics.Svg.Types: strokeOffset :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Number)
+ Graphics.Svg.Types: strokeOffset :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Number)
- Graphics.Svg.Types: strokeOpacity :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Maybe Float)
+ Graphics.Svg.Types: strokeOpacity :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Maybe Float)
- Graphics.Svg.Types: strokeWidth :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last Number)
+ Graphics.Svg.Types: strokeWidth :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last Number)
- Graphics.Svg.Types: styleRules :: HasDocument c_aEuC => Lens' c_aEuC [CssRule]
+ Graphics.Svg.Types: styleRules :: HasDocument c_aEuT => Lens' c_aEuT [CssRule]
- Graphics.Svg.Types: text :: HasText c_aCOo => Lens' c_aCOo Text
+ Graphics.Svg.Types: text :: HasText c_aCOF => Lens' c_aCOF Text
- Graphics.Svg.Types: textAdjust :: HasText c_aCOo => Lens' c_aCOo TextAdjust
+ Graphics.Svg.Types: textAdjust :: HasText c_aCOF => Lens' c_aCOF TextAdjust
- Graphics.Svg.Types: textAnchor :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Last TextAnchor)
+ Graphics.Svg.Types: textAnchor :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Last TextAnchor)
- Graphics.Svg.Types: textInfo :: HasTextInfo c_aCpX => Lens' c_aCpX TextInfo
+ Graphics.Svg.Types: textInfo :: HasTextInfo c_aCqe => Lens' c_aCqe TextInfo
- Graphics.Svg.Types: textInfoDX :: HasTextInfo c_aCpX => Lens' c_aCpX [Number]
+ Graphics.Svg.Types: textInfoDX :: HasTextInfo c_aCqe => Lens' c_aCqe [Number]
- Graphics.Svg.Types: textInfoDY :: HasTextInfo c_aCpX => Lens' c_aCpX [Number]
+ Graphics.Svg.Types: textInfoDY :: HasTextInfo c_aCqe => Lens' c_aCqe [Number]
- Graphics.Svg.Types: textInfoLength :: HasTextInfo c_aCpX => Lens' c_aCpX (Maybe Number)
+ Graphics.Svg.Types: textInfoLength :: HasTextInfo c_aCqe => Lens' c_aCqe (Maybe Number)
- Graphics.Svg.Types: textInfoRotate :: HasTextInfo c_aCpX => Lens' c_aCpX [Double]
+ Graphics.Svg.Types: textInfoRotate :: HasTextInfo c_aCqe => Lens' c_aCqe [Double]
- Graphics.Svg.Types: textInfoX :: HasTextInfo c_aCpX => Lens' c_aCpX [Number]
+ Graphics.Svg.Types: textInfoX :: HasTextInfo c_aCqe => Lens' c_aCqe [Number]
- Graphics.Svg.Types: textInfoY :: HasTextInfo c_aCpX => Lens' c_aCpX [Number]
+ Graphics.Svg.Types: textInfoY :: HasTextInfo c_aCqe => Lens' c_aCqe [Number]
- Graphics.Svg.Types: textPath :: HasTextPath c_aCH9 => Lens' c_aCH9 TextPath
+ Graphics.Svg.Types: textPath :: HasTextPath c_aCHq => Lens' c_aCHq TextPath
- Graphics.Svg.Types: textPathData :: HasTextPath c_aCH9 => Lens' c_aCH9 [PathCommand]
+ Graphics.Svg.Types: textPathData :: HasTextPath c_aCHq => Lens' c_aCHq [PathCommand]
- Graphics.Svg.Types: textPathMethod :: HasTextPath c_aCH9 => Lens' c_aCH9 TextPathMethod
+ Graphics.Svg.Types: textPathMethod :: HasTextPath c_aCHq => Lens' c_aCHq TextPathMethod
- Graphics.Svg.Types: textPathName :: HasTextPath c_aCH9 => Lens' c_aCH9 String
+ Graphics.Svg.Types: textPathName :: HasTextPath c_aCHq => Lens' c_aCHq String
- Graphics.Svg.Types: textPathSpacing :: HasTextPath c_aCH9 => Lens' c_aCH9 TextPathSpacing
+ Graphics.Svg.Types: textPathSpacing :: HasTextPath c_aCHq => Lens' c_aCHq TextPathSpacing
- Graphics.Svg.Types: textPathStartOffset :: HasTextPath c_aCH9 => Lens' c_aCH9 Number
+ Graphics.Svg.Types: textPathStartOffset :: HasTextPath c_aCHq => Lens' c_aCHq Number
- Graphics.Svg.Types: textRoot :: HasText c_aCOo => Lens' c_aCOo TextSpan
+ Graphics.Svg.Types: textRoot :: HasText c_aCOF => Lens' c_aCOF TextSpan
- Graphics.Svg.Types: textSpan :: HasTextSpan c_aCzw => Lens' c_aCzw TextSpan
+ Graphics.Svg.Types: textSpan :: HasTextSpan c_aCzN => Lens' c_aCzN TextSpan
- Graphics.Svg.Types: transform :: HasDrawAttributes c_aAjQ => Lens' c_aAjQ (Maybe [Transformation])
+ Graphics.Svg.Types: transform :: HasDrawAttributes c_aAk7 => Lens' c_aAk7 (Maybe [Transformation])
- Graphics.Svg.Types: use :: HasUse c_aCgH => Lens' c_aCgH Use
+ Graphics.Svg.Types: use :: HasUse c_aCgY => Lens' c_aCgY Use
- Graphics.Svg.Types: useBase :: HasUse c_aCgH => Lens' c_aCgH Point
+ Graphics.Svg.Types: useBase :: HasUse c_aCgY => Lens' c_aCgY Point
- Graphics.Svg.Types: useDrawAttributes :: HasUse c_aCgH => Lens' c_aCgH DrawAttributes
+ Graphics.Svg.Types: useDrawAttributes :: HasUse c_aCgY => Lens' c_aCgY DrawAttributes
- Graphics.Svg.Types: useHeight :: HasUse c_aCgH => Lens' c_aCgH (Maybe Number)
+ Graphics.Svg.Types: useHeight :: HasUse c_aCgY => Lens' c_aCgY (Maybe Number)
- Graphics.Svg.Types: useName :: HasUse c_aCgH => Lens' c_aCgH String
+ Graphics.Svg.Types: useName :: HasUse c_aCgY => Lens' c_aCgY String
- Graphics.Svg.Types: useWidth :: HasUse c_aCgH => Lens' c_aCgH (Maybe Number)
+ Graphics.Svg.Types: useWidth :: HasUse c_aCgY => Lens' c_aCgY (Maybe Number)
- Graphics.Svg.Types: viewBox :: HasDocument c_aEuC => Lens' c_aEuC (Maybe (Double, Double, Double, Double))
+ Graphics.Svg.Types: viewBox :: HasDocument c_aEuT => Lens' c_aEuT (Maybe (Double, Double, Double, Double))
- Graphics.Svg.Types: width :: HasDocument c_aEuC => Lens' c_aEuC (Maybe Number)
+ Graphics.Svg.Types: width :: HasDocument c_aEuT => Lens' c_aEuT (Maybe Number)
Files
- changelog.md +60/−57
- src/Graphics/Svg.hs +111/−111
- src/Graphics/Svg/ColorParser.hs +111/−111
- src/Graphics/Svg/CssParser.hs +257/−257
- src/Graphics/Svg/CssTypes.hs +271/−271
- src/Graphics/Svg/PathParser.hs +231/−226
- src/Graphics/Svg/Types.hs +1521/−1521
- src/Graphics/Svg/XmlParser.hs +1164/−1164
- svg-tree.cabal +61/−61
changelog.md view
@@ -1,57 +1,60 @@--*-change-log-*---v0.5.1.1: May 2016- * Fix: GHC 8.0 compatibility--v0.5.1: March 2016- * Fix: serialization of multi criteria css selector.--v0.5: March 2016:- * Adding: preserveAspectRatio attribute- * Fix: Application of CSS rules with indirect parent/child relation.--v0.4.2: March 2016- * Enhancement: avoiding serializatinon of empty class attribute- * Fix: incorrect deserialization of complex CSS- * Fix: Really fixing duplicate ID with serialization--v0.4.1: February 2016- * Fix: fixing duplicate ID with serialization--v0.4: February 2016- * Breaking change: viewbox types are no longer Int- but double, sneakingly passed in v0.3.2.2. This- version acknoweledge this change--V0.3.2.2 February 2016 (Deprecated)- * Fix: Bad serialization of some None constructors.--v0.3.2.1 October 2015- * Fix: Don't add '#' for <img> serialization--v0.3.2 August 2015- * Fix: allow compilation with GHC 7.4--v0.3.1 May 2015- * Fix: Bumping lens dependency and removing upper bound.--v0.3 April 2015- * Breaking change: Switching all the numeric types associated to geometry- to Double precision (thx to Kasbah)--v0.2 April 2015- * Fix: Differentiating opacity & fill-opacity, as they are- semantically deferent (BREAKING CHANGE!)--v0.1.1 April 2015- * Fix: Bumping lens bounds--v0.1.0.2 March 2015- * Fix: Bumping lens bounds--v0.1.0.1- * Fix: Lowering some lower bounds--v0.1- * Initial release-+-*-change-log-*- + +v0.5.1.2: September 2016 + * Fix path parsing with white space prefix + +v0.5.1.1: May 2016 + * Fix: GHC 8.0 compatibility + +v0.5.1: March 2016 + * Fix: serialization of multi criteria css selector. + +v0.5: March 2016: + * Adding: preserveAspectRatio attribute + * Fix: Application of CSS rules with indirect parent/child relation. + +v0.4.2: March 2016 + * Enhancement: avoiding serializatinon of empty class attribute + * Fix: incorrect deserialization of complex CSS + * Fix: Really fixing duplicate ID with serialization + +v0.4.1: February 2016 + * Fix: fixing duplicate ID with serialization + +v0.4: February 2016 + * Breaking change: viewbox types are no longer Int + but double, sneakingly passed in v0.3.2.2. This + version acknoweledge this change + +V0.3.2.2 February 2016 (Deprecated) + * Fix: Bad serialization of some None constructors. + +v0.3.2.1 October 2015 + * Fix: Don't add '#' for <img> serialization + +v0.3.2 August 2015 + * Fix: allow compilation with GHC 7.4 + +v0.3.1 May 2015 + * Fix: Bumping lens dependency and removing upper bound. + +v0.3 April 2015 + * Breaking change: Switching all the numeric types associated to geometry + to Double precision (thx to Kasbah) + +v0.2 April 2015 + * Fix: Differentiating opacity & fill-opacity, as they are + semantically deferent (BREAKING CHANGE!) + +v0.1.1 April 2015 + * Fix: Bumping lens bounds + +v0.1.0.2 March 2015 + * Fix: Bumping lens bounds + +v0.1.0.1 + * Fix: Lowering some lower bounds + +v0.1 + * Initial release +
src/Graphics/Svg.hs view
@@ -1,111 +1,111 @@-{-# LANGUAGE CPP #-}--- | Module providing basic input/output for the SVG document,--- for document building, please refer to Graphics.Svg.Types.-module Graphics.Svg ( -- * Saving/Loading functions- loadSvgFile- , parseSvgFile- , xmlOfDocument- , saveXmlFile-- -- * Manipulation functions- , cssApply- , cssRulesOfText- , applyCSSRules- , resolveUses-- -- * Type definitions- , module Graphics.Svg.Types- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative( (<$>) )-#endif--import Data.List( foldl' )-import qualified Data.ByteString as B-import qualified Data.Map as M-import qualified Data.Text as T-import Text.XML.Light.Input( parseXMLDoc )-import Text.XML.Light.Output( ppcTopElement, prettyConfigPP )-import Control.Lens--import Graphics.Svg.Types-import Graphics.Svg.CssTypes-import Graphics.Svg.CssParser( cssRulesOfText )-import Graphics.Svg.XmlParser--{-import Graphics.Svg.CssParser-}---- | Try to load an svg file on disc and parse it as--- a SVG Document.-loadSvgFile :: FilePath -> IO (Maybe Document)-loadSvgFile filename =- parseSvgFile filename <$> B.readFile filename---- | Parse an in-memory SVG file-parseSvgFile :: FilePath -- ^ Source path/URL of the document, used- -- to resolve relative links.- -> B.ByteString- -> Maybe Document-parseSvgFile filename fileContent =- parseXMLDoc fileContent >>= unparseDocument filename---- | Save a svg Document on a file on disk.-saveXmlFile :: FilePath -> Document -> IO ()-saveXmlFile filePath =- writeFile filePath . ppcTopElement prettyConfigPP . xmlOfDocument--cssDeclApplyer :: DrawAttributes -> CssDeclaration- -> DrawAttributes-cssDeclApplyer value (CssDeclaration txt elems) =- case lookup txt cssUpdaters of- Nothing -> value- Just f -> f value elems- where- cssUpdaters = [(T.pack $ _attributeName n, u) |- (n, u) <- drawAttributesList]---- | Rewrite a SVG Tree using some CSS rules.------ This action will propagate the definition of the--- css directly in each matched element.-cssApply :: [CssRule] -> Tree -> Tree-cssApply rules = zipTree go where- go [] = None- go ([]:_) = None- go context@((t:_):_) = t & drawAttr .~ attr'- where- matchingDeclarations =- findMatchingDeclarations rules context- attr = view drawAttr t- attr' = foldl' cssDeclApplyer attr matchingDeclarations---- | For every 'use' tag, try to resolve the geometry associated--- with it and place it in the scene Tree. It is important to--- resolve the 'use' tag before applying the CSS rules, as some--- rules may apply some elements matching the children of "use".-resolveUses :: Document -> Document-resolveUses doc =- doc { _elements = mapTree fetchUses <$> _elements doc }- where- fetchUses (UseTree useInfo _) = UseTree useInfo $ search useInfo- fetchUses a = a-- search nfo = maybe Nothing geometryExtract found where- found = M.lookup (_useName nfo) $ _definitions doc-- geometryExtract c = case c of- ElementLinearGradient _ -> Nothing- ElementRadialGradient _ -> Nothing- ElementMask _ -> Nothing- ElementClipPath _ -> Nothing- ElementGeometry t -> Just t- ElementPattern _ -> Nothing- ElementMarker _ -> Nothing---- | Rewrite the document by applying the CSS rules embedded--- inside it.-applyCSSRules :: Document -> Document-applyCSSRules doc = doc- { _elements = cssApply (_styleRules doc) <$> _elements doc }-+{-# LANGUAGE CPP #-} +-- | Module providing basic input/output for the SVG document, +-- for document building, please refer to Graphics.Svg.Types. +module Graphics.Svg ( -- * Saving/Loading functions + loadSvgFile + , parseSvgFile + , xmlOfDocument + , saveXmlFile + + -- * Manipulation functions + , cssApply + , cssRulesOfText + , applyCSSRules + , resolveUses + + -- * Type definitions + , module Graphics.Svg.Types + ) where + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative( (<$>) ) +#endif + +import Data.List( foldl' ) +import qualified Data.ByteString as B +import qualified Data.Map as M +import qualified Data.Text as T +import Text.XML.Light.Input( parseXMLDoc ) +import Text.XML.Light.Output( ppcTopElement, prettyConfigPP ) +import Control.Lens + +import Graphics.Svg.Types +import Graphics.Svg.CssTypes +import Graphics.Svg.CssParser( cssRulesOfText ) +import Graphics.Svg.XmlParser + +{-import Graphics.Svg.CssParser-} + +-- | Try to load an svg file on disc and parse it as +-- a SVG Document. +loadSvgFile :: FilePath -> IO (Maybe Document) +loadSvgFile filename = + parseSvgFile filename <$> B.readFile filename + +-- | Parse an in-memory SVG file +parseSvgFile :: FilePath -- ^ Source path/URL of the document, used + -- to resolve relative links. + -> B.ByteString + -> Maybe Document +parseSvgFile filename fileContent = + parseXMLDoc fileContent >>= unparseDocument filename + +-- | Save a svg Document on a file on disk. +saveXmlFile :: FilePath -> Document -> IO () +saveXmlFile filePath = + writeFile filePath . ppcTopElement prettyConfigPP . xmlOfDocument + +cssDeclApplyer :: DrawAttributes -> CssDeclaration + -> DrawAttributes +cssDeclApplyer value (CssDeclaration txt elems) = + case lookup txt cssUpdaters of + Nothing -> value + Just f -> f value elems + where + cssUpdaters = [(T.pack $ _attributeName n, u) | + (n, u) <- drawAttributesList] + +-- | Rewrite a SVG Tree using some CSS rules. +-- +-- This action will propagate the definition of the +-- css directly in each matched element. +cssApply :: [CssRule] -> Tree -> Tree +cssApply rules = zipTree go where + go [] = None + go ([]:_) = None + go context@((t:_):_) = t & drawAttr .~ attr' + where + matchingDeclarations = + findMatchingDeclarations rules context + attr = view drawAttr t + attr' = foldl' cssDeclApplyer attr matchingDeclarations + +-- | For every 'use' tag, try to resolve the geometry associated +-- with it and place it in the scene Tree. It is important to +-- resolve the 'use' tag before applying the CSS rules, as some +-- rules may apply some elements matching the children of "use". +resolveUses :: Document -> Document +resolveUses doc = + doc { _elements = mapTree fetchUses <$> _elements doc } + where + fetchUses (UseTree useInfo _) = UseTree useInfo $ search useInfo + fetchUses a = a + + search nfo = maybe Nothing geometryExtract found where + found = M.lookup (_useName nfo) $ _definitions doc + + geometryExtract c = case c of + ElementLinearGradient _ -> Nothing + ElementRadialGradient _ -> Nothing + ElementMask _ -> Nothing + ElementClipPath _ -> Nothing + ElementGeometry t -> Just t + ElementPattern _ -> Nothing + ElementMarker _ -> Nothing + +-- | Rewrite the document by applying the CSS rules embedded +-- inside it. +applyCSSRules :: Document -> Document +applyCSSRules doc = doc + { _elements = cssApply (_styleRules doc) <$> _elements doc } +
src/Graphics/Svg/ColorParser.hs view
@@ -1,111 +1,111 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-module Graphics.Svg.ColorParser( colorParser- , colorSerializer- , textureParser- , textureSerializer- , urlRef- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative( (<*>), (<*), (*>), (<$>), (<$) )-#endif--import Data.Bits( (.|.), unsafeShiftL )-import Control.Applicative( (<|>) )-import Data.Attoparsec.Text- ( Parser- , string- , skipSpace- , satisfy- , inClass- , takeWhile1- , option- , char- , digit- , letter- , many1- , scientific- )--import Text.Printf( printf )-import Data.Scientific( toRealFloat )-import Codec.Picture( PixelRGBA8( .. ) )-import Data.Word( Word8 )-import Graphics.Svg.NamedColors-import Graphics.Svg.Types-import qualified Data.Map as M--commaWsp :: Parser ()-commaWsp = skipSpace *> option () (string "," *> return ())- <* skipSpace---num :: Parser Double-num = realToFrac <$> (skipSpace *> plusMinus <* skipSpace)- where doubleNumber :: Parser Double- doubleNumber = toRealFloat <$> scientific-- plusMinus = negate <$ string "-" <*> doubleNumber- <|> string "+" *> doubleNumber- <|> doubleNumber--colorSerializer :: PixelRGBA8 -> String-colorSerializer (PixelRGBA8 r g b _) = printf "#%02X%02X%02X" r g b--colorParser :: Parser PixelRGBA8-colorParser = rgbColor- <|> (string "#" *> (color <|> colorReduced))- <|> namedColor- where- charRange c1 c2 =- (\c -> fromIntegral $ fromEnum c - fromEnum c1) <$> satisfy (\v -> c1 <= v && v <= c2)- black = PixelRGBA8 0 0 0 255-- hexChar :: Parser Word8- hexChar = charRange '0' '9'- <|> ((+ 10) <$> charRange 'a' 'f')- <|> ((+ 10) <$> charRange 'A' 'F')-- namedColor = do- str <- takeWhile1 (inClass "a-z")- return $ M.findWithDefault black str svgNamedColors-- percentToWord v = floor $ v * (255 / 100)-- numPercent = ((percentToWord <$> num) <* string "%")- <|> (floor <$> num)-- hexByte = (\h1 h2 -> h1 `unsafeShiftL` 4 .|. h2)- <$> hexChar <*> hexChar-- color = (\r g b -> PixelRGBA8 r g b 255)- <$> hexByte <*> hexByte <*> hexByte- rgbColor = (\r g b -> PixelRGBA8 r g b 255)- <$> (string "rgb(" *> numPercent)- <*> (commaWsp *> numPercent)- <*> (commaWsp *> numPercent <* skipSpace <* string ")")-- colorReduced =- (\r g b -> PixelRGBA8 (r * 17) (g * 17) (b * 17) 255)- <$> hexChar <*> hexChar <*> hexChar---textureSerializer :: Texture -> String-textureSerializer (ColorRef px) = colorSerializer px-textureSerializer (TextureRef str) = printf "url(#%s)" str-textureSerializer FillNone = "none"--urlRef :: Parser String-urlRef = string "url(" *> skipSpace *>- char '#' *> many1 (letter <|> digit)- <* skipSpace <* char ')' <* skipSpace---textureParser :: Parser Texture-textureParser =- none <|> (TextureRef <$> urlRef)- <|> (ColorRef <$> colorParser)- where- none = FillNone <$ string "none"-+{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} +module Graphics.Svg.ColorParser( colorParser + , colorSerializer + , textureParser + , textureSerializer + , urlRef + ) where + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative( (<*>), (<*), (*>), (<$>), (<$) ) +#endif + +import Data.Bits( (.|.), unsafeShiftL ) +import Control.Applicative( (<|>) ) +import Data.Attoparsec.Text + ( Parser + , string + , skipSpace + , satisfy + , inClass + , takeWhile1 + , option + , char + , digit + , letter + , many1 + , scientific + ) + +import Text.Printf( printf ) +import Data.Scientific( toRealFloat ) +import Codec.Picture( PixelRGBA8( .. ) ) +import Data.Word( Word8 ) +import Graphics.Svg.NamedColors +import Graphics.Svg.Types +import qualified Data.Map as M + +commaWsp :: Parser () +commaWsp = skipSpace *> option () (string "," *> return ()) + <* skipSpace + + +num :: Parser Double +num = realToFrac <$> (skipSpace *> plusMinus <* skipSpace) + where doubleNumber :: Parser Double + doubleNumber = toRealFloat <$> scientific + + plusMinus = negate <$ string "-" <*> doubleNumber + <|> string "+" *> doubleNumber + <|> doubleNumber + +colorSerializer :: PixelRGBA8 -> String +colorSerializer (PixelRGBA8 r g b _) = printf "#%02X%02X%02X" r g b + +colorParser :: Parser PixelRGBA8 +colorParser = rgbColor + <|> (string "#" *> (color <|> colorReduced)) + <|> namedColor + where + charRange c1 c2 = + (\c -> fromIntegral $ fromEnum c - fromEnum c1) <$> satisfy (\v -> c1 <= v && v <= c2) + black = PixelRGBA8 0 0 0 255 + + hexChar :: Parser Word8 + hexChar = charRange '0' '9' + <|> ((+ 10) <$> charRange 'a' 'f') + <|> ((+ 10) <$> charRange 'A' 'F') + + namedColor = do + str <- takeWhile1 (inClass "a-z") + return $ M.findWithDefault black str svgNamedColors + + percentToWord v = floor $ v * (255 / 100) + + numPercent = ((percentToWord <$> num) <* string "%") + <|> (floor <$> num) + + hexByte = (\h1 h2 -> h1 `unsafeShiftL` 4 .|. h2) + <$> hexChar <*> hexChar + + color = (\r g b -> PixelRGBA8 r g b 255) + <$> hexByte <*> hexByte <*> hexByte + rgbColor = (\r g b -> PixelRGBA8 r g b 255) + <$> (string "rgb(" *> numPercent) + <*> (commaWsp *> numPercent) + <*> (commaWsp *> numPercent <* skipSpace <* string ")") + + colorReduced = + (\r g b -> PixelRGBA8 (r * 17) (g * 17) (b * 17) 255) + <$> hexChar <*> hexChar <*> hexChar + + +textureSerializer :: Texture -> String +textureSerializer (ColorRef px) = colorSerializer px +textureSerializer (TextureRef str) = printf "url(#%s)" str +textureSerializer FillNone = "none" + +urlRef :: Parser String +urlRef = string "url(" *> skipSpace *> + char '#' *> many1 (letter <|> digit) + <* skipSpace <* char ')' <* skipSpace + + +textureParser :: Parser Texture +textureParser = + none <|> (TextureRef <$> urlRef) + <|> (ColorRef <$> colorParser) + where + none = FillNone <$ string "none" +
src/Graphics/Svg/CssParser.hs view
@@ -1,257 +1,257 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PatternGuards #-}-module Graphics.Svg.CssParser- ( CssElement( .. )- , complexNumber- , declaration- , ruleSet- , styleString- , dashArray- , numberList- , num- , cssRulesOfText- )- where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative( (<*>), (<*), (*>)- , (<$>), (<$)- , pure- )-#endif--import Control.Applicative( (<|>)- , many- )-import Data.Attoparsec.Text- ( Parser- , double- , string- , skipSpace- , letter- , char- , digit- {-, skip-}- , sepBy1- , (<?>)- , skipMany- , notChar- , parseOnly- )-import qualified Data.Attoparsec.Text as AT--import Data.Attoparsec.Combinator- ( option- , sepBy- {-, sepBy1-}- , many1- )--import Codec.Picture( PixelRGBA8( .. ) )-import Graphics.Svg.Types-import Graphics.Svg.NamedColors( svgNamedColors )-import Graphics.Svg.ColorParser( colorParser )-import Graphics.Svg.CssTypes-import qualified Data.Text as T-import qualified Data.Map as M-{-import Graphics.Rasterific.Linear( V2( V2 ) )-}-{-import Graphics.Rasterific.Transformations-}--num :: Parser Double-num = realToFrac <$> (skipSpace *> plusMinus <* skipSpace)- where doubleNumber = char '.' *> (scale <$> double)- <|> double-- scalingCoeff n = 10 ^ digitCount- where digitCount :: Int- digitCount = ceiling . logBase 10 $ abs n-- scale n = n / scalingCoeff n-- plusMinus = negate <$ string "-" <*> doubleNumber- <|> string "+" *> doubleNumber- <|> doubleNumber---ident :: Parser T.Text-ident =- (\f c -> f . T.cons c . T.pack)- <$> trailingSub- <*> nmstart <*> nmchar- where- trailingSub = option id $ T.cons '-' <$ char '-'- underscore = char '_'- nmstart = letter <|> underscore- nmchar = many (letter <|> digit <|> underscore <|> char '-')--str :: Parser T.Text-str = char '"' *> AT.takeWhile (/= '"') <* char '"' <* skipSpace- <?> "str"--between :: Char -> Char -> Parser a -> Parser a-between o e p =- (skipSpace *>- char o *> skipSpace *> p- <* skipSpace <* char e <* skipSpace)- <?> ("between " ++ [o, e])--bracket :: Parser a -> Parser a-bracket = between '[' ']'---comment :: Parser ()-comment = string "/*" *> toStar *> skipSpace- where- toStar = skipMany (notChar '*') *> char '*' *> testEnd- testEnd = (() <$ char '/') <|> toStar--cleanSpace :: Parser ()-cleanSpace = skipSpace <* many comment---- | combinator: '+' S* | '>' S*-combinator :: Parser CssSelector-combinator = parse <* cleanSpace where- parse = Nearby <$ char '+'- <|> DirectChildren <$ char '>'- <?> "combinator"---- unary_operator : '-' | '+' ;--commaWsp :: Parser Char-commaWsp = skipSpace *> option ',' (char ',') <* skipSpace--ruleSet :: Parser CssRule-ruleSet = cleanSpace *> rule where- rule = CssRule- <$> selector `sepBy1` commaWsp- <*> (between '{' '}' styleString)- <?> "cssrule"--styleString :: Parser [CssDeclaration]-styleString = ((cleanSpace *> declaration) `sepBy` semiWsp) <* mayWsp- <?> "styleString"- where semiWsp = skipSpace *> char ';' <* skipSpace- mayWsp = option ';' semiWsp--selector :: Parser [CssSelector]-selector = (:)- <$> (AllOf <$> simpleSelector <* skipSpace <?> "firstpart:(")- <*> ((next <|> return []) <?> "secondpart")- <?> "selector"- where- combOpt :: Parser ([CssSelector] -> [CssSelector])-- combOpt = cleanSpace *> option id ((:) <$> combinator)- next :: Parser [CssSelector]- next = id <$> combOpt <*> selector--simpleSelector :: Parser [CssDescriptor]-simpleSelector = (:) <$> elementName <*> many whole- <|> (many1 whole <?> "inmany")- <?> "simple selector"- where- whole = pseudo <|> hash <|> classParser <|> attrib- <?> "whole"- pseudo = char ':' *> (OfPseudoClass <$> ident)- <?> "pseudo"- hash = char '#' *> (OfId <$> ident)- <?> "hash"- classParser = char '.' *> (OfClass <$> ident)- <?> "classParser"-- elementName = el <* skipSpace <?> "elementName"- where el = (OfName <$> ident)- <|> AnyElem <$ char '*'-- attrib = bracket- (WithAttrib <$> ident <*> (char '=' *> skipSpace *> (ident <|> str))- <?> "attrib")--declaration :: Parser CssDeclaration-declaration =- CssDeclaration <$> property- <*> (char ':'- *> cleanSpace- *> many1 expr- <* prio- )- <?> "declaration"- where- property = (ident <* cleanSpace) <?> "property"- prio = option "" $ string "!important"--operator :: Parser CssElement-operator = skipSpace *> op <* skipSpace- where- op = CssOpSlash <$ char '/'- <|> CssOpComa <$ char ','- <?> "operator"--expr :: Parser [CssElement]-expr = ((:) <$> term <*> (concat <$> many termOp))- <?> "expr"- where- op = option (:[]) $ (\a b -> [a, b]) <$> operator- termOp = ($) <$> op <*> term--dashArray :: Parser [Number]-dashArray = skipSpace *> (complexNumber `sepBy1` commaWsp)--numberList :: Parser [Double]-numberList = skipSpace *> (num `sepBy1` commaWsp)--complexNumber :: Parser Number-complexNumber = do- n <- num- (Percent (n / 100) <$ char '%')- <|> (Em n <$ string "em")- <|> (Mm n <$ string "mm")- <|> (Cm n <$ string "cm")- <|> (Point n <$ string "pt")- <|> (Pc n <$ string "pc")- <|> (Px n <$ string "px")- <|> (Inches n <$ string "in")- <|> pure (Num n)--term :: Parser CssElement-term = checkRgb <$> function- <|> (CssNumber <$> complexNumber)- <|> (CssString <$> str)- <|> (checkNamedColor <$> ident)- <|> (CssColor <$> colorParser)- where- comma = skipSpace *> char ',' <* skipSpace- checkNamedColor n- | Just c <- M.lookup n svgNamedColors = CssColor c- | otherwise = CssIdent n-- ref = char '#' *> ident-- checkRgb (CssFunction "rgb"- [CssNumber r, CssNumber g, CssNumber b]) =- CssColor $ PixelRGBA8 (to r) (to g) (to b) 255- where clamp = max 0 . min 255- to (Num n) = floor $ clamp n- to (Px n) = floor $ clamp n- to (Percent p) = floor . clamp $ p * 255- to (Em c) = floor $ clamp c- to (Pc n) = floor $ clamp n- to (Mm n) = floor $ clamp n- to (Cm n) = floor $ clamp n- to (Point n) = floor $ clamp n- to (Inches n) = floor $ clamp n-- checkRgb a = a- functionParam = (CssReference <$> ref) <|> term-- function = CssFunction- <$> ident <* char '('- <*> (functionParam `sepBy` comma) <* char ')' <* skipSpace---- | Parse CSS text into rules.-cssRulesOfText :: T.Text -> [CssRule]-cssRulesOfText txt = case parseOnly (many1 ruleSet) $ txt of- Left _ -> []- Right rules -> rules-+{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternGuards #-} +module Graphics.Svg.CssParser + ( CssElement( .. ) + , complexNumber + , declaration + , ruleSet + , styleString + , dashArray + , numberList + , num + , cssRulesOfText + ) + where + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative( (<*>), (<*), (*>) + , (<$>), (<$) + , pure + ) +#endif + +import Control.Applicative( (<|>) + , many + ) +import Data.Attoparsec.Text + ( Parser + , double + , string + , skipSpace + , letter + , char + , digit + {-, skip-} + , sepBy1 + , (<?>) + , skipMany + , notChar + , parseOnly + ) +import qualified Data.Attoparsec.Text as AT + +import Data.Attoparsec.Combinator + ( option + , sepBy + {-, sepBy1-} + , many1 + ) + +import Codec.Picture( PixelRGBA8( .. ) ) +import Graphics.Svg.Types +import Graphics.Svg.NamedColors( svgNamedColors ) +import Graphics.Svg.ColorParser( colorParser ) +import Graphics.Svg.CssTypes +import qualified Data.Text as T +import qualified Data.Map as M +{-import Graphics.Rasterific.Linear( V2( V2 ) )-} +{-import Graphics.Rasterific.Transformations-} + +num :: Parser Double +num = realToFrac <$> (skipSpace *> plusMinus <* skipSpace) + where doubleNumber = char '.' *> (scale <$> double) + <|> double + + scalingCoeff n = 10 ^ digitCount + where digitCount :: Int + digitCount = ceiling . logBase 10 $ abs n + + scale n = n / scalingCoeff n + + plusMinus = negate <$ string "-" <*> doubleNumber + <|> string "+" *> doubleNumber + <|> doubleNumber + + +ident :: Parser T.Text +ident = + (\f c -> f . T.cons c . T.pack) + <$> trailingSub + <*> nmstart <*> nmchar + where + trailingSub = option id $ T.cons '-' <$ char '-' + underscore = char '_' + nmstart = letter <|> underscore + nmchar = many (letter <|> digit <|> underscore <|> char '-') + +str :: Parser T.Text +str = char '"' *> AT.takeWhile (/= '"') <* char '"' <* skipSpace + <?> "str" + +between :: Char -> Char -> Parser a -> Parser a +between o e p = + (skipSpace *> + char o *> skipSpace *> p + <* skipSpace <* char e <* skipSpace) + <?> ("between " ++ [o, e]) + +bracket :: Parser a -> Parser a +bracket = between '[' ']' + + +comment :: Parser () +comment = string "/*" *> toStar *> skipSpace + where + toStar = skipMany (notChar '*') *> char '*' *> testEnd + testEnd = (() <$ char '/') <|> toStar + +cleanSpace :: Parser () +cleanSpace = skipSpace <* many comment + +-- | combinator: '+' S* | '>' S* +combinator :: Parser CssSelector +combinator = parse <* cleanSpace where + parse = Nearby <$ char '+' + <|> DirectChildren <$ char '>' + <?> "combinator" + +-- unary_operator : '-' | '+' ; + +commaWsp :: Parser Char +commaWsp = skipSpace *> option ',' (char ',') <* skipSpace + +ruleSet :: Parser CssRule +ruleSet = cleanSpace *> rule where + rule = CssRule + <$> selector `sepBy1` commaWsp + <*> (between '{' '}' styleString) + <?> "cssrule" + +styleString :: Parser [CssDeclaration] +styleString = ((cleanSpace *> declaration) `sepBy` semiWsp) <* mayWsp + <?> "styleString" + where semiWsp = skipSpace *> char ';' <* skipSpace + mayWsp = option ';' semiWsp + +selector :: Parser [CssSelector] +selector = (:) + <$> (AllOf <$> simpleSelector <* skipSpace <?> "firstpart:(") + <*> ((next <|> return []) <?> "secondpart") + <?> "selector" + where + combOpt :: Parser ([CssSelector] -> [CssSelector]) + + combOpt = cleanSpace *> option id ((:) <$> combinator) + next :: Parser [CssSelector] + next = id <$> combOpt <*> selector + +simpleSelector :: Parser [CssDescriptor] +simpleSelector = (:) <$> elementName <*> many whole + <|> (many1 whole <?> "inmany") + <?> "simple selector" + where + whole = pseudo <|> hash <|> classParser <|> attrib + <?> "whole" + pseudo = char ':' *> (OfPseudoClass <$> ident) + <?> "pseudo" + hash = char '#' *> (OfId <$> ident) + <?> "hash" + classParser = char '.' *> (OfClass <$> ident) + <?> "classParser" + + elementName = el <* skipSpace <?> "elementName" + where el = (OfName <$> ident) + <|> AnyElem <$ char '*' + + attrib = bracket + (WithAttrib <$> ident <*> (char '=' *> skipSpace *> (ident <|> str)) + <?> "attrib") + +declaration :: Parser CssDeclaration +declaration = + CssDeclaration <$> property + <*> (char ':' + *> cleanSpace + *> many1 expr + <* prio + ) + <?> "declaration" + where + property = (ident <* cleanSpace) <?> "property" + prio = option "" $ string "!important" + +operator :: Parser CssElement +operator = skipSpace *> op <* skipSpace + where + op = CssOpSlash <$ char '/' + <|> CssOpComa <$ char ',' + <?> "operator" + +expr :: Parser [CssElement] +expr = ((:) <$> term <*> (concat <$> many termOp)) + <?> "expr" + where + op = option (:[]) $ (\a b -> [a, b]) <$> operator + termOp = ($) <$> op <*> term + +dashArray :: Parser [Number] +dashArray = skipSpace *> (complexNumber `sepBy1` commaWsp) + +numberList :: Parser [Double] +numberList = skipSpace *> (num `sepBy1` commaWsp) + +complexNumber :: Parser Number +complexNumber = do + n <- num + (Percent (n / 100) <$ char '%') + <|> (Em n <$ string "em") + <|> (Mm n <$ string "mm") + <|> (Cm n <$ string "cm") + <|> (Point n <$ string "pt") + <|> (Pc n <$ string "pc") + <|> (Px n <$ string "px") + <|> (Inches n <$ string "in") + <|> pure (Num n) + +term :: Parser CssElement +term = checkRgb <$> function + <|> (CssNumber <$> complexNumber) + <|> (CssString <$> str) + <|> (checkNamedColor <$> ident) + <|> (CssColor <$> colorParser) + where + comma = skipSpace *> char ',' <* skipSpace + checkNamedColor n + | Just c <- M.lookup n svgNamedColors = CssColor c + | otherwise = CssIdent n + + ref = char '#' *> ident + + checkRgb (CssFunction "rgb" + [CssNumber r, CssNumber g, CssNumber b]) = + CssColor $ PixelRGBA8 (to r) (to g) (to b) 255 + where clamp = max 0 . min 255 + to (Num n) = floor $ clamp n + to (Px n) = floor $ clamp n + to (Percent p) = floor . clamp $ p * 255 + to (Em c) = floor $ clamp c + to (Pc n) = floor $ clamp n + to (Mm n) = floor $ clamp n + to (Cm n) = floor $ clamp n + to (Point n) = floor $ clamp n + to (Inches n) = floor $ clamp n + + checkRgb a = a + functionParam = (CssReference <$> ref) <|> term + + function = CssFunction + <$> ident <* char '(' + <*> (functionParam `sepBy` comma) <* char ')' <* skipSpace + +-- | Parse CSS text into rules. +cssRulesOfText :: T.Text -> [CssRule] +cssRulesOfText txt = case parseOnly (many1 ruleSet) $ txt of + Left _ -> [] + Right rules -> rules +
src/Graphics/Svg/CssTypes.hs view
@@ -1,271 +1,271 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}--- | Define the types used to describes CSS elements-module Graphics.Svg.CssTypes- ( CssSelector( .. )- , CssSelectorRule- , CssRule( .. )- , CssDescriptor( .. )- , CssDeclaration( .. )- , CssElement( .. )-- , CssMatcheable( .. )- , CssContext- , Dpi- , Number( .. )- , serializeNumber- , findMatchingDeclarations- , toUserUnit- , mapNumber- , tserialize- ) where--#if !MIN_VERSION_base(4,8,0)-import Data.Monoid( mconcat )-#endif--import Data.Monoid( (<>) )-import Data.List( intersperse )-import qualified Data.Text as T-import qualified Data.Text.Lazy.Builder as TB-import Text.Printf--import Codec.Picture( PixelRGBA8( .. ) )---- | Alias describing a "dot per inch" information--- used for size calculation (see toUserUnit).-type Dpi = Int---- | Helper typeclass for serialization to Text.-class TextBuildable a where- -- | Serialize an element to a text builder.- tserialize :: a -> TB.Builder---- | Describe an element of a CSS selector. Multiple--- elements can be combined in a CssSelector type.-data CssDescriptor- = OfClass T.Text -- ^ .IDENT- | OfName T.Text -- ^ IDENT- | OfId T.Text -- ^ #IDENT- | OfPseudoClass T.Text -- ^ `:IDENT` (ignore function syntax)- | AnyElem -- ^ '*'- | WithAttrib T.Text T.Text -- ^ ``- deriving (Eq, Show)--instance TextBuildable CssDescriptor where- tserialize d = case d of- OfClass c -> si '.' <> ft c- OfName n -> ft n- OfId i -> si '#' <> ft i- OfPseudoClass c -> si '#' <> ft c- AnyElem -> si '*'- WithAttrib a b -> mconcat [si '[', ft a, si '=', ft b, si ']']- where- ft = TB.fromText- si = TB.singleton---- | Define complex selector.-data CssSelector- = Nearby -- ^ Correspond to the `+` CSS selector.- | DirectChildren -- ^ Correspond to the `>` CSS selectro.- | AllOf [CssDescriptor] -- ^ Grouping construct, all the elements- -- of the list must be matched.- deriving (Eq, Show)--instance TextBuildable CssSelector where- tserialize s = case s of- Nearby -> si '+'- DirectChildren -> si '>'- AllOf lst -> mconcat $ map tserialize lst- where- si = TB.singleton---- | A CssSelectorRule is a list of all the elements--- that must be meet in a depth first search fashion.-type CssSelectorRule = [CssSelector]---- | Represent a CSS selector and the different declarations--- to apply to the matched elemens.-data CssRule = CssRule- { -- | At the first level represent a list of elements- -- to be matched. If any match is made, you can apply- -- the declarations. At the second level- cssRuleSelector :: ![CssSelectorRule]- -- | Declarations to apply to the matched element.- , cssDeclarations :: ![CssDeclaration]- }- deriving (Eq, Show)--instance TextBuildable CssRule where- tserialize (CssRule selectors decl) =- mconcat tselectors- <> ft " {\n"- <> mconcat (fmap tserializeDecl decl)- <> ft "}\n"- where- ft = TB.fromText- tserializeDecl d = ft " " <> tserialize d <> ft ";\n"- tselector =- mconcat . intersperse (ft " ") . fmap tserialize- tselectors = - intersperse (ft ",\n") $ fmap tselector selectors---- | Interface for elements to be matched against--- some CssRule.-class CssMatcheable a where- -- | For an element, tell its optional ID attribute.- cssIdOf :: a -> Maybe T.Text- -- | For an element, return all of it's class attributes.- cssClassOf :: a -> [T.Text]- -- | Return the name of the tagname of the element- cssNameOf :: a -> T.Text- -- | Return a value of a given attribute if present- cssAttribOf :: a -> T.Text -> Maybe T.Text---- | Represent a zipper in depth at the first list--- level, and the previous nodes at in the second--- list level.-type CssContext a = [[a]]--isDescribedBy :: CssMatcheable a- => a -> [CssDescriptor] -> Bool-isDescribedBy e = all tryMatch- where- tryMatch (OfClass t) = t `elem` cssClassOf e- tryMatch (OfId i) = cssIdOf e == Just i- tryMatch (OfName n) = cssNameOf e == n- tryMatch (OfPseudoClass _) = False- tryMatch (WithAttrib a v) = cssAttribOf e a == Just v- tryMatch AnyElem = True--isMatching :: CssMatcheable a- => CssContext a -> [CssSelector] -> Bool-isMatching = go where- go _ [] = True- go [] _ = False- go ((_ : near):upper) (Nearby : rest) = go (near:upper) rest- go ((e:_):upper) (DirectChildren:AllOf descr:rest)- | isDescribedBy e descr = go upper rest- go _ (DirectChildren:_) = False- go ((e:_):upper) selectors@(AllOf descr : rest)- | isDescribedBy e descr = go upper rest- | otherwise = go upper selectors- go (_:upper) selector = go upper selector---- | Given CSS rules, find all the declaration to apply to the--- element in a given context.-findMatchingDeclarations :: CssMatcheable a- => [CssRule] -> CssContext a -> [CssDeclaration]-findMatchingDeclarations rules context =- concat [cssDeclarations rule- | rule <- rules- , selector <- cssRuleSelector rule- , isMatching context $ reverse selector ]---- | Represent the content to apply to some--- CSS matched rules.-data CssDeclaration = CssDeclaration- { -- | Property name to change (like font-family or color).- _cssDeclarationProperty :: T.Text- -- | List of values- , _cssDecarationlValues :: [[CssElement]]- }- deriving (Eq, Show)--instance TextBuildable CssDeclaration where- tserialize (CssDeclaration n elems) =- mconcat $ ft n : ft ": " : intersperse (si ' ') finalElems- where- finalElems = map tserialize (concat elems)- ft = TB.fromText- si = TB.singleton----- | Encode complex number possibly dependant to the current--- render size.-data Number- = Num Double -- ^ Simple coordinate in current user coordinate.- | Px Double -- ^ With suffix "px"- | Em Double -- ^ Number relative to the current font size.- | Percent Double -- ^ Number relative to the current viewport size.- | Pc Double- | Mm Double -- ^ Number in millimeters, relative to DPI.- | Cm Double -- ^ Number in centimeters, relative to DPI.- | Point Double -- ^ Number in points, relative to DPI.- | Inches Double -- ^ Number in inches, relative to DPI.- deriving (Eq, Show)---- | Helper function to modify inner value of a number-mapNumber :: (Double -> Double) -> Number -> Number-mapNumber f nu = case nu of- Num n -> Num $ f n- Px n -> Px $ f n- Em n -> Em $ f n- Percent n -> Percent $ f n- Pc n -> Pc $ f n- Mm n -> Mm $ f n- Cm n -> Cm $ f n- Point n -> Point $ f n- Inches n -> Inches $ f n---- | Encode the number to string which can be used in a--- CSS or a svg attributes.-serializeNumber :: Number -> String-serializeNumber n = case n of- Num c -> printf "%g" c- Px c -> printf "%gpx" c- Em cc -> printf "%gem" cc- Percent p -> printf "%d%%" (floor $ 100 * p :: Int)- Pc p -> printf "%gpc" p- Mm m -> printf "%gmm" m- Cm c -> printf "%gcm" c- Point p -> printf "%gpt" p- Inches i -> printf "%gin" i--instance TextBuildable Number where- tserialize = TB.fromText . T.pack . serializeNumber---- | Value of a CSS property.-data CssElement- = CssIdent !T.Text- | CssString !T.Text- | CssReference !T.Text- | CssNumber !Number- | CssColor !PixelRGBA8- | CssFunction !T.Text ![CssElement]- | CssOpComa- | CssOpSlash- deriving (Eq, Show)--instance TextBuildable CssElement where- tserialize e = case e of- CssIdent n -> ft n- CssString s -> si '"' <> ft s <> si '"'- CssReference r -> si '#' <> ft r- CssNumber n -> tserialize n- CssColor (PixelRGBA8 r g b _) ->- ft . T.pack $ printf "#%02X%02X%02X" r g b- CssFunction t els -> mconcat $ ft t : si '(' : args ++ [si ')']- where args = intersperse (ft ", ") (map tserialize els)- CssOpComa -> si ','- CssOpSlash -> si '/'- where- ft = TB.fromText- si = TB.singleton---- | This function replace all device dependant units to user--- units given it's DPI configuration.--- Preserve percentage and "em" notation.-toUserUnit :: Dpi -> Number -> Number-toUserUnit dpi = go where- go nu = case nu of- Num _ -> nu- Px p -> go $ Num p- Em _ -> nu- Percent _ -> nu- Pc n -> go . Inches $ (12 * n) / 72- Inches n -> Num $ n * fromIntegral dpi- Mm n -> go . Inches $ n / 25.4- Cm n -> go . Inches $ n / 2.54- Point n -> go . Inches $ n / 72-+{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE CPP #-} +-- | Define the types used to describes CSS elements +module Graphics.Svg.CssTypes + ( CssSelector( .. ) + , CssSelectorRule + , CssRule( .. ) + , CssDescriptor( .. ) + , CssDeclaration( .. ) + , CssElement( .. ) + + , CssMatcheable( .. ) + , CssContext + , Dpi + , Number( .. ) + , serializeNumber + , findMatchingDeclarations + , toUserUnit + , mapNumber + , tserialize + ) where + +#if !MIN_VERSION_base(4,8,0) +import Data.Monoid( mconcat ) +#endif + +import Data.Monoid( (<>) ) +import Data.List( intersperse ) +import qualified Data.Text as T +import qualified Data.Text.Lazy.Builder as TB +import Text.Printf + +import Codec.Picture( PixelRGBA8( .. ) ) + +-- | Alias describing a "dot per inch" information +-- used for size calculation (see toUserUnit). +type Dpi = Int + +-- | Helper typeclass for serialization to Text. +class TextBuildable a where + -- | Serialize an element to a text builder. + tserialize :: a -> TB.Builder + +-- | Describe an element of a CSS selector. Multiple +-- elements can be combined in a CssSelector type. +data CssDescriptor + = OfClass T.Text -- ^ .IDENT + | OfName T.Text -- ^ IDENT + | OfId T.Text -- ^ #IDENT + | OfPseudoClass T.Text -- ^ `:IDENT` (ignore function syntax) + | AnyElem -- ^ '*' + | WithAttrib T.Text T.Text -- ^ `` + deriving (Eq, Show) + +instance TextBuildable CssDescriptor where + tserialize d = case d of + OfClass c -> si '.' <> ft c + OfName n -> ft n + OfId i -> si '#' <> ft i + OfPseudoClass c -> si '#' <> ft c + AnyElem -> si '*' + WithAttrib a b -> mconcat [si '[', ft a, si '=', ft b, si ']'] + where + ft = TB.fromText + si = TB.singleton + +-- | Define complex selector. +data CssSelector + = Nearby -- ^ Correspond to the `+` CSS selector. + | DirectChildren -- ^ Correspond to the `>` CSS selectro. + | AllOf [CssDescriptor] -- ^ Grouping construct, all the elements + -- of the list must be matched. + deriving (Eq, Show) + +instance TextBuildable CssSelector where + tserialize s = case s of + Nearby -> si '+' + DirectChildren -> si '>' + AllOf lst -> mconcat $ map tserialize lst + where + si = TB.singleton + +-- | A CssSelectorRule is a list of all the elements +-- that must be meet in a depth first search fashion. +type CssSelectorRule = [CssSelector] + +-- | Represent a CSS selector and the different declarations +-- to apply to the matched elemens. +data CssRule = CssRule + { -- | At the first level represent a list of elements + -- to be matched. If any match is made, you can apply + -- the declarations. At the second level + cssRuleSelector :: ![CssSelectorRule] + -- | Declarations to apply to the matched element. + , cssDeclarations :: ![CssDeclaration] + } + deriving (Eq, Show) + +instance TextBuildable CssRule where + tserialize (CssRule selectors decl) = + mconcat tselectors + <> ft " {\n" + <> mconcat (fmap tserializeDecl decl) + <> ft "}\n" + where + ft = TB.fromText + tserializeDecl d = ft " " <> tserialize d <> ft ";\n" + tselector = + mconcat . intersperse (ft " ") . fmap tserialize + tselectors = + intersperse (ft ",\n") $ fmap tselector selectors + +-- | Interface for elements to be matched against +-- some CssRule. +class CssMatcheable a where + -- | For an element, tell its optional ID attribute. + cssIdOf :: a -> Maybe T.Text + -- | For an element, return all of it's class attributes. + cssClassOf :: a -> [T.Text] + -- | Return the name of the tagname of the element + cssNameOf :: a -> T.Text + -- | Return a value of a given attribute if present + cssAttribOf :: a -> T.Text -> Maybe T.Text + +-- | Represent a zipper in depth at the first list +-- level, and the previous nodes at in the second +-- list level. +type CssContext a = [[a]] + +isDescribedBy :: CssMatcheable a + => a -> [CssDescriptor] -> Bool +isDescribedBy e = all tryMatch + where + tryMatch (OfClass t) = t `elem` cssClassOf e + tryMatch (OfId i) = cssIdOf e == Just i + tryMatch (OfName n) = cssNameOf e == n + tryMatch (OfPseudoClass _) = False + tryMatch (WithAttrib a v) = cssAttribOf e a == Just v + tryMatch AnyElem = True + +isMatching :: CssMatcheable a + => CssContext a -> [CssSelector] -> Bool +isMatching = go where + go _ [] = True + go [] _ = False + go ((_ : near):upper) (Nearby : rest) = go (near:upper) rest + go ((e:_):upper) (DirectChildren:AllOf descr:rest) + | isDescribedBy e descr = go upper rest + go _ (DirectChildren:_) = False + go ((e:_):upper) selectors@(AllOf descr : rest) + | isDescribedBy e descr = go upper rest + | otherwise = go upper selectors + go (_:upper) selector = go upper selector + +-- | Given CSS rules, find all the declaration to apply to the +-- element in a given context. +findMatchingDeclarations :: CssMatcheable a + => [CssRule] -> CssContext a -> [CssDeclaration] +findMatchingDeclarations rules context = + concat [cssDeclarations rule + | rule <- rules + , selector <- cssRuleSelector rule + , isMatching context $ reverse selector ] + +-- | Represent the content to apply to some +-- CSS matched rules. +data CssDeclaration = CssDeclaration + { -- | Property name to change (like font-family or color). + _cssDeclarationProperty :: T.Text + -- | List of values + , _cssDecarationlValues :: [[CssElement]] + } + deriving (Eq, Show) + +instance TextBuildable CssDeclaration where + tserialize (CssDeclaration n elems) = + mconcat $ ft n : ft ": " : intersperse (si ' ') finalElems + where + finalElems = map tserialize (concat elems) + ft = TB.fromText + si = TB.singleton + + +-- | Encode complex number possibly dependant to the current +-- render size. +data Number + = Num Double -- ^ Simple coordinate in current user coordinate. + | Px Double -- ^ With suffix "px" + | Em Double -- ^ Number relative to the current font size. + | Percent Double -- ^ Number relative to the current viewport size. + | Pc Double + | Mm Double -- ^ Number in millimeters, relative to DPI. + | Cm Double -- ^ Number in centimeters, relative to DPI. + | Point Double -- ^ Number in points, relative to DPI. + | Inches Double -- ^ Number in inches, relative to DPI. + deriving (Eq, Show) + +-- | Helper function to modify inner value of a number +mapNumber :: (Double -> Double) -> Number -> Number +mapNumber f nu = case nu of + Num n -> Num $ f n + Px n -> Px $ f n + Em n -> Em $ f n + Percent n -> Percent $ f n + Pc n -> Pc $ f n + Mm n -> Mm $ f n + Cm n -> Cm $ f n + Point n -> Point $ f n + Inches n -> Inches $ f n + +-- | Encode the number to string which can be used in a +-- CSS or a svg attributes. +serializeNumber :: Number -> String +serializeNumber n = case n of + Num c -> printf "%g" c + Px c -> printf "%gpx" c + Em cc -> printf "%gem" cc + Percent p -> printf "%d%%" (floor $ 100 * p :: Int) + Pc p -> printf "%gpc" p + Mm m -> printf "%gmm" m + Cm c -> printf "%gcm" c + Point p -> printf "%gpt" p + Inches i -> printf "%gin" i + +instance TextBuildable Number where + tserialize = TB.fromText . T.pack . serializeNumber + +-- | Value of a CSS property. +data CssElement + = CssIdent !T.Text + | CssString !T.Text + | CssReference !T.Text + | CssNumber !Number + | CssColor !PixelRGBA8 + | CssFunction !T.Text ![CssElement] + | CssOpComa + | CssOpSlash + deriving (Eq, Show) + +instance TextBuildable CssElement where + tserialize e = case e of + CssIdent n -> ft n + CssString s -> si '"' <> ft s <> si '"' + CssReference r -> si '#' <> ft r + CssNumber n -> tserialize n + CssColor (PixelRGBA8 r g b _) -> + ft . T.pack $ printf "#%02X%02X%02X" r g b + CssFunction t els -> mconcat $ ft t : si '(' : args ++ [si ')'] + where args = intersperse (ft ", ") (map tserialize els) + CssOpComa -> si ',' + CssOpSlash -> si '/' + where + ft = TB.fromText + si = TB.singleton + +-- | This function replace all device dependant units to user +-- units given it's DPI configuration. +-- Preserve percentage and "em" notation. +toUserUnit :: Dpi -> Number -> Number +toUserUnit dpi = go where + go nu = case nu of + Num _ -> nu + Px p -> go $ Num p + Em _ -> nu + Percent _ -> nu + Pc n -> go . Inches $ (12 * n) / 72 + Inches n -> Num $ n * fromIntegral dpi + Mm n -> go . Inches $ n / 25.4 + Cm n -> go . Inches $ n / 2.54 + Point n -> go . Inches $ n / 72 +
src/Graphics/Svg/PathParser.hs view
@@ -1,226 +1,231 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}-module Graphics.Svg.PathParser( transformParser- , command- , viewBoxParser- , pointData- , serializePoints- , serializeCommand- , serializeCommands- , serializeViewBox- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative( (<*>), (<*), (*>), (<$>), (<$) )-#endif--import Control.Applicative( (<|>) )-import Data.Scientific( toRealFloat )-import Data.Attoparsec.Text- ( Parser- , scientific- , string- , skipSpace- , char- )-import Data.Attoparsec.Combinator( option- , sepBy- , sepBy1 )--import Linear hiding ( angle, point )-import Graphics.Svg.Types-import qualified Data.Text as T-import Text.Printf( printf )--num :: Parser Double-num = realToFrac <$> (skipSpace *> plusMinus <* skipSpace)- where doubleNumber :: Parser Double- doubleNumber = toRealFloat <$> scientific-- plusMinus = negate <$ string "-" <*> doubleNumber- <|> string "+" *> doubleNumber- <|> doubleNumber--viewBoxParser :: Parser (Double, Double, Double, Double)-viewBoxParser = (,,,)- <$> iParse <*> iParse <*> iParse <*> iParse- where- iParse = num <* skipSpace--serializeViewBox :: (Double, Double, Double, Double) -> String-serializeViewBox (a, b, c, d) = printf "%g %g %g %g" a b c d--commaWsp :: Parser ()-commaWsp = skipSpace *> option () (string "," *> return ()) <* skipSpace--point :: Parser RPoint-point = V2 <$> num <* commaWsp <*> num--pointData :: Parser [RPoint]-pointData = point `sepBy` commaWsp--command :: Parser PathCommand-command = (MoveTo OriginAbsolute <$ string "M" <*> pointList)- <|> (MoveTo OriginRelative <$ string "m" <*> pointList)- <|> (LineTo OriginAbsolute <$ string "L" <*> pointList)- <|> (LineTo OriginRelative <$ string "l" <*> pointList)- <|> (HorizontalTo OriginAbsolute <$ string "H" <*> coordList)- <|> (HorizontalTo OriginRelative <$ string "h" <*> coordList)- <|> (VerticalTo OriginAbsolute <$ string "V" <*> coordList)- <|> (VerticalTo OriginRelative <$ string "v" <*> coordList)- <|> (CurveTo OriginAbsolute <$ string "C" <*> manyComma curveToArgs)- <|> (CurveTo OriginRelative <$ string "c" <*> manyComma curveToArgs)- <|> (SmoothCurveTo OriginAbsolute <$ string "S" <*> pointPairList)- <|> (SmoothCurveTo OriginRelative <$ string "s" <*> pointPairList)- <|> (QuadraticBezier OriginAbsolute <$ string "Q" <*> pointPairList)- <|> (QuadraticBezier OriginRelative <$ string "q" <*> pointPairList)- <|> (SmoothQuadraticBezierCurveTo OriginAbsolute <$ string "T" <*> pointList)- <|> (SmoothQuadraticBezierCurveTo OriginRelative <$ string "t" <*> pointList)- <|> (EllipticalArc OriginAbsolute <$ string "A" <*> manyComma ellipticalArgs)- <|> (EllipticalArc OriginRelative <$ string "a" <*> manyComma ellipticalArgs)- <|> (EndPath <$ string "Z" <* commaWsp)- <|> (EndPath <$ string "z" <* commaWsp)- where pointList = point `sepBy1` commaWsp- pointPair = (,) <$> point <* commaWsp <*> point- pointPairList = pointPair `sepBy1` commaWsp- coordList = num `sepBy1` commaWsp- curveToArgs = (,,) <$> (point <* commaWsp)- <*> (point <* commaWsp)- <*> point- manyComma a = a `sepBy1` commaWsp-- numComma = num <* commaWsp- ellipticalArgs = (,,,,,) <$> numComma- <*> numComma- <*> numComma- <*> (fmap (/= 0) numComma)- <*> (fmap (/= 0) numComma)- <*> point--serializePoint :: RPoint -> String-serializePoint (V2 x y) = printf "%g,%g" x y--serializePoints :: [RPoint] -> String-serializePoints = unwords . fmap serializePoint--serializeCoords :: [Coord] -> String-serializeCoords = unwords . fmap (printf "%g")--serializePointPair :: (RPoint, RPoint) -> String-serializePointPair (a, b) = serializePoint a ++ " " ++ serializePoint b--serializePointPairs :: [(RPoint, RPoint)] -> String-serializePointPairs = unwords . fmap serializePointPair--serializePointTriplet :: (RPoint, RPoint, RPoint) -> String-serializePointTriplet (a, b, c) =- serializePoint a ++ " " ++ serializePoint b ++ " " ++ serializePoint c--serializePointTriplets :: [(RPoint, RPoint, RPoint)] -> String-serializePointTriplets = unwords . fmap serializePointTriplet--serializeCommands :: [PathCommand] -> String-serializeCommands = unwords . fmap serializeCommand--serializeCommand :: PathCommand -> String-serializeCommand p = case p of- MoveTo OriginAbsolute points -> "M" ++ serializePoints points- MoveTo OriginRelative points -> "m" ++ serializePoints points- LineTo OriginAbsolute points -> "L" ++ serializePoints points- LineTo OriginRelative points -> "l" ++ serializePoints points-- HorizontalTo OriginAbsolute coords -> "H" ++ serializeCoords coords- HorizontalTo OriginRelative coords -> "h" ++ serializeCoords coords- VerticalTo OriginAbsolute coords -> "V" ++ serializeCoords coords- VerticalTo OriginRelative coords -> "v" ++ serializeCoords coords-- CurveTo OriginAbsolute triplets -> "C" ++ serializePointTriplets triplets- CurveTo OriginRelative triplets -> "c" ++ serializePointTriplets triplets- SmoothCurveTo OriginAbsolute pointPairs -> "S" ++ serializePointPairs pointPairs- SmoothCurveTo OriginRelative pointPairs -> "s" ++ serializePointPairs pointPairs- QuadraticBezier OriginAbsolute pointPairs -> "Q" ++ serializePointPairs pointPairs- QuadraticBezier OriginRelative pointPairs -> "q" ++ serializePointPairs pointPairs- SmoothQuadraticBezierCurveTo OriginAbsolute points -> "T" ++ serializePoints points- SmoothQuadraticBezierCurveTo OriginRelative points -> "t" ++ serializePoints points- EllipticalArc OriginAbsolute args -> "A" ++ serializeArgs args- EllipticalArc OriginRelative args -> "a" ++ serializeArgs args- EndPath -> "Z"- where- serializeArg (a, b, c, d, e, V2 x y) =- printf "%g %g %g %d %d %g,%g" a b c (fromEnum d) (fromEnum e) x y- serializeArgs = unwords . fmap serializeArg----transformParser :: Parser Transformation-transformParser = matrixParser- <|> translationParser- <|> scaleParser- <|> rotateParser- <|> skewYParser- <|> skewXParser--functionParser :: T.Text -> Parser [Double]-functionParser funcName =- string funcName *> skipSpace- *> char '(' *> skipSpace- *> num `sepBy1` commaWsp- <* skipSpace <* char ')' <* skipSpace--translationParser :: Parser Transformation-translationParser = do- args <- functionParser "translate"- return $ case args of- [x] -> Translate x 0- [x, y] -> Translate x y- _ -> TransformUnknown--skewXParser :: Parser Transformation-skewXParser = do- args <- functionParser "skewX"- return $ case args of- [x] -> SkewX x- _ -> TransformUnknown--skewYParser :: Parser Transformation-skewYParser = do- args <- functionParser "skewY"- return $ case args of- [x] -> SkewY x- _ -> TransformUnknown---scaleParser :: Parser Transformation-scaleParser = do- args <- functionParser "scale"- return $ case args of- [x] -> Scale x Nothing- [x, y] -> Scale x (Just y)- _ -> TransformUnknown--matrixParser :: Parser Transformation-matrixParser = do- args <- functionParser "matrix"- return $ case args of- [a, b, c, d, e, f] ->- TransformMatrix a b c d e f- _ -> TransformUnknown--rotateParser :: Parser Transformation-rotateParser = do- args <- functionParser "rotate"- return $ case args of- [angle] -> Rotate angle Nothing- [angle, x, y] -> Rotate angle $ Just (x, y)- _ -> TransformUnknown-{--rotate(<rotate-angle> [<cx> <cy>]), which specifies a rotation by <rotate-angle> degrees about a given point.--If optional parameters <cx> and <cy> are not supplied, the rotation is about the origin of the current user coordinate system. The operation corresponds to the matrix [cos(a) sin(a) -sin(a) cos(a) 0 0].--If optional parameters <cx> and <cy> are supplied, the rotation is about the point (cx, cy). The operation represents the equivalent of the following specification: translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>).--skewX(<skew-angle>), which specifies a skew transformation along the x-axis.--skewY(<skew-angle>), which specifies a skew transformation along the y-axis.- -}+{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE CPP #-} +module Graphics.Svg.PathParser( transformParser + , command + , pathParser + , viewBoxParser + , pointData + , serializePoints + , serializeCommand + , serializeCommands + , serializeViewBox + ) where + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative( (<*>), (<*), (*>), (<$>), (<$) ) +#endif + +import Control.Applicative( (<|>) ) +import Data.Scientific( toRealFloat ) +import Data.Attoparsec.Text + ( Parser + , scientific + , string + , skipSpace + , char + , many1 + ) +import Data.Attoparsec.Combinator( option + , sepBy + , sepBy1 ) + +import Linear hiding ( angle, point ) +import Graphics.Svg.Types +import qualified Data.Text as T +import Text.Printf( printf ) + +num :: Parser Double +num = realToFrac <$> (skipSpace *> plusMinus <* skipSpace) + where doubleNumber :: Parser Double + doubleNumber = toRealFloat <$> scientific + + plusMinus = negate <$ string "-" <*> doubleNumber + <|> string "+" *> doubleNumber + <|> doubleNumber + +viewBoxParser :: Parser (Double, Double, Double, Double) +viewBoxParser = (,,,) + <$> iParse <*> iParse <*> iParse <*> iParse + where + iParse = num <* skipSpace + +serializeViewBox :: (Double, Double, Double, Double) -> String +serializeViewBox (a, b, c, d) = printf "%g %g %g %g" a b c d + +commaWsp :: Parser () +commaWsp = skipSpace *> option () (string "," *> return ()) <* skipSpace + +point :: Parser RPoint +point = V2 <$> num <* commaWsp <*> num + +pointData :: Parser [RPoint] +pointData = point `sepBy` commaWsp + +pathParser :: Parser [PathCommand] +pathParser = skipSpace *> many1 command + +command :: Parser PathCommand +command = (MoveTo OriginAbsolute <$ string "M" <*> pointList) + <|> (MoveTo OriginRelative <$ string "m" <*> pointList) + <|> (LineTo OriginAbsolute <$ string "L" <*> pointList) + <|> (LineTo OriginRelative <$ string "l" <*> pointList) + <|> (HorizontalTo OriginAbsolute <$ string "H" <*> coordList) + <|> (HorizontalTo OriginRelative <$ string "h" <*> coordList) + <|> (VerticalTo OriginAbsolute <$ string "V" <*> coordList) + <|> (VerticalTo OriginRelative <$ string "v" <*> coordList) + <|> (CurveTo OriginAbsolute <$ string "C" <*> manyComma curveToArgs) + <|> (CurveTo OriginRelative <$ string "c" <*> manyComma curveToArgs) + <|> (SmoothCurveTo OriginAbsolute <$ string "S" <*> pointPairList) + <|> (SmoothCurveTo OriginRelative <$ string "s" <*> pointPairList) + <|> (QuadraticBezier OriginAbsolute <$ string "Q" <*> pointPairList) + <|> (QuadraticBezier OriginRelative <$ string "q" <*> pointPairList) + <|> (SmoothQuadraticBezierCurveTo OriginAbsolute <$ string "T" <*> pointList) + <|> (SmoothQuadraticBezierCurveTo OriginRelative <$ string "t" <*> pointList) + <|> (EllipticalArc OriginAbsolute <$ string "A" <*> manyComma ellipticalArgs) + <|> (EllipticalArc OriginRelative <$ string "a" <*> manyComma ellipticalArgs) + <|> (EndPath <$ string "Z" <* commaWsp) + <|> (EndPath <$ string "z" <* commaWsp) + where pointList = point `sepBy1` commaWsp + pointPair = (,) <$> point <* commaWsp <*> point + pointPairList = pointPair `sepBy1` commaWsp + coordList = num `sepBy1` commaWsp + curveToArgs = (,,) <$> (point <* commaWsp) + <*> (point <* commaWsp) + <*> point + manyComma a = a `sepBy1` commaWsp + + numComma = num <* commaWsp + ellipticalArgs = (,,,,,) <$> numComma + <*> numComma + <*> numComma + <*> (fmap (/= 0) numComma) + <*> (fmap (/= 0) numComma) + <*> point + +serializePoint :: RPoint -> String +serializePoint (V2 x y) = printf "%g,%g" x y + +serializePoints :: [RPoint] -> String +serializePoints = unwords . fmap serializePoint + +serializeCoords :: [Coord] -> String +serializeCoords = unwords . fmap (printf "%g") + +serializePointPair :: (RPoint, RPoint) -> String +serializePointPair (a, b) = serializePoint a ++ " " ++ serializePoint b + +serializePointPairs :: [(RPoint, RPoint)] -> String +serializePointPairs = unwords . fmap serializePointPair + +serializePointTriplet :: (RPoint, RPoint, RPoint) -> String +serializePointTriplet (a, b, c) = + serializePoint a ++ " " ++ serializePoint b ++ " " ++ serializePoint c + +serializePointTriplets :: [(RPoint, RPoint, RPoint)] -> String +serializePointTriplets = unwords . fmap serializePointTriplet + +serializeCommands :: [PathCommand] -> String +serializeCommands = unwords . fmap serializeCommand + +serializeCommand :: PathCommand -> String +serializeCommand p = case p of + MoveTo OriginAbsolute points -> "M" ++ serializePoints points + MoveTo OriginRelative points -> "m" ++ serializePoints points + LineTo OriginAbsolute points -> "L" ++ serializePoints points + LineTo OriginRelative points -> "l" ++ serializePoints points + + HorizontalTo OriginAbsolute coords -> "H" ++ serializeCoords coords + HorizontalTo OriginRelative coords -> "h" ++ serializeCoords coords + VerticalTo OriginAbsolute coords -> "V" ++ serializeCoords coords + VerticalTo OriginRelative coords -> "v" ++ serializeCoords coords + + CurveTo OriginAbsolute triplets -> "C" ++ serializePointTriplets triplets + CurveTo OriginRelative triplets -> "c" ++ serializePointTriplets triplets + SmoothCurveTo OriginAbsolute pointPairs -> "S" ++ serializePointPairs pointPairs + SmoothCurveTo OriginRelative pointPairs -> "s" ++ serializePointPairs pointPairs + QuadraticBezier OriginAbsolute pointPairs -> "Q" ++ serializePointPairs pointPairs + QuadraticBezier OriginRelative pointPairs -> "q" ++ serializePointPairs pointPairs + SmoothQuadraticBezierCurveTo OriginAbsolute points -> "T" ++ serializePoints points + SmoothQuadraticBezierCurveTo OriginRelative points -> "t" ++ serializePoints points + EllipticalArc OriginAbsolute args -> "A" ++ serializeArgs args + EllipticalArc OriginRelative args -> "a" ++ serializeArgs args + EndPath -> "Z" + where + serializeArg (a, b, c, d, e, V2 x y) = + printf "%g %g %g %d %d %g,%g" a b c (fromEnum d) (fromEnum e) x y + serializeArgs = unwords . fmap serializeArg + + + +transformParser :: Parser Transformation +transformParser = matrixParser + <|> translationParser + <|> scaleParser + <|> rotateParser + <|> skewYParser + <|> skewXParser + +functionParser :: T.Text -> Parser [Double] +functionParser funcName = + string funcName *> skipSpace + *> char '(' *> skipSpace + *> num `sepBy1` commaWsp + <* skipSpace <* char ')' <* skipSpace + +translationParser :: Parser Transformation +translationParser = do + args <- functionParser "translate" + return $ case args of + [x] -> Translate x 0 + [x, y] -> Translate x y + _ -> TransformUnknown + +skewXParser :: Parser Transformation +skewXParser = do + args <- functionParser "skewX" + return $ case args of + [x] -> SkewX x + _ -> TransformUnknown + +skewYParser :: Parser Transformation +skewYParser = do + args <- functionParser "skewY" + return $ case args of + [x] -> SkewY x + _ -> TransformUnknown + + +scaleParser :: Parser Transformation +scaleParser = do + args <- functionParser "scale" + return $ case args of + [x] -> Scale x Nothing + [x, y] -> Scale x (Just y) + _ -> TransformUnknown + +matrixParser :: Parser Transformation +matrixParser = do + args <- functionParser "matrix" + return $ case args of + [a, b, c, d, e, f] -> + TransformMatrix a b c d e f + _ -> TransformUnknown + +rotateParser :: Parser Transformation +rotateParser = do + args <- functionParser "rotate" + return $ case args of + [angle] -> Rotate angle Nothing + [angle, x, y] -> Rotate angle $ Just (x, y) + _ -> TransformUnknown +{- +rotate(<rotate-angle> [<cx> <cy>]), which specifies a rotation by <rotate-angle> degrees about a given point. + +If optional parameters <cx> and <cy> are not supplied, the rotation is about the origin of the current user coordinate system. The operation corresponds to the matrix [cos(a) sin(a) -sin(a) cos(a) 0 0]. + +If optional parameters <cx> and <cy> are supplied, the rotation is about the point (cx, cy). The operation represents the equivalent of the following specification: translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>). + +skewX(<skew-angle>), which specifies a skew transformation along the x-axis. + +skewY(<skew-angle>), which specifies a skew transformation along the y-axis. + -}
src/Graphics/Svg/Types.hs view
@@ -1,1521 +1,1521 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE CPP #-}--- | This module define all the types used in the definition--- of a svg scene.------ Most of the types are lensified.-module Graphics.Svg.Types- ( -- * Basic building types- Coord- , Origin( .. )- , Point- , RPoint- , PathCommand( .. )- , Transformation( .. )- , ElementRef( .. )- , CoordinateUnits( .. )-- -- ** Building helpers- , toPoint- , serializeNumber- , serializeTransformation- , serializeTransformations-- -- * Drawing control types- , Cap( .. )- , LineJoin( .. )- , Tree( .. )- , Number( .. )- , Spread( .. )- , Texture( .. )- , Element( .. )- , FillRule( .. )- , FontStyle( .. )- , Dpi-- , WithDefaultSvg( .. )-- -- * Main type- , Document( .. )- , HasDocument( .. )- , documentSize-- -- * Drawing attributes- , DrawAttributes( .. )- , HasDrawAttributes( .. )- , WithDrawAttributes( .. )-- -- * SVG drawing primitives- -- ** Rectangle- , Rectangle( .. )- , HasRectangle( .. )-- -- ** Line- , Line( .. )- , HasLine( .. )-- -- ** Polygon- , Polygon( .. )- , HasPolygon( .. )-- -- ** Polyline- , PolyLine( .. )- , HasPolyLine( .. )-- -- ** Path- , Path( .. )- , HasPath( .. )-- -- ** Circle- , Circle( .. )- , HasCircle( .. )-- -- ** Ellipse- , Ellipse( .. )- , HasEllipse( .. )-- -- ** Image- , Image( .. )- , HasImage( .. )-- -- ** Use- , Use( .. )- , HasUse( .. )-- -- * Grouping primitives- -- ** Group- , Group( .. )- , HasGroup( .. )-- -- ** Symbol- , Symbol( .. )- , groupOfSymbol-- -- * Text related types- -- ** Text- , Text( .. )- , HasText( .. )- , TextAnchor( .. )- , textAt-- -- ** Text path- , TextPath( .. )- , HasTextPath( .. )-- , TextPathSpacing( .. )- , TextPathMethod( .. )-- -- ** Text span.- , TextSpanContent( .. )-- , TextSpan( .. )- , HasTextSpan( .. )-- , TextInfo( .. )- , HasTextInfo( .. )-- , TextAdjust( .. )-- -- * Marker definition- , Marker( .. )- , Overflow( .. )- , MarkerOrientation( .. )- , MarkerUnit( .. )- , HasMarker( .. )-- -- * Gradient definition- , GradientStop( .. )- , HasGradientStop( .. )-- -- ** Linear Gradient- , LinearGradient( .. )- , HasLinearGradient( .. )-- -- ** Radial Gradient- , RadialGradient( .. )- , HasRadialGradient( .. )-- -- * Pattern definition- , Pattern( .. )- , HasPattern( .. )-- -- * Mask definition- , Mask( .. )- , HasMask( .. )-- -- * Clip path definition- , ClipPath( .. )- , HasClipPath( .. )-- -- * Aspect Ratio description- , PreserveAspectRatio( .. )- , Alignment( .. )- , MeetSlice( .. )- , HasPreserveAspectRatio( .. )-- -- * MISC functions- , isPathArc- , isPathWithArc- , nameOfTree- , zipTree- , mapTree- , foldTree- , toUserUnit- , mapNumber- ) where--#if !MIN_VERSION_base(4,8,0)-import Data.Monoid( Monoid( .. ) )-import Data.Foldable( Foldable )-#endif--import Data.Function( on )-import Data.List( inits )-import qualified Data.Map as M-import Data.Monoid( Last( .. ), (<>) )-import qualified Data.Foldable as F-import qualified Data.Text as T-import Codec.Picture( PixelRGBA8( .. ) )-import Control.Lens( Lens'- , lens- , makeClassy- , makeLenses- , view- , (^.)- , (&)- , (.~)- )-import Graphics.Svg.CssTypes-import Linear hiding ( angle )--import Text.Printf---- | Basic coordiante type.-type Coord = Double---- | Real Point, fully determined and not--- dependant of the rendering context.-type RPoint = V2 Coord---- | Possibly context dependant point.-type Point = (Number, Number)---- | Tell if a path command is absolute (in the current--- user coordiante) or relative to the previous poitn.-data Origin- = OriginAbsolute -- ^ Next point in absolute coordinate- | OriginRelative -- ^ Next point relative to the previous- deriving (Eq, Show)---- | Path command definition.-data PathCommand- -- | 'M' or 'm' command- = MoveTo !Origin ![RPoint]- -- | Line to, 'L' or 'l' Svg path command.- | LineTo !Origin ![RPoint]-- -- | Equivalent to the 'H' or 'h' svg path command.- | HorizontalTo !Origin ![Coord]- -- | Equivalent to the 'V' or 'v' svg path command.- | VerticalTo !Origin ![Coord]-- -- | Cubic bezier, 'C' or 'c' command- | CurveTo !Origin ![(RPoint, RPoint, RPoint)]- -- | Smooth cubic bezier, equivalent to 'S' or 's' command- | SmoothCurveTo !Origin ![(RPoint, RPoint)]- -- | Quadratic bezier, 'Q' or 'q' command- | QuadraticBezier !Origin ![(RPoint, RPoint)]- -- | Quadratic bezier, 'T' or 't' command- | SmoothQuadraticBezierCurveTo !Origin ![RPoint]- -- | Eliptical arc, 'A' or 'a' command.- | EllipticalArc !Origin ![(Coord, Coord, Coord, Bool, Bool, RPoint)]- -- | Close the path, 'Z' or 'z' svg path command.- | EndPath- deriving (Eq, Show)---- | Little helper function to build a point.-toPoint :: Number -> Number -> Point-toPoint = (,)---- | Tell if the path command is an EllipticalArc.-isPathArc :: PathCommand -> Bool-isPathArc (EllipticalArc _ _) = True-isPathArc _ = False---- | Tell if a full path contain an EllipticalArc.-isPathWithArc :: Foldable f => f PathCommand -> Bool-isPathWithArc = F.any isPathArc---- | This type represent the align information of the--- preserveAspectRatio SVGattribute-data Alignment- = AlignNone -- ^ "none" value- | AlignxMinYMin -- "xMinYMin" value- | AlignxMidYMin -- ^ "xMidYMin" value- | AlignxMaxYMin -- ^ "xMaxYMin" value- | AlignxMinYMid -- ^ "xMinYMid" value- | AlignxMidYMid -- ^ "xMidYMid" value- | AlignxMaxYMid -- ^ "xMaxYMid" value- | AlignxMinYMax -- ^ "xMinYMax" value- | AlignxMidYMax -- ^ "xMidYMax" value- | AlignxMaxYMax -- ^ "xMaxYMax" value- deriving (Eq, Show)---- | This type represent the "meet or slice" information--- of the preserveAspectRatio SVGattribute-data MeetSlice = Meet | Slice- deriving (Eq, Show)---- | Describe the content of the preserveAspectRatio attribute.-data PreserveAspectRatio = PreserveAspectRatio- { _aspectRatioDefer :: !Bool- , _aspectRatioAlign :: !Alignment- , _aspectRatioMeetSlice :: !(Maybe MeetSlice)- }- deriving (Eq, Show)---- | Lenses for the PreserveAspectRatio type-makeClassy ''PreserveAspectRatio --instance WithDefaultSvg PreserveAspectRatio where- defaultSvg = PreserveAspectRatio - { _aspectRatioDefer = False- , _aspectRatioAlign = AlignxMidYMid- , _aspectRatioMeetSlice = Nothing- }---- | Describe how the line should be terminated--- when stroking them. Describe the values of the--- `stroke-linecap` attribute.--- See `_strokeLineCap`-data Cap- = CapRound -- ^ End with a round (`round` value)- | CapButt -- ^ Define straight just at the end (`butt` value)- | CapSquare -- ^ Straight further of the ends (`square` value)- deriving (Eq, Show)---- | Define the possible values of the `stroke-linejoin`--- attribute.--- see `_strokeLineJoin`-data LineJoin- = JoinMiter -- ^ `miter` value- | JoinBevel -- ^ `bevel` value- | JoinRound -- ^ `round` value- deriving (Eq, Show)---- | Describe the different value which can be used--- in the `fill` or `stroke` attributes.-data Texture- = ColorRef PixelRGBA8 -- ^ Direct solid color (#rrggbb, #rgb)- | TextureRef String -- ^ Link to a complex texture (url(#name))- | FillNone -- ^ Equivalent to the `none` value.- deriving (Eq, Show)---- | Describe the possile filling algorithms.--- Map the values of the `fill-rule` attributes.-data FillRule- = FillEvenOdd -- ^ Correspond to the `evenodd` value.- | FillNonZero -- ^ Correspond to the `nonzero` value.- deriving (Eq, Show)---- | Describe the content of the `transformation` attribute.--- see `_transform` and `transform`.-data Transformation- = -- | Directly encode the translation matrix.- TransformMatrix !Coord !Coord !Coord- !Coord !Coord !Coord- -- | Translation along a vector- | Translate !Double !Double- -- | Scaling on both axis or on X axis and Y axis.- | Scale !Double !(Maybe Double)- -- | Rotation around `(0, 0)` or around an optional- -- point.- | Rotate !Double !(Maybe (Double, Double))- -- | Skew transformation along the X axis.- | SkewX !Double- -- | Skew transformation along the Y axis.- | SkewY !Double- -- | Unkown transformation, like identity.- | TransformUnknown- deriving (Eq, Show)---- | Convert the Transformation to a string which can be--- directly used in a svg attributes.-serializeTransformation :: Transformation -> String-serializeTransformation t = case t of- TransformUnknown -> ""- TransformMatrix a b c d e f ->- printf "matrix(%g, %g, %g, %g, %g, %g)" a b c d e f- Translate x y -> printf "translate(%g, %g)" x y- Scale x Nothing -> printf "scale(%g)" x- Scale x (Just y) -> printf "scale(%g, %g)" x y- Rotate angle Nothing -> printf "rotate(%g)" angle- Rotate angle (Just (x, y))-> printf "rotate(%g, %g, %g)" angle x y- SkewX x -> printf "skewX(%g)" x- SkewY y -> printf "skewY(%g)" y---- | Transform a list of transformations to a string for svg--- `transform` attributes.-serializeTransformations :: [Transformation] -> String-serializeTransformations =- unwords . fmap serializeTransformation---- | Class helping find the drawing attributes for all--- the SVG attributes.-class WithDrawAttributes a where- -- | Lens which can be used to read/write primitives.- drawAttr :: Lens' a DrawAttributes---- | Define an empty 'default' element for the SVG tree.--- It is used as base when parsing the element from XML.-class WithDefaultSvg a where- -- | The default element.- defaultSvg :: a---- | Classify the font style, used to search a matching--- font in the FontCache.-data FontStyle- = FontStyleNormal- | FontStyleItalic- | FontStyleOblique- deriving (Eq, Show)---- | Tell where to anchor the text, where the position--- given is realative to the text.-data TextAnchor- -- | The text with left aligned, or start at the postion- -- If the point is the '*' then the text will be printed- -- this way:- --- -- > *THE_TEXT_TO_PRINT- --- -- Equivalent to the `start` value.- = TextAnchorStart- -- | The text is middle aligned, so the text will be at- -- the left and right of the position:- --- -- > THE_TEXT*TO_PRINT- --- -- Equivalent to the `middle` value.- | TextAnchorMiddle- -- | The text is right aligned.- --- -- > THE_TEXT_TO_PRINT*- --- -- Equivalent to the `end` value.- | TextAnchorEnd- deriving (Eq, Show)----- | Correspond to the possible values of the--- the attributes which are either `none` or--- `url(#elem)`-data ElementRef- = RefNone -- ^ Value for `none`- | Ref String -- ^ Equivalent to `url()` attribute.- deriving (Eq, Show)---- | This type define how to draw any primitives,--- which color to use, how to stroke the primitives--- and the potential transformations to use.------ All these attributes are propagated to the children.-data DrawAttributes = DrawAttributes- { -- | Attribute corresponding to the `stroke-width`- -- SVG attribute.- _strokeWidth :: !(Last Number)- -- | Correspond to the `stroke` attribute.- , _strokeColor :: !(Last Texture)- -- | Define the `stroke-opacity` attribute, the transparency- -- for the "border".- , _strokeOpacity :: !(Maybe Float)- -- | Correspond to the `stroke-linecap` SVG- -- attribute- , _strokeLineCap :: !(Last Cap)- -- | Correspond to the `stroke-linejoin` SVG- -- attribute- , _strokeLineJoin :: !(Last LineJoin)- -- | Define the distance of the miter join, correspond- -- to the `stroke-miterlimit` attritbue.- , _strokeMiterLimit :: !(Last Double)- -- | Define the filling color of the elements. Corresponding- -- to the `fill` attribute.- , _fillColor :: !(Last Texture)- -- | Define the `fill-opacity` attribute, the transparency- -- for the "content".- , _fillOpacity :: !(Maybe Float)- -- | Define the global or group opacity attribute.- , _groupOpacity :: !(Maybe Float)- -- | Content of the `transform` attribute- , _transform :: !(Maybe [Transformation])- -- | Define the `fill-rule` used during the rendering.- , _fillRule :: !(Last FillRule)- -- | Define the `mask` attribute.- , _maskRef :: !(Last ElementRef)- -- | Define the `clip-path` attribute.- , _clipPathRef :: !(Last ElementRef)- -- | Define the `clip-rule` attribute.- , _clipRule :: !(Last FillRule)- -- | Map to the `class` attribute. Used for the CSS- -- rewriting.- , _attrClass :: ![T.Text]- -- | Map to the `id` attribute. Used for the CSS- -- rewriting.- , _attrId :: !(Maybe String)- -- | Define the start distance of the dashing pattern.- -- Correspond to the `stroke-dashoffset` attribute.- , _strokeOffset :: !(Last Number)- -- | Define the dashing pattern for the lines. Correspond- -- to the `stroke-dasharray` attribute.- , _strokeDashArray :: !(Last [Number])- -- | Current size of the text, correspond to the- -- `font-size` SVG attribute.- , _fontSize :: !(Last Number)- -- | Define the possible fonts to be used for text rendering.- -- Map to the `font-family` attribute.- , _fontFamily :: !(Last [String])- -- | Map to the `font-style` attribute.- , _fontStyle :: !(Last FontStyle)- -- | Define how to interpret the text position, correspond- -- to the `text-anchor` attribute.- , _textAnchor :: !(Last TextAnchor)- -- | Define the marker used for the start of the line.- -- Correspond to the `marker-start` attribute.- , _markerStart :: !(Last ElementRef)- -- | Define the marker used for every point of the- -- polyline/path Correspond to the `marker-mid`- -- attribute.- , _markerMid :: !(Last ElementRef)- -- | Define the marker used for the end of the line.- -- Correspond to the `marker-end` attribute.- , _markerEnd :: !(Last ElementRef)- }- deriving (Eq, Show)---- | Lenses for the DrawAttributes type.-makeClassy ''DrawAttributes---- | This primitive describe an unclosed suite of--- segments. Correspond to the `<polyline>` tag.-data PolyLine = PolyLine- { -- | drawing attributes of the polyline.- _polyLineDrawAttributes :: !DrawAttributes-- -- | Geometry definition of the polyline.- -- correspond to the `points` attribute- , _polyLinePoints :: ![RPoint]- }- deriving (Eq, Show)---- | Lenses for the PolyLine type.-makeClassy ''PolyLine--instance WithDefaultSvg PolyLine where- defaultSvg = PolyLine- { _polyLineDrawAttributes = mempty- , _polyLinePoints = []- }---instance WithDrawAttributes PolyLine where- drawAttr = polyLineDrawAttributes---- | Primitive decriving polygon composed--- of segements. Correspond to the `<polygon>`--- tag-data Polygon = Polygon- { -- | Drawing attributes for the polygon.- _polygonDrawAttributes :: !DrawAttributes- -- | Points of the polygon. Correspond to- -- the `points` attributes.- , _polygonPoints :: ![RPoint]- }- deriving (Eq, Show)---- | Lenses for the Polygon type-makeClassy ''Polygon--instance WithDrawAttributes Polygon where- drawAttr = polygonDrawAttributes--instance WithDefaultSvg Polygon where- defaultSvg = Polygon- { _polygonDrawAttributes = mempty- , _polygonPoints = []- }---- | Define a simple line. Correspond to the--- `<line>` tag.-data Line = Line- { -- | Drawing attributes of line.- _lineDrawAttributes :: !DrawAttributes- -- | First point of the the line, correspond- -- to the `x1` and `y1` attributes.- , _linePoint1 :: !Point- -- | Second point of the the line, correspond- -- to the `x2` and `y2` attributes.- , _linePoint2 :: !Point- }- deriving (Eq, Show)---- | Lenses for the Line type.-makeClassy ''Line--instance WithDrawAttributes Line where- drawAttr = lineDrawAttributes--instance WithDefaultSvg Line where- defaultSvg = Line- { _lineDrawAttributes = mempty- , _linePoint1 = zeroPoint- , _linePoint2 = zeroPoint- }- where zeroPoint = (Num 0, Num 0)---- | Define a rectangle. Correspond to--- `<rectangle>` svg tag.-data Rectangle = Rectangle- { -- | Rectangle drawing attributes.- _rectDrawAttributes :: !DrawAttributes- -- | Upper left corner of the rectangle, correspond- -- to the attributes `x` and `y`.- , _rectUpperLeftCorner :: !Point- -- | Rectangle width, correspond, strangely, to- -- the `width` attribute.- , _rectWidth :: !Number- -- | Rectangle height, correspond, amazingly, to- -- the `height` attribute.- , _rectHeight :: !Number- -- | Define the rounded corner radius radius- -- of the rectangle. Correspond to the `rx` and- -- `ry` attributes.- , _rectCornerRadius :: !(Number, Number)- }- deriving (Eq, Show)---- | Lenses for the Rectangle type.-makeClassy ''Rectangle--instance WithDrawAttributes Rectangle where- drawAttr = rectDrawAttributes--instance WithDefaultSvg Rectangle where- defaultSvg = Rectangle- { _rectDrawAttributes = mempty- , _rectUpperLeftCorner = (Num 0, Num 0)- , _rectWidth = Num 0- , _rectHeight = Num 0- , _rectCornerRadius = (Num 0, Num 0)- }---- | Type mapping the `<path>` svg tag.-data Path = Path- { -- | Drawing attributes of the path.- _pathDrawAttributes :: !DrawAttributes- -- | Definition of the path, correspond to the- -- `d` attributes.- , _pathDefinition :: ![PathCommand]- }- deriving (Eq, Show)---- | Lenses for the Path type-makeClassy ''Path--instance WithDrawAttributes Path where- drawAttr = pathDrawAttributes--instance WithDefaultSvg Path where- defaultSvg = Path- { _pathDrawAttributes = mempty- , _pathDefinition = []- }---- | Define a SVG group, corresponding `<g>` tag.-data Group a = Group- { -- | Group drawing attributes, propagated to all of its- -- children.- _groupDrawAttributes :: !DrawAttributes- -- | Content of the group, corresponding to all the tags- -- inside the `<g>` tag.- , _groupChildren :: ![a]- -- | Mapped to the attribute `viewBox`- , _groupViewBox :: !(Maybe (Double, Double, Double, Double))- -- | used for symbols only- , _groupAspectRatio :: !PreserveAspectRatio- }- deriving (Eq, Show)---- | Lenses associated to the Group type.-makeClassy ''Group--instance WithDrawAttributes (Group a) where- drawAttr = groupDrawAttributes--instance WithDefaultSvg (Group a) where- defaultSvg = Group- { _groupDrawAttributes = mempty- , _groupChildren = []- , _groupViewBox = Nothing- , _groupAspectRatio = defaultSvg- }---- | Define the `<symbol>` tag, equivalent to--- a named group.-newtype Symbol a =- Symbol { _groupOfSymbol :: Group a }- deriving (Eq, Show)---- | Lenses associated with the Symbol type.-makeLenses ''Symbol--instance WithDrawAttributes (Symbol a) where- drawAttr = groupOfSymbol . drawAttr--instance WithDefaultSvg (Symbol a) where- defaultSvg = Symbol defaultSvg---- | Define a `<circle>`.-data Circle = Circle- { -- | Drawing attributes of the circle.- _circleDrawAttributes :: !DrawAttributes- -- | Define the center of the circle, describe- -- the `cx` and `cy` attributes.- , _circleCenter :: !Point- -- | Radius of the circle, equivalent to the `r`- -- attribute.- , _circleRadius :: !Number- }- deriving (Eq, Show)---- | Lenses for the Circle type.-makeClassy ''Circle--instance WithDrawAttributes Circle where- drawAttr = circleDrawAttributes--instance WithDefaultSvg Circle where- defaultSvg = Circle- { _circleDrawAttributes = mempty- , _circleCenter = (Num 0, Num 0)- , _circleRadius = Num 0- }---- | Define an `<ellipse>`-data Ellipse = Ellipse- { -- | Drawing attributes of the ellipse.- _ellipseDrawAttributes :: !DrawAttributes- -- | Center of the ellipse, map to the `cx`- -- and `cy` attributes.- , _ellipseCenter :: !Point- -- | Radius along the X axis, map the- -- `rx` attribute.- , _ellipseXRadius :: !Number- -- | Radius along the Y axis, map the- -- `ry` attribute.- , _ellipseYRadius :: !Number- }- deriving (Eq, Show)---- | Lenses for the ellipse type.-makeClassy ''Ellipse--instance WithDrawAttributes Ellipse where- drawAttr = ellipseDrawAttributes--instance WithDefaultSvg Ellipse where- defaultSvg = Ellipse- { _ellipseDrawAttributes = mempty- , _ellipseCenter = (Num 0, Num 0)- , _ellipseXRadius = Num 0- , _ellipseYRadius = Num 0- }---- | Define an `<image>` tag.-data Image = Image- { -- | Drawing attributes of the image- _imageDrawAttributes :: !DrawAttributes- -- | Position of the image referenced by its- -- upper left corner.- , _imageCornerUpperLeft :: !Point- -- | Image width- , _imageWidth :: !Number- -- | Image Height- , _imageHeight :: !Number- -- | Image href, pointing to the real image.- , _imageHref :: !String- -- | preserveAspectRatio attribute- , _imageAspectRatio :: !PreserveAspectRatio- }- deriving (Eq, Show)---- | Lenses for the Image type.-makeClassy ''Image--instance WithDrawAttributes Image where- drawAttr = imageDrawAttributes--instance WithDefaultSvg Image where- defaultSvg = Image- { _imageDrawAttributes = mempty- , _imageCornerUpperLeft = (Num 0, Num 0)- , _imageWidth = Num 0- , _imageHeight = Num 0- , _imageHref = ""- , _imageAspectRatio = defaultSvg- }---- | Define an `<use>` for a named content.--- Every named content can be reused in the--- document using this element.-data Use = Use- { -- | Position where to draw the "used" element.- -- Correspond to the `x` and `y` attributes.- _useBase :: Point- -- | Referenced name, correspond to `xlink:href`- -- attribute.- , _useName :: String- -- | Define the width of the region where- -- to place the element. Map to the `width`- -- attribute.- , _useWidth :: Maybe Number- -- | Define the height of the region where- -- to place the element. Map to the `height`- -- attribute.- , _useHeight :: Maybe Number- -- | Use draw attributes.- , _useDrawAttributes :: DrawAttributes- }- deriving (Eq, Show)---- | Lenses for the Use type.-makeClassy ''Use--instance WithDrawAttributes Use where- drawAttr = useDrawAttributes--instance WithDefaultSvg Use where- defaultSvg = Use- { _useBase = (Num 0, Num 0)- , _useName = ""- , _useWidth = Nothing- , _useHeight = Nothing- , _useDrawAttributes = mempty- }---- | Define position information associated to--- `<text>` or `<tspan>` svg tag.-data TextInfo = TextInfo- { _textInfoX :: ![Number] -- ^ `x` attribute.- , _textInfoY :: ![Number] -- ^ `y` attribute.- , _textInfoDX :: ![Number] -- ^ `dx` attribute.- , _textInfoDY :: ![Number] -- ^ `dy` attribute.- , _textInfoRotate :: ![Double] -- ^ `rotate` attribute.- , _textInfoLength :: !(Maybe Number) -- ^ `textLength` attribute.- }- deriving (Eq, Show)--instance Monoid TextInfo where- mempty = TextInfo [] [] [] [] [] Nothing- mappend (TextInfo x1 y1 dx1 dy1 r1 l1)- (TextInfo x2 y2 dx2 dy2 r2 l2) =- TextInfo (x1 <> x2) (y1 <> y2)- (dx1 <> dx2) (dy1 <> dy2)- (r1 <> r2)- (getLast $ Last l1 <> Last l2)---- | Lenses for the TextInfo type.-makeClassy ''TextInfo--instance WithDefaultSvg TextInfo where- defaultSvg = mempty---- | Define the content of a `<tspan>` tag.-data TextSpanContent- = SpanText !T.Text -- ^ Raw text- | SpanTextRef !String -- ^ Equivalent to a `<tref>`- | SpanSub !TextSpan -- ^ Define a `<tspan>`- deriving (Eq, Show)---- | Define a `<tspan>` tag.-data TextSpan = TextSpan- { -- | Placing information for the text.- _spanInfo :: !TextInfo- -- | Drawing attributes for the text span.- , _spanDrawAttributes :: !DrawAttributes- -- | Content of the span.- , _spanContent :: ![TextSpanContent]- }- deriving (Eq, Show)---- | Lenses for the TextSpan type.-makeClassy ''TextSpan--instance WithDefaultSvg TextSpan where- defaultSvg = TextSpan- { _spanInfo = defaultSvg- , _spanDrawAttributes = mempty- , _spanContent = mempty- }---- | Describe the content of the `method` attribute on--- text path.-data TextPathMethod- = TextPathAlign -- ^ Map to the `align` value.- | TextPathStretch -- ^ Map to the `stretch` value.- deriving (Eq, Show)---- | Describe the content of the `spacing` text path--- attribute.-data TextPathSpacing- = TextPathSpacingExact -- ^ Map to the `exact` value.- | TextPathSpacingAuto -- ^ Map to the `auto` value.- deriving (Eq, Show)---- | Describe the `<textpath>` SVG tag.-data TextPath = TextPath- { -- | Define the beginning offset on the path,- -- the `startOffset` attribute.- _textPathStartOffset :: !Number- -- | Define the `xlink:href` attribute.- , _textPathName :: !String- -- | Correspond to the `method` attribute.- , _textPathMethod :: !TextPathMethod- -- | Correspond to the `spacing` attribute.- , _textPathSpacing :: !TextPathSpacing- -- | Real content of the path.- , _textPathData :: ![PathCommand]- }- deriving (Eq, Show)---- | Lenses for the TextPath type.-makeClassy ''TextPath--instance WithDefaultSvg TextPath where- defaultSvg = TextPath- { _textPathStartOffset = Num 0- , _textPathName = mempty- , _textPathMethod = TextPathAlign- , _textPathSpacing = TextPathSpacingExact- , _textPathData = []- }---- | Define the possible values of the `lengthAdjust`--- attribute.-data TextAdjust- = TextAdjustSpacing -- ^ Value `spacing`- | TextAdjustSpacingAndGlyphs -- ^ Value `spacingAndGlyphs`- deriving (Eq, Show)---- | Define the global `<tag>` SVG tag.-data Text = Text- { -- | Define the `lengthAdjust` attribute.- _textAdjust :: !TextAdjust- -- | Root of the text content.- , _textRoot :: !TextSpan- }- deriving (Eq, Show)---- | Lenses for the Text type.-makeClassy ''Text---- | Little helper to create a SVG text at a given--- baseline position.-textAt :: Point -> T.Text -> Text-textAt (x, y) txt = Text TextAdjustSpacing tspan where- tspan = defaultSvg- { _spanContent = [SpanText txt]- , _spanInfo = defaultSvg- { _textInfoX = [x]- , _textInfoY = [y]- }- }--instance WithDrawAttributes Text where- drawAttr = textRoot . spanDrawAttributes--instance WithDefaultSvg Text where- defaultSvg = Text- { _textRoot = defaultSvg- , _textAdjust = TextAdjustSpacing- }---- | Main type for the scene description, reorient to--- specific type describing each tag.-data Tree- = None- | UseTree { useInformation :: !Use- , useSubTree :: !(Maybe Tree) }- | GroupTree !(Group Tree)- | SymbolTree !(Symbol Tree)- | PathTree !Path- | CircleTree !Circle- | PolyLineTree !PolyLine- | PolygonTree !Polygon- | EllipseTree !Ellipse- | LineTree !Line- | RectangleTree !Rectangle- | TextTree !(Maybe TextPath) !Text- | ImageTree !Image- deriving (Eq, Show)---- | Define the orientation, associated to the--- `orient` attribute on the Marker-data MarkerOrientation- = OrientationAuto -- ^ Auto value- | OrientationAngle Coord -- ^ Specific angle.- deriving (Eq, Show)---- | Define the content of the `markerUnits` attribute--- on the Marker.-data MarkerUnit- = MarkerUnitStrokeWidth -- ^ Value `strokeWidth`- | MarkerUnitUserSpaceOnUse -- ^ Value `userSpaceOnUse`- deriving (Eq, Show)---- | Define the content of the `markerUnits` attribute--- on the Marker.-data Overflow- = OverflowVisible -- ^ Value `visible`- | OverflowHidden -- ^ Value `hidden`- deriving (Eq, Show)---- | Define the `<marker>` tag.-data Marker = Marker- { -- | Draw attributes of the marker.- _markerDrawAttributes :: DrawAttributes- -- | Define the reference point of the marker.- -- correspond to the `refX` and `refY` attributes.- , _markerRefPoint :: !(Number, Number)- -- | Define the width of the marker. Correspond to- -- the `markerWidth` attribute.- , _markerWidth :: !(Maybe Number)- -- | Define the height of the marker. Correspond to- -- the `markerHeight` attribute.- , _markerHeight :: !(Maybe Number)- -- | Correspond to the `orient` attribute.- , _markerOrient :: !(Maybe MarkerOrientation)- -- | Map the `markerUnits` attribute.- , _markerUnits :: !(Maybe MarkerUnit)- -- | Optional viewbox- , _markerViewBox :: !(Maybe (Double, Double, Double, Double))- -- | Elements defining the marker.- , _markerOverflow :: !(Maybe Overflow)- -- | preserveAspectRatio attribute- , _markerAspectRatio :: !PreserveAspectRatio- -- | Elements defining the marker.- , _markerElements :: [Tree]- }- deriving (Eq, Show)---- | Lenses for the Marker type.-makeClassy ''Marker--instance WithDrawAttributes Marker where- drawAttr = markerDrawAttributes--instance WithDefaultSvg Marker where- defaultSvg = Marker- { _markerDrawAttributes = mempty- , _markerRefPoint = (Num 0, Num 0)- , _markerWidth = Just (Num 3)- , _markerHeight = Just (Num 3)- , _markerOrient = Nothing -- MarkerOrientation- , _markerUnits = Nothing -- MarkerUnitStrokeWidth- , _markerViewBox = Nothing- , _markerOverflow = Nothing- , _markerElements = mempty- , _markerAspectRatio = defaultSvg- }---- | Insert element in the first sublist in the list of list.-appNode :: [[a]] -> a -> [[a]]-appNode [] e = [[e]]-appNode (curr:above) e = (e:curr) : above---- | Map a tree while propagating context information.--- The function passed in parameter receive a list--- representing the the path used to go arrive to the--- current node.-zipTree :: ([[Tree]] -> Tree) -> Tree -> Tree-zipTree f = dig [] where- dig prev e@None = f $ appNode prev e- dig prev e@(UseTree _ Nothing) = f $ appNode prev e- dig prev e@(UseTree nfo (Just u)) =- f . appNode prev . UseTree nfo . Just $ dig ([] : appNode prev e) u- dig prev e@(GroupTree g) =- f . appNode prev . GroupTree $ zipGroup (appNode prev e) g- dig prev e@(SymbolTree g) =- f . appNode prev . SymbolTree . Symbol .- zipGroup (appNode prev e) $ _groupOfSymbol g- dig prev e@(PathTree _) = f $ appNode prev e- dig prev e@(CircleTree _) = f $ appNode prev e- dig prev e@(PolyLineTree _) = f $ appNode prev e- dig prev e@(PolygonTree _) = f $ appNode prev e- dig prev e@(EllipseTree _) = f $ appNode prev e- dig prev e@(LineTree _) = f $ appNode prev e- dig prev e@(RectangleTree _) = f $ appNode prev e- dig prev e@(TextTree _ _) = f $ appNode prev e- dig prev e@(ImageTree _) = f $ appNode prev e-- zipGroup prev g = g { _groupChildren = updatedChildren }- where- groupChild = _groupChildren g- updatedChildren =- [dig (c:prev) child- | (child, c) <- zip groupChild $ inits groupChild]---- | Fold all nodes of a SVG tree.-foldTree :: (a -> Tree -> a) -> a -> Tree -> a-foldTree f = go where- go acc e = case e of- None -> f acc e- UseTree _ _ -> f acc e- PathTree _ -> f acc e- CircleTree _ -> f acc e- PolyLineTree _ -> f acc e- PolygonTree _ -> f acc e- EllipseTree _ -> f acc e- LineTree _ -> f acc e- RectangleTree _ -> f acc e- TextTree _ _ -> f acc e- ImageTree _ -> f acc e- GroupTree g ->- let subAcc = F.foldl' go acc $ _groupChildren g in- f subAcc e- SymbolTree s ->- let subAcc =- F.foldl' go acc . _groupChildren $ _groupOfSymbol s in- f subAcc e---- | Helper function mapping every tree element.-mapTree :: (Tree -> Tree) -> Tree -> Tree-mapTree f = go where- go e@None = f e- go e@(UseTree _ _) = f e- go (GroupTree g) = f . GroupTree $ mapGroup g- go (SymbolTree g) =- f . SymbolTree . Symbol . mapGroup $ _groupOfSymbol g- go e@(PathTree _) = f e- go e@(CircleTree _) = f e- go e@(PolyLineTree _) = f e- go e@(PolygonTree _) = f e- go e@(EllipseTree _) = f e- go e@(LineTree _) = f e- go e@(RectangleTree _) = f e- go e@(TextTree _ _) = f e- go e@(ImageTree _) = f e-- mapGroup g =- g { _groupChildren = map go $ _groupChildren g }---- | For every element of a svg tree, associate--- it's SVG tag name.-nameOfTree :: Tree -> T.Text-nameOfTree v =- case v of- None -> ""- UseTree _ _ -> "use"- GroupTree _ -> "g"- SymbolTree _ -> "symbol"- PathTree _ -> "path"- CircleTree _ -> "circle"- PolyLineTree _ -> "polyline"- PolygonTree _ -> "polygon"- EllipseTree _ -> "ellipse"- LineTree _ -> "line"- RectangleTree _ -> "rectangle"- TextTree _ _ -> "text"- ImageTree _ -> "image"--drawAttrOfTree :: Tree -> DrawAttributes-drawAttrOfTree v = case v of- None -> mempty- UseTree e _ -> e ^. drawAttr- GroupTree e -> e ^. drawAttr- SymbolTree e -> e ^. drawAttr- PathTree e -> e ^. drawAttr- CircleTree e -> e ^. drawAttr- PolyLineTree e -> e ^. drawAttr- PolygonTree e -> e ^. drawAttr- EllipseTree e -> e ^. drawAttr- LineTree e -> e ^. drawAttr- RectangleTree e -> e ^. drawAttr- TextTree _ e -> e ^. drawAttr- ImageTree e -> e ^. drawAttr--setDrawAttrOfTree :: Tree -> DrawAttributes -> Tree-setDrawAttrOfTree v attr = case v of- None -> None- UseTree e m -> UseTree (e & drawAttr .~ attr) m- GroupTree e -> GroupTree $ e & drawAttr .~ attr- SymbolTree e -> SymbolTree $ e & drawAttr .~ attr- PathTree e -> PathTree $ e & drawAttr .~ attr- CircleTree e -> CircleTree $ e & drawAttr .~ attr- PolyLineTree e -> PolyLineTree $ e & drawAttr .~ attr- PolygonTree e -> PolygonTree $ e & drawAttr .~ attr- EllipseTree e -> EllipseTree $ e & drawAttr .~ attr- LineTree e -> LineTree $ e & drawAttr .~ attr- RectangleTree e -> RectangleTree $ e & drawAttr .~ attr- TextTree a e -> TextTree a $ e & drawAttr .~ attr- ImageTree e -> ImageTree $ e & drawAttr .~ attr--instance WithDrawAttributes Tree where- drawAttr = lens drawAttrOfTree setDrawAttrOfTree--instance WithDefaultSvg Tree where- defaultSvg = None---- | Define the possible values of various *units attributes--- used in the definition of the gradients and masks.-data CoordinateUnits- = CoordUserSpace -- ^ `userSpaceOnUse` value- | CoordBoundingBox -- ^ `objectBoundingBox` value- deriving (Eq, Show)---- | Define the possible values for the `spreadMethod`--- values used for the gradient definitions.-data Spread- = SpreadRepeat -- ^ `reapeat` value- | SpreadPad -- ^ `pad` value- | SpreadReflect -- ^ `reflect value`- deriving (Eq, Show)---- | Define a color stop for the gradients. Represent--- the `<stop>` SVG tag.-data GradientStop = GradientStop- { -- | Gradient offset between 0 and 1, correspond- -- to the `offset` attribute.- _gradientOffset :: Float- -- | Color of the gradient stop. Correspond- -- to the `stop-color` attribute.- , _gradientColor :: PixelRGBA8- }- deriving (Eq, Show)---- | Lenses for the GradientStop type.-makeClassy ''GradientStop--instance WithDefaultSvg GradientStop where- defaultSvg = GradientStop- { _gradientOffset = 0.0- , _gradientColor = PixelRGBA8 0 0 0 255- }---- | Define a `<linearGradient>` tag.-data LinearGradient = LinearGradient- { -- | Define coordinate system of the gradient,- -- associated to the `gradientUnits` attribute.- _linearGradientUnits :: CoordinateUnits- -- | Point defining the beginning of the line gradient.- -- Associated to the `x1` and `y1` attribute.- , _linearGradientStart :: Point- -- | Point defining the end of the line gradient.- -- Associated to the `x2` and `y2` attribute.- , _linearGradientStop :: Point- -- | Define how to handle the values outside- -- the gradient start and stop. Associated to the- -- `spreadMethod` attribute.- , _linearGradientSpread :: Spread- -- | Define the transformation to apply to the- -- gradient points. Associated to the `gradientTransform`- -- attribute.- , _linearGradientTransform :: [Transformation]- -- | List of color stops of the linear gradient.- , _linearGradientStops :: [GradientStop]- }- deriving (Eq, Show)---- | Lenses for the LinearGradient type.-makeClassy ''LinearGradient--instance WithDefaultSvg LinearGradient where- defaultSvg = LinearGradient- { _linearGradientUnits = CoordBoundingBox- , _linearGradientStart = (Percent 0, Percent 0)- , _linearGradientStop = (Percent 1, Percent 0)- , _linearGradientSpread = SpreadPad- , _linearGradientTransform = []- , _linearGradientStops = []- }---- | Define a `<radialGradient>` tag.-data RadialGradient = RadialGradient- { -- | Define coordinate system of the gradient,- -- associated to the `gradientUnits` attribute.- _radialGradientUnits :: CoordinateUnits- -- | Center of the radial gradient. Associated to- -- the `cx` and `cy` attributes.- , _radialGradientCenter :: Point- -- | Radius of the radial gradient. Associated to- -- the `r` attribute.- , _radialGradientRadius :: Number- -- | X coordinate of the focus point of the radial- -- gradient. Associated to the `fx` attribute.- , _radialGradientFocusX :: Maybe Number- -- | Y coordinate of the focus point of the radial- -- gradient. Associated to the `fy` attribute.- , _radialGradientFocusY :: Maybe Number- -- | Define how to handle the values outside- -- the gradient start and stop. Associated to the- -- `spreadMethod` attribute.- , _radialGradientSpread :: Spread- -- | Define the transformation to apply to the- -- gradient points. Associated to the `gradientTransform`- -- attribute.- , _radialGradientTransform :: [Transformation]- -- | List of color stops of the radial gradient.- , _radialGradientStops :: [GradientStop]- }- deriving (Eq, Show)---- | Lenses for the RadialGradient type.-makeClassy ''RadialGradient--instance WithDefaultSvg RadialGradient where- defaultSvg = RadialGradient- { _radialGradientUnits = CoordBoundingBox- , _radialGradientCenter = (Percent 0.5, Percent 0.5)- , _radialGradientRadius = Percent 0.5- , _radialGradientFocusX = Nothing- , _radialGradientFocusY = Nothing- , _radialGradientSpread = SpreadPad- , _radialGradientTransform = []- , _radialGradientStops = []- }---- | Define a SVG `<mask>` tag.-data Mask = Mask- { -- | Drawing attributes of the Mask- _maskDrawAttributes :: DrawAttributes- -- | Correspond to the `maskContentUnits` attributes.- , _maskContentUnits :: CoordinateUnits- -- | Mapping to the `maskUnits` attribute.- , _maskUnits :: CoordinateUnits- -- | Map to the `x` and `y` attributes.- , _maskPosition :: Point- -- | Map to the `width` attribute- , _maskWidth :: Number- -- | Map to the `height` attribute.- , _maskHeight :: Number- -- | Children of the `<mask>` tag.- , _maskContent :: [Tree]- }- deriving (Eq, Show)---- | Lenses for the Mask type.-makeClassy ''Mask--instance WithDrawAttributes Mask where- drawAttr = maskDrawAttributes--instance WithDefaultSvg Mask where- defaultSvg = Mask- { _maskDrawAttributes = mempty- , _maskContentUnits = CoordUserSpace- , _maskUnits = CoordBoundingBox- , _maskPosition = (Percent (-0.1), Percent (-0.1))- , _maskWidth = Percent 1.2- , _maskHeight = Percent 1.2- , _maskContent = []- }---- | Define a `<clipPath>` tag.-data ClipPath = ClipPath- { _clipPathDrawAttributes :: DrawAttributes- -- | Maps to the `clipPathUnits` attribute- , _clipPathUnits :: CoordinateUnits- -- | Maps to the content of the tree- , _clipPathContent :: [Tree]- }- deriving (Eq, Show)---- | Lenses for the ClipPath type.-makeClassy ''ClipPath--instance WithDrawAttributes ClipPath where- drawAttr = clipPathDrawAttributes--instance WithDefaultSvg ClipPath where- defaultSvg = ClipPath- { _clipPathDrawAttributes = mempty- , _clipPathUnits = CoordUserSpace- , _clipPathContent = mempty- }---- | Define a `<pattern>` tag.-data Pattern = Pattern- { -- | Pattern drawing attributes.- _patternDrawAttributes :: !DrawAttributes- -- | Possible `viewBox`.- , _patternViewBox :: !(Maybe (Double, Double, Double, Double))- -- | Width of the pattern tile, mapped to the- -- `width` attribute- , _patternWidth :: !Number- -- | Height of the pattern tile, mapped to the- -- `height` attribute- , _patternHeight :: !Number- -- | Pattern tile base, mapped to the `x` and- -- `y` attributes.- , _patternPos :: !Point- -- | Elements used in the pattern.- , _patternElements :: ![Tree]- -- | Define the cordinate system to use for- -- the pattern. Mapped to the `patternUnits`- -- attribute.- , _patternUnit :: !CoordinateUnits- -- | Value of the "preserveAspectRatio" attribute- , _patternAspectRatio :: !PreserveAspectRatio - }- deriving Show---- | Lenses for the Patter type.-makeClassy ''Pattern--instance WithDrawAttributes Pattern where- drawAttr = patternDrawAttributes--instance WithDefaultSvg Pattern where- defaultSvg = Pattern- { _patternViewBox = Nothing- , _patternWidth = Num 0- , _patternHeight = Num 0- , _patternPos = (Num 0, Num 0)- , _patternElements = []- , _patternUnit = CoordBoundingBox- , _patternDrawAttributes = mempty- , _patternAspectRatio = defaultSvg- }---- | Sum types helping keeping track of all the namable--- elemens in a SVG document.-data Element- = ElementLinearGradient LinearGradient- | ElementRadialGradient RadialGradient- | ElementGeometry Tree- | ElementPattern Pattern- | ElementMarker Marker- | ElementMask Mask- | ElementClipPath ClipPath- deriving Show---- | Represent a full svg document with style,--- geometry and named elements.-data Document = Document- { _viewBox :: Maybe (Double, Double, Double, Double)- , _width :: Maybe Number- , _height :: Maybe Number- , _elements :: [Tree]- , _definitions :: M.Map String Element- , _description :: String- , _styleRules :: [CssRule]- , _documentLocation :: FilePath- }- deriving Show---- | Lenses associated to a SVG document.-makeClassy ''Document---- | Calculate the document size in function of the--- different available attributes in the document.-documentSize :: Dpi -> Document -> (Int, Int)-documentSize _ Document { _viewBox = Just (x1, y1, x2, y2)- , _width = Just (Percent pw)- , _height = Just (Percent ph)- } =- (floor $ dx * pw, floor $ dy * ph)- where- dx = abs $ x2 - x1- dy = abs $ y2 - y1-documentSize _ Document { _width = Just (Num w)- , _height = Just (Num h) } = (floor w, floor h)-documentSize dpi doc@(Document { _width = Just w- , _height = Just h }) =- documentSize dpi $ doc- { _width = Just $ toUserUnit dpi w- , _height = Just $ toUserUnit dpi h }-documentSize _ Document { _viewBox = Just (x1, y1, x2, y2) } =- (floor . abs $ x2 - x1, floor . abs $ y2 - y1)-documentSize _ _ = (1, 1)--mayMerge :: Monoid a => Maybe a -> Maybe a -> Maybe a-mayMerge (Just a) (Just b) = Just $ mappend a b-mayMerge _ b@(Just _) = b-mayMerge a Nothing = a--instance Monoid DrawAttributes where- mempty = DrawAttributes- { _strokeWidth = Last Nothing- , _strokeColor = Last Nothing- , _strokeOpacity = Nothing- , _strokeLineCap = Last Nothing- , _strokeLineJoin = Last Nothing- , _strokeMiterLimit = Last Nothing- , _fillColor = Last Nothing- , _groupOpacity = Nothing- , _fillOpacity = Nothing- , _fontSize = Last Nothing- , _fontFamily = Last Nothing- , _fontStyle = Last Nothing- , _transform = Nothing- , _fillRule = Last Nothing- , _attrClass = mempty- , _attrId = Nothing- , _strokeOffset = Last Nothing- , _strokeDashArray = Last Nothing- , _textAnchor = Last Nothing- , _maskRef = Last Nothing- , _clipPathRef = Last Nothing- , _clipRule = Last Nothing-- , _markerStart = Last Nothing- , _markerMid = Last Nothing- , _markerEnd = Last Nothing- }-- mappend a b = DrawAttributes- { _strokeWidth = (mappend `on` _strokeWidth) a b- , _strokeColor = (mappend `on` _strokeColor) a b- , _strokeLineCap = (mappend `on` _strokeLineCap) a b- , _strokeOpacity = (opacityMappend `on` _strokeOpacity) a b- , _strokeLineJoin = (mappend `on` _strokeLineJoin) a b- , _strokeMiterLimit = (mappend `on` _strokeMiterLimit) a b- , _fillColor = (mappend `on` _fillColor) a b- , _fillOpacity = (opacityMappend `on` _fillOpacity) a b- , _fontSize = (mappend `on` _fontSize) a b- , _transform = (mayMerge `on` _transform) a b- , _fillRule = (mappend `on` _fillRule) a b- , _attrClass = _attrClass b- , _attrId = _attrId b- , _groupOpacity = _groupOpacity b- , _strokeOffset = (mappend `on` _strokeOffset) a b- , _strokeDashArray = (mappend `on` _strokeDashArray) a b- , _fontFamily = (mappend `on` _fontFamily) a b- , _fontStyle = (mappend `on` _fontStyle) a b- , _textAnchor = (mappend `on` _textAnchor) a b- , _maskRef = (mappend `on` _maskRef) a b- , _clipPathRef = (mappend `on` _clipPathRef) a b- , _clipRule = (mappend `on` _clipRule) a b- , _markerStart = (mappend `on` _markerStart) a b- , _markerMid = (mappend `on` _markerMid) a b- , _markerEnd = (mappend `on` _markerEnd) a b- }- where- opacityMappend Nothing Nothing = Nothing- opacityMappend (Just v) Nothing = Just v- opacityMappend Nothing (Just v) = Just v- opacityMappend (Just v) (Just v2) = Just $ v * v2--instance WithDefaultSvg DrawAttributes where- defaultSvg = mempty--instance CssMatcheable Tree where- cssAttribOf _ _ = Nothing- cssClassOf = view (drawAttr . attrClass)- cssIdOf = fmap T.pack . view (drawAttr . attrId)- cssNameOf = nameOfTree-+{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE CPP #-} +-- | This module define all the types used in the definition +-- of a svg scene. +-- +-- Most of the types are lensified. +module Graphics.Svg.Types + ( -- * Basic building types + Coord + , Origin( .. ) + , Point + , RPoint + , PathCommand( .. ) + , Transformation( .. ) + , ElementRef( .. ) + , CoordinateUnits( .. ) + + -- ** Building helpers + , toPoint + , serializeNumber + , serializeTransformation + , serializeTransformations + + -- * Drawing control types + , Cap( .. ) + , LineJoin( .. ) + , Tree( .. ) + , Number( .. ) + , Spread( .. ) + , Texture( .. ) + , Element( .. ) + , FillRule( .. ) + , FontStyle( .. ) + , Dpi + + , WithDefaultSvg( .. ) + + -- * Main type + , Document( .. ) + , HasDocument( .. ) + , documentSize + + -- * Drawing attributes + , DrawAttributes( .. ) + , HasDrawAttributes( .. ) + , WithDrawAttributes( .. ) + + -- * SVG drawing primitives + -- ** Rectangle + , Rectangle( .. ) + , HasRectangle( .. ) + + -- ** Line + , Line( .. ) + , HasLine( .. ) + + -- ** Polygon + , Polygon( .. ) + , HasPolygon( .. ) + + -- ** Polyline + , PolyLine( .. ) + , HasPolyLine( .. ) + + -- ** Path + , Path( .. ) + , HasPath( .. ) + + -- ** Circle + , Circle( .. ) + , HasCircle( .. ) + + -- ** Ellipse + , Ellipse( .. ) + , HasEllipse( .. ) + + -- ** Image + , Image( .. ) + , HasImage( .. ) + + -- ** Use + , Use( .. ) + , HasUse( .. ) + + -- * Grouping primitives + -- ** Group + , Group( .. ) + , HasGroup( .. ) + + -- ** Symbol + , Symbol( .. ) + , groupOfSymbol + + -- * Text related types + -- ** Text + , Text( .. ) + , HasText( .. ) + , TextAnchor( .. ) + , textAt + + -- ** Text path + , TextPath( .. ) + , HasTextPath( .. ) + + , TextPathSpacing( .. ) + , TextPathMethod( .. ) + + -- ** Text span. + , TextSpanContent( .. ) + + , TextSpan( .. ) + , HasTextSpan( .. ) + + , TextInfo( .. ) + , HasTextInfo( .. ) + + , TextAdjust( .. ) + + -- * Marker definition + , Marker( .. ) + , Overflow( .. ) + , MarkerOrientation( .. ) + , MarkerUnit( .. ) + , HasMarker( .. ) + + -- * Gradient definition + , GradientStop( .. ) + , HasGradientStop( .. ) + + -- ** Linear Gradient + , LinearGradient( .. ) + , HasLinearGradient( .. ) + + -- ** Radial Gradient + , RadialGradient( .. ) + , HasRadialGradient( .. ) + + -- * Pattern definition + , Pattern( .. ) + , HasPattern( .. ) + + -- * Mask definition + , Mask( .. ) + , HasMask( .. ) + + -- * Clip path definition + , ClipPath( .. ) + , HasClipPath( .. ) + + -- * Aspect Ratio description + , PreserveAspectRatio( .. ) + , Alignment( .. ) + , MeetSlice( .. ) + , HasPreserveAspectRatio( .. ) + + -- * MISC functions + , isPathArc + , isPathWithArc + , nameOfTree + , zipTree + , mapTree + , foldTree + , toUserUnit + , mapNumber + ) where + +#if !MIN_VERSION_base(4,8,0) +import Data.Monoid( Monoid( .. ) ) +import Data.Foldable( Foldable ) +#endif + +import Data.Function( on ) +import Data.List( inits ) +import qualified Data.Map as M +import Data.Monoid( Last( .. ), (<>) ) +import qualified Data.Foldable as F +import qualified Data.Text as T +import Codec.Picture( PixelRGBA8( .. ) ) +import Control.Lens( Lens' + , lens + , makeClassy + , makeLenses + , view + , (^.) + , (&) + , (.~) + ) +import Graphics.Svg.CssTypes +import Linear hiding ( angle ) + +import Text.Printf + +-- | Basic coordiante type. +type Coord = Double + +-- | Real Point, fully determined and not +-- dependant of the rendering context. +type RPoint = V2 Coord + +-- | Possibly context dependant point. +type Point = (Number, Number) + +-- | Tell if a path command is absolute (in the current +-- user coordiante) or relative to the previous poitn. +data Origin + = OriginAbsolute -- ^ Next point in absolute coordinate + | OriginRelative -- ^ Next point relative to the previous + deriving (Eq, Show) + +-- | Path command definition. +data PathCommand + -- | 'M' or 'm' command + = MoveTo !Origin ![RPoint] + -- | Line to, 'L' or 'l' Svg path command. + | LineTo !Origin ![RPoint] + + -- | Equivalent to the 'H' or 'h' svg path command. + | HorizontalTo !Origin ![Coord] + -- | Equivalent to the 'V' or 'v' svg path command. + | VerticalTo !Origin ![Coord] + + -- | Cubic bezier, 'C' or 'c' command + | CurveTo !Origin ![(RPoint, RPoint, RPoint)] + -- | Smooth cubic bezier, equivalent to 'S' or 's' command + | SmoothCurveTo !Origin ![(RPoint, RPoint)] + -- | Quadratic bezier, 'Q' or 'q' command + | QuadraticBezier !Origin ![(RPoint, RPoint)] + -- | Quadratic bezier, 'T' or 't' command + | SmoothQuadraticBezierCurveTo !Origin ![RPoint] + -- | Eliptical arc, 'A' or 'a' command. + | EllipticalArc !Origin ![(Coord, Coord, Coord, Bool, Bool, RPoint)] + -- | Close the path, 'Z' or 'z' svg path command. + | EndPath + deriving (Eq, Show) + +-- | Little helper function to build a point. +toPoint :: Number -> Number -> Point +toPoint = (,) + +-- | Tell if the path command is an EllipticalArc. +isPathArc :: PathCommand -> Bool +isPathArc (EllipticalArc _ _) = True +isPathArc _ = False + +-- | Tell if a full path contain an EllipticalArc. +isPathWithArc :: Foldable f => f PathCommand -> Bool +isPathWithArc = F.any isPathArc + +-- | This type represent the align information of the +-- preserveAspectRatio SVGattribute +data Alignment + = AlignNone -- ^ "none" value + | AlignxMinYMin -- "xMinYMin" value + | AlignxMidYMin -- ^ "xMidYMin" value + | AlignxMaxYMin -- ^ "xMaxYMin" value + | AlignxMinYMid -- ^ "xMinYMid" value + | AlignxMidYMid -- ^ "xMidYMid" value + | AlignxMaxYMid -- ^ "xMaxYMid" value + | AlignxMinYMax -- ^ "xMinYMax" value + | AlignxMidYMax -- ^ "xMidYMax" value + | AlignxMaxYMax -- ^ "xMaxYMax" value + deriving (Eq, Show) + +-- | This type represent the "meet or slice" information +-- of the preserveAspectRatio SVGattribute +data MeetSlice = Meet | Slice + deriving (Eq, Show) + +-- | Describe the content of the preserveAspectRatio attribute. +data PreserveAspectRatio = PreserveAspectRatio + { _aspectRatioDefer :: !Bool + , _aspectRatioAlign :: !Alignment + , _aspectRatioMeetSlice :: !(Maybe MeetSlice) + } + deriving (Eq, Show) + +-- | Lenses for the PreserveAspectRatio type +makeClassy ''PreserveAspectRatio + +instance WithDefaultSvg PreserveAspectRatio where + defaultSvg = PreserveAspectRatio + { _aspectRatioDefer = False + , _aspectRatioAlign = AlignxMidYMid + , _aspectRatioMeetSlice = Nothing + } + +-- | Describe how the line should be terminated +-- when stroking them. Describe the values of the +-- `stroke-linecap` attribute. +-- See `_strokeLineCap` +data Cap + = CapRound -- ^ End with a round (`round` value) + | CapButt -- ^ Define straight just at the end (`butt` value) + | CapSquare -- ^ Straight further of the ends (`square` value) + deriving (Eq, Show) + +-- | Define the possible values of the `stroke-linejoin` +-- attribute. +-- see `_strokeLineJoin` +data LineJoin + = JoinMiter -- ^ `miter` value + | JoinBevel -- ^ `bevel` value + | JoinRound -- ^ `round` value + deriving (Eq, Show) + +-- | Describe the different value which can be used +-- in the `fill` or `stroke` attributes. +data Texture + = ColorRef PixelRGBA8 -- ^ Direct solid color (#rrggbb, #rgb) + | TextureRef String -- ^ Link to a complex texture (url(#name)) + | FillNone -- ^ Equivalent to the `none` value. + deriving (Eq, Show) + +-- | Describe the possile filling algorithms. +-- Map the values of the `fill-rule` attributes. +data FillRule + = FillEvenOdd -- ^ Correspond to the `evenodd` value. + | FillNonZero -- ^ Correspond to the `nonzero` value. + deriving (Eq, Show) + +-- | Describe the content of the `transformation` attribute. +-- see `_transform` and `transform`. +data Transformation + = -- | Directly encode the translation matrix. + TransformMatrix !Coord !Coord !Coord + !Coord !Coord !Coord + -- | Translation along a vector + | Translate !Double !Double + -- | Scaling on both axis or on X axis and Y axis. + | Scale !Double !(Maybe Double) + -- | Rotation around `(0, 0)` or around an optional + -- point. + | Rotate !Double !(Maybe (Double, Double)) + -- | Skew transformation along the X axis. + | SkewX !Double + -- | Skew transformation along the Y axis. + | SkewY !Double + -- | Unkown transformation, like identity. + | TransformUnknown + deriving (Eq, Show) + +-- | Convert the Transformation to a string which can be +-- directly used in a svg attributes. +serializeTransformation :: Transformation -> String +serializeTransformation t = case t of + TransformUnknown -> "" + TransformMatrix a b c d e f -> + printf "matrix(%g, %g, %g, %g, %g, %g)" a b c d e f + Translate x y -> printf "translate(%g, %g)" x y + Scale x Nothing -> printf "scale(%g)" x + Scale x (Just y) -> printf "scale(%g, %g)" x y + Rotate angle Nothing -> printf "rotate(%g)" angle + Rotate angle (Just (x, y))-> printf "rotate(%g, %g, %g)" angle x y + SkewX x -> printf "skewX(%g)" x + SkewY y -> printf "skewY(%g)" y + +-- | Transform a list of transformations to a string for svg +-- `transform` attributes. +serializeTransformations :: [Transformation] -> String +serializeTransformations = + unwords . fmap serializeTransformation + +-- | Class helping find the drawing attributes for all +-- the SVG attributes. +class WithDrawAttributes a where + -- | Lens which can be used to read/write primitives. + drawAttr :: Lens' a DrawAttributes + +-- | Define an empty 'default' element for the SVG tree. +-- It is used as base when parsing the element from XML. +class WithDefaultSvg a where + -- | The default element. + defaultSvg :: a + +-- | Classify the font style, used to search a matching +-- font in the FontCache. +data FontStyle + = FontStyleNormal + | FontStyleItalic + | FontStyleOblique + deriving (Eq, Show) + +-- | Tell where to anchor the text, where the position +-- given is realative to the text. +data TextAnchor + -- | The text with left aligned, or start at the postion + -- If the point is the '*' then the text will be printed + -- this way: + -- + -- > *THE_TEXT_TO_PRINT + -- + -- Equivalent to the `start` value. + = TextAnchorStart + -- | The text is middle aligned, so the text will be at + -- the left and right of the position: + -- + -- > THE_TEXT*TO_PRINT + -- + -- Equivalent to the `middle` value. + | TextAnchorMiddle + -- | The text is right aligned. + -- + -- > THE_TEXT_TO_PRINT* + -- + -- Equivalent to the `end` value. + | TextAnchorEnd + deriving (Eq, Show) + + +-- | Correspond to the possible values of the +-- the attributes which are either `none` or +-- `url(#elem)` +data ElementRef + = RefNone -- ^ Value for `none` + | Ref String -- ^ Equivalent to `url()` attribute. + deriving (Eq, Show) + +-- | This type define how to draw any primitives, +-- which color to use, how to stroke the primitives +-- and the potential transformations to use. +-- +-- All these attributes are propagated to the children. +data DrawAttributes = DrawAttributes + { -- | Attribute corresponding to the `stroke-width` + -- SVG attribute. + _strokeWidth :: !(Last Number) + -- | Correspond to the `stroke` attribute. + , _strokeColor :: !(Last Texture) + -- | Define the `stroke-opacity` attribute, the transparency + -- for the "border". + , _strokeOpacity :: !(Maybe Float) + -- | Correspond to the `stroke-linecap` SVG + -- attribute + , _strokeLineCap :: !(Last Cap) + -- | Correspond to the `stroke-linejoin` SVG + -- attribute + , _strokeLineJoin :: !(Last LineJoin) + -- | Define the distance of the miter join, correspond + -- to the `stroke-miterlimit` attritbue. + , _strokeMiterLimit :: !(Last Double) + -- | Define the filling color of the elements. Corresponding + -- to the `fill` attribute. + , _fillColor :: !(Last Texture) + -- | Define the `fill-opacity` attribute, the transparency + -- for the "content". + , _fillOpacity :: !(Maybe Float) + -- | Define the global or group opacity attribute. + , _groupOpacity :: !(Maybe Float) + -- | Content of the `transform` attribute + , _transform :: !(Maybe [Transformation]) + -- | Define the `fill-rule` used during the rendering. + , _fillRule :: !(Last FillRule) + -- | Define the `mask` attribute. + , _maskRef :: !(Last ElementRef) + -- | Define the `clip-path` attribute. + , _clipPathRef :: !(Last ElementRef) + -- | Define the `clip-rule` attribute. + , _clipRule :: !(Last FillRule) + -- | Map to the `class` attribute. Used for the CSS + -- rewriting. + , _attrClass :: ![T.Text] + -- | Map to the `id` attribute. Used for the CSS + -- rewriting. + , _attrId :: !(Maybe String) + -- | Define the start distance of the dashing pattern. + -- Correspond to the `stroke-dashoffset` attribute. + , _strokeOffset :: !(Last Number) + -- | Define the dashing pattern for the lines. Correspond + -- to the `stroke-dasharray` attribute. + , _strokeDashArray :: !(Last [Number]) + -- | Current size of the text, correspond to the + -- `font-size` SVG attribute. + , _fontSize :: !(Last Number) + -- | Define the possible fonts to be used for text rendering. + -- Map to the `font-family` attribute. + , _fontFamily :: !(Last [String]) + -- | Map to the `font-style` attribute. + , _fontStyle :: !(Last FontStyle) + -- | Define how to interpret the text position, correspond + -- to the `text-anchor` attribute. + , _textAnchor :: !(Last TextAnchor) + -- | Define the marker used for the start of the line. + -- Correspond to the `marker-start` attribute. + , _markerStart :: !(Last ElementRef) + -- | Define the marker used for every point of the + -- polyline/path Correspond to the `marker-mid` + -- attribute. + , _markerMid :: !(Last ElementRef) + -- | Define the marker used for the end of the line. + -- Correspond to the `marker-end` attribute. + , _markerEnd :: !(Last ElementRef) + } + deriving (Eq, Show) + +-- | Lenses for the DrawAttributes type. +makeClassy ''DrawAttributes + +-- | This primitive describe an unclosed suite of +-- segments. Correspond to the `<polyline>` tag. +data PolyLine = PolyLine + { -- | drawing attributes of the polyline. + _polyLineDrawAttributes :: !DrawAttributes + + -- | Geometry definition of the polyline. + -- correspond to the `points` attribute + , _polyLinePoints :: ![RPoint] + } + deriving (Eq, Show) + +-- | Lenses for the PolyLine type. +makeClassy ''PolyLine + +instance WithDefaultSvg PolyLine where + defaultSvg = PolyLine + { _polyLineDrawAttributes = mempty + , _polyLinePoints = [] + } + + +instance WithDrawAttributes PolyLine where + drawAttr = polyLineDrawAttributes + +-- | Primitive decriving polygon composed +-- of segements. Correspond to the `<polygon>` +-- tag +data Polygon = Polygon + { -- | Drawing attributes for the polygon. + _polygonDrawAttributes :: !DrawAttributes + -- | Points of the polygon. Correspond to + -- the `points` attributes. + , _polygonPoints :: ![RPoint] + } + deriving (Eq, Show) + +-- | Lenses for the Polygon type +makeClassy ''Polygon + +instance WithDrawAttributes Polygon where + drawAttr = polygonDrawAttributes + +instance WithDefaultSvg Polygon where + defaultSvg = Polygon + { _polygonDrawAttributes = mempty + , _polygonPoints = [] + } + +-- | Define a simple line. Correspond to the +-- `<line>` tag. +data Line = Line + { -- | Drawing attributes of line. + _lineDrawAttributes :: !DrawAttributes + -- | First point of the the line, correspond + -- to the `x1` and `y1` attributes. + , _linePoint1 :: !Point + -- | Second point of the the line, correspond + -- to the `x2` and `y2` attributes. + , _linePoint2 :: !Point + } + deriving (Eq, Show) + +-- | Lenses for the Line type. +makeClassy ''Line + +instance WithDrawAttributes Line where + drawAttr = lineDrawAttributes + +instance WithDefaultSvg Line where + defaultSvg = Line + { _lineDrawAttributes = mempty + , _linePoint1 = zeroPoint + , _linePoint2 = zeroPoint + } + where zeroPoint = (Num 0, Num 0) + +-- | Define a rectangle. Correspond to +-- `<rectangle>` svg tag. +data Rectangle = Rectangle + { -- | Rectangle drawing attributes. + _rectDrawAttributes :: !DrawAttributes + -- | Upper left corner of the rectangle, correspond + -- to the attributes `x` and `y`. + , _rectUpperLeftCorner :: !Point + -- | Rectangle width, correspond, strangely, to + -- the `width` attribute. + , _rectWidth :: !Number + -- | Rectangle height, correspond, amazingly, to + -- the `height` attribute. + , _rectHeight :: !Number + -- | Define the rounded corner radius radius + -- of the rectangle. Correspond to the `rx` and + -- `ry` attributes. + , _rectCornerRadius :: !(Number, Number) + } + deriving (Eq, Show) + +-- | Lenses for the Rectangle type. +makeClassy ''Rectangle + +instance WithDrawAttributes Rectangle where + drawAttr = rectDrawAttributes + +instance WithDefaultSvg Rectangle where + defaultSvg = Rectangle + { _rectDrawAttributes = mempty + , _rectUpperLeftCorner = (Num 0, Num 0) + , _rectWidth = Num 0 + , _rectHeight = Num 0 + , _rectCornerRadius = (Num 0, Num 0) + } + +-- | Type mapping the `<path>` svg tag. +data Path = Path + { -- | Drawing attributes of the path. + _pathDrawAttributes :: !DrawAttributes + -- | Definition of the path, correspond to the + -- `d` attributes. + , _pathDefinition :: ![PathCommand] + } + deriving (Eq, Show) + +-- | Lenses for the Path type +makeClassy ''Path + +instance WithDrawAttributes Path where + drawAttr = pathDrawAttributes + +instance WithDefaultSvg Path where + defaultSvg = Path + { _pathDrawAttributes = mempty + , _pathDefinition = [] + } + +-- | Define a SVG group, corresponding `<g>` tag. +data Group a = Group + { -- | Group drawing attributes, propagated to all of its + -- children. + _groupDrawAttributes :: !DrawAttributes + -- | Content of the group, corresponding to all the tags + -- inside the `<g>` tag. + , _groupChildren :: ![a] + -- | Mapped to the attribute `viewBox` + , _groupViewBox :: !(Maybe (Double, Double, Double, Double)) + -- | used for symbols only + , _groupAspectRatio :: !PreserveAspectRatio + } + deriving (Eq, Show) + +-- | Lenses associated to the Group type. +makeClassy ''Group + +instance WithDrawAttributes (Group a) where + drawAttr = groupDrawAttributes + +instance WithDefaultSvg (Group a) where + defaultSvg = Group + { _groupDrawAttributes = mempty + , _groupChildren = [] + , _groupViewBox = Nothing + , _groupAspectRatio = defaultSvg + } + +-- | Define the `<symbol>` tag, equivalent to +-- a named group. +newtype Symbol a = + Symbol { _groupOfSymbol :: Group a } + deriving (Eq, Show) + +-- | Lenses associated with the Symbol type. +makeLenses ''Symbol + +instance WithDrawAttributes (Symbol a) where + drawAttr = groupOfSymbol . drawAttr + +instance WithDefaultSvg (Symbol a) where + defaultSvg = Symbol defaultSvg + +-- | Define a `<circle>`. +data Circle = Circle + { -- | Drawing attributes of the circle. + _circleDrawAttributes :: !DrawAttributes + -- | Define the center of the circle, describe + -- the `cx` and `cy` attributes. + , _circleCenter :: !Point + -- | Radius of the circle, equivalent to the `r` + -- attribute. + , _circleRadius :: !Number + } + deriving (Eq, Show) + +-- | Lenses for the Circle type. +makeClassy ''Circle + +instance WithDrawAttributes Circle where + drawAttr = circleDrawAttributes + +instance WithDefaultSvg Circle where + defaultSvg = Circle + { _circleDrawAttributes = mempty + , _circleCenter = (Num 0, Num 0) + , _circleRadius = Num 0 + } + +-- | Define an `<ellipse>` +data Ellipse = Ellipse + { -- | Drawing attributes of the ellipse. + _ellipseDrawAttributes :: !DrawAttributes + -- | Center of the ellipse, map to the `cx` + -- and `cy` attributes. + , _ellipseCenter :: !Point + -- | Radius along the X axis, map the + -- `rx` attribute. + , _ellipseXRadius :: !Number + -- | Radius along the Y axis, map the + -- `ry` attribute. + , _ellipseYRadius :: !Number + } + deriving (Eq, Show) + +-- | Lenses for the ellipse type. +makeClassy ''Ellipse + +instance WithDrawAttributes Ellipse where + drawAttr = ellipseDrawAttributes + +instance WithDefaultSvg Ellipse where + defaultSvg = Ellipse + { _ellipseDrawAttributes = mempty + , _ellipseCenter = (Num 0, Num 0) + , _ellipseXRadius = Num 0 + , _ellipseYRadius = Num 0 + } + +-- | Define an `<image>` tag. +data Image = Image + { -- | Drawing attributes of the image + _imageDrawAttributes :: !DrawAttributes + -- | Position of the image referenced by its + -- upper left corner. + , _imageCornerUpperLeft :: !Point + -- | Image width + , _imageWidth :: !Number + -- | Image Height + , _imageHeight :: !Number + -- | Image href, pointing to the real image. + , _imageHref :: !String + -- | preserveAspectRatio attribute + , _imageAspectRatio :: !PreserveAspectRatio + } + deriving (Eq, Show) + +-- | Lenses for the Image type. +makeClassy ''Image + +instance WithDrawAttributes Image where + drawAttr = imageDrawAttributes + +instance WithDefaultSvg Image where + defaultSvg = Image + { _imageDrawAttributes = mempty + , _imageCornerUpperLeft = (Num 0, Num 0) + , _imageWidth = Num 0 + , _imageHeight = Num 0 + , _imageHref = "" + , _imageAspectRatio = defaultSvg + } + +-- | Define an `<use>` for a named content. +-- Every named content can be reused in the +-- document using this element. +data Use = Use + { -- | Position where to draw the "used" element. + -- Correspond to the `x` and `y` attributes. + _useBase :: Point + -- | Referenced name, correspond to `xlink:href` + -- attribute. + , _useName :: String + -- | Define the width of the region where + -- to place the element. Map to the `width` + -- attribute. + , _useWidth :: Maybe Number + -- | Define the height of the region where + -- to place the element. Map to the `height` + -- attribute. + , _useHeight :: Maybe Number + -- | Use draw attributes. + , _useDrawAttributes :: DrawAttributes + } + deriving (Eq, Show) + +-- | Lenses for the Use type. +makeClassy ''Use + +instance WithDrawAttributes Use where + drawAttr = useDrawAttributes + +instance WithDefaultSvg Use where + defaultSvg = Use + { _useBase = (Num 0, Num 0) + , _useName = "" + , _useWidth = Nothing + , _useHeight = Nothing + , _useDrawAttributes = mempty + } + +-- | Define position information associated to +-- `<text>` or `<tspan>` svg tag. +data TextInfo = TextInfo + { _textInfoX :: ![Number] -- ^ `x` attribute. + , _textInfoY :: ![Number] -- ^ `y` attribute. + , _textInfoDX :: ![Number] -- ^ `dx` attribute. + , _textInfoDY :: ![Number] -- ^ `dy` attribute. + , _textInfoRotate :: ![Double] -- ^ `rotate` attribute. + , _textInfoLength :: !(Maybe Number) -- ^ `textLength` attribute. + } + deriving (Eq, Show) + +instance Monoid TextInfo where + mempty = TextInfo [] [] [] [] [] Nothing + mappend (TextInfo x1 y1 dx1 dy1 r1 l1) + (TextInfo x2 y2 dx2 dy2 r2 l2) = + TextInfo (x1 <> x2) (y1 <> y2) + (dx1 <> dx2) (dy1 <> dy2) + (r1 <> r2) + (getLast $ Last l1 <> Last l2) + +-- | Lenses for the TextInfo type. +makeClassy ''TextInfo + +instance WithDefaultSvg TextInfo where + defaultSvg = mempty + +-- | Define the content of a `<tspan>` tag. +data TextSpanContent + = SpanText !T.Text -- ^ Raw text + | SpanTextRef !String -- ^ Equivalent to a `<tref>` + | SpanSub !TextSpan -- ^ Define a `<tspan>` + deriving (Eq, Show) + +-- | Define a `<tspan>` tag. +data TextSpan = TextSpan + { -- | Placing information for the text. + _spanInfo :: !TextInfo + -- | Drawing attributes for the text span. + , _spanDrawAttributes :: !DrawAttributes + -- | Content of the span. + , _spanContent :: ![TextSpanContent] + } + deriving (Eq, Show) + +-- | Lenses for the TextSpan type. +makeClassy ''TextSpan + +instance WithDefaultSvg TextSpan where + defaultSvg = TextSpan + { _spanInfo = defaultSvg + , _spanDrawAttributes = mempty + , _spanContent = mempty + } + +-- | Describe the content of the `method` attribute on +-- text path. +data TextPathMethod + = TextPathAlign -- ^ Map to the `align` value. + | TextPathStretch -- ^ Map to the `stretch` value. + deriving (Eq, Show) + +-- | Describe the content of the `spacing` text path +-- attribute. +data TextPathSpacing + = TextPathSpacingExact -- ^ Map to the `exact` value. + | TextPathSpacingAuto -- ^ Map to the `auto` value. + deriving (Eq, Show) + +-- | Describe the `<textpath>` SVG tag. +data TextPath = TextPath + { -- | Define the beginning offset on the path, + -- the `startOffset` attribute. + _textPathStartOffset :: !Number + -- | Define the `xlink:href` attribute. + , _textPathName :: !String + -- | Correspond to the `method` attribute. + , _textPathMethod :: !TextPathMethod + -- | Correspond to the `spacing` attribute. + , _textPathSpacing :: !TextPathSpacing + -- | Real content of the path. + , _textPathData :: ![PathCommand] + } + deriving (Eq, Show) + +-- | Lenses for the TextPath type. +makeClassy ''TextPath + +instance WithDefaultSvg TextPath where + defaultSvg = TextPath + { _textPathStartOffset = Num 0 + , _textPathName = mempty + , _textPathMethod = TextPathAlign + , _textPathSpacing = TextPathSpacingExact + , _textPathData = [] + } + +-- | Define the possible values of the `lengthAdjust` +-- attribute. +data TextAdjust + = TextAdjustSpacing -- ^ Value `spacing` + | TextAdjustSpacingAndGlyphs -- ^ Value `spacingAndGlyphs` + deriving (Eq, Show) + +-- | Define the global `<tag>` SVG tag. +data Text = Text + { -- | Define the `lengthAdjust` attribute. + _textAdjust :: !TextAdjust + -- | Root of the text content. + , _textRoot :: !TextSpan + } + deriving (Eq, Show) + +-- | Lenses for the Text type. +makeClassy ''Text + +-- | Little helper to create a SVG text at a given +-- baseline position. +textAt :: Point -> T.Text -> Text +textAt (x, y) txt = Text TextAdjustSpacing tspan where + tspan = defaultSvg + { _spanContent = [SpanText txt] + , _spanInfo = defaultSvg + { _textInfoX = [x] + , _textInfoY = [y] + } + } + +instance WithDrawAttributes Text where + drawAttr = textRoot . spanDrawAttributes + +instance WithDefaultSvg Text where + defaultSvg = Text + { _textRoot = defaultSvg + , _textAdjust = TextAdjustSpacing + } + +-- | Main type for the scene description, reorient to +-- specific type describing each tag. +data Tree + = None + | UseTree { useInformation :: !Use + , useSubTree :: !(Maybe Tree) } + | GroupTree !(Group Tree) + | SymbolTree !(Symbol Tree) + | PathTree !Path + | CircleTree !Circle + | PolyLineTree !PolyLine + | PolygonTree !Polygon + | EllipseTree !Ellipse + | LineTree !Line + | RectangleTree !Rectangle + | TextTree !(Maybe TextPath) !Text + | ImageTree !Image + deriving (Eq, Show) + +-- | Define the orientation, associated to the +-- `orient` attribute on the Marker +data MarkerOrientation + = OrientationAuto -- ^ Auto value + | OrientationAngle Coord -- ^ Specific angle. + deriving (Eq, Show) + +-- | Define the content of the `markerUnits` attribute +-- on the Marker. +data MarkerUnit + = MarkerUnitStrokeWidth -- ^ Value `strokeWidth` + | MarkerUnitUserSpaceOnUse -- ^ Value `userSpaceOnUse` + deriving (Eq, Show) + +-- | Define the content of the `markerUnits` attribute +-- on the Marker. +data Overflow + = OverflowVisible -- ^ Value `visible` + | OverflowHidden -- ^ Value `hidden` + deriving (Eq, Show) + +-- | Define the `<marker>` tag. +data Marker = Marker + { -- | Draw attributes of the marker. + _markerDrawAttributes :: DrawAttributes + -- | Define the reference point of the marker. + -- correspond to the `refX` and `refY` attributes. + , _markerRefPoint :: !(Number, Number) + -- | Define the width of the marker. Correspond to + -- the `markerWidth` attribute. + , _markerWidth :: !(Maybe Number) + -- | Define the height of the marker. Correspond to + -- the `markerHeight` attribute. + , _markerHeight :: !(Maybe Number) + -- | Correspond to the `orient` attribute. + , _markerOrient :: !(Maybe MarkerOrientation) + -- | Map the `markerUnits` attribute. + , _markerUnits :: !(Maybe MarkerUnit) + -- | Optional viewbox + , _markerViewBox :: !(Maybe (Double, Double, Double, Double)) + -- | Elements defining the marker. + , _markerOverflow :: !(Maybe Overflow) + -- | preserveAspectRatio attribute + , _markerAspectRatio :: !PreserveAspectRatio + -- | Elements defining the marker. + , _markerElements :: [Tree] + } + deriving (Eq, Show) + +-- | Lenses for the Marker type. +makeClassy ''Marker + +instance WithDrawAttributes Marker where + drawAttr = markerDrawAttributes + +instance WithDefaultSvg Marker where + defaultSvg = Marker + { _markerDrawAttributes = mempty + , _markerRefPoint = (Num 0, Num 0) + , _markerWidth = Just (Num 3) + , _markerHeight = Just (Num 3) + , _markerOrient = Nothing -- MarkerOrientation + , _markerUnits = Nothing -- MarkerUnitStrokeWidth + , _markerViewBox = Nothing + , _markerOverflow = Nothing + , _markerElements = mempty + , _markerAspectRatio = defaultSvg + } + +-- | Insert element in the first sublist in the list of list. +appNode :: [[a]] -> a -> [[a]] +appNode [] e = [[e]] +appNode (curr:above) e = (e:curr) : above + +-- | Map a tree while propagating context information. +-- The function passed in parameter receive a list +-- representing the the path used to go arrive to the +-- current node. +zipTree :: ([[Tree]] -> Tree) -> Tree -> Tree +zipTree f = dig [] where + dig prev e@None = f $ appNode prev e + dig prev e@(UseTree _ Nothing) = f $ appNode prev e + dig prev e@(UseTree nfo (Just u)) = + f . appNode prev . UseTree nfo . Just $ dig ([] : appNode prev e) u + dig prev e@(GroupTree g) = + f . appNode prev . GroupTree $ zipGroup (appNode prev e) g + dig prev e@(SymbolTree g) = + f . appNode prev . SymbolTree . Symbol . + zipGroup (appNode prev e) $ _groupOfSymbol g + dig prev e@(PathTree _) = f $ appNode prev e + dig prev e@(CircleTree _) = f $ appNode prev e + dig prev e@(PolyLineTree _) = f $ appNode prev e + dig prev e@(PolygonTree _) = f $ appNode prev e + dig prev e@(EllipseTree _) = f $ appNode prev e + dig prev e@(LineTree _) = f $ appNode prev e + dig prev e@(RectangleTree _) = f $ appNode prev e + dig prev e@(TextTree _ _) = f $ appNode prev e + dig prev e@(ImageTree _) = f $ appNode prev e + + zipGroup prev g = g { _groupChildren = updatedChildren } + where + groupChild = _groupChildren g + updatedChildren = + [dig (c:prev) child + | (child, c) <- zip groupChild $ inits groupChild] + +-- | Fold all nodes of a SVG tree. +foldTree :: (a -> Tree -> a) -> a -> Tree -> a +foldTree f = go where + go acc e = case e of + None -> f acc e + UseTree _ _ -> f acc e + PathTree _ -> f acc e + CircleTree _ -> f acc e + PolyLineTree _ -> f acc e + PolygonTree _ -> f acc e + EllipseTree _ -> f acc e + LineTree _ -> f acc e + RectangleTree _ -> f acc e + TextTree _ _ -> f acc e + ImageTree _ -> f acc e + GroupTree g -> + let subAcc = F.foldl' go acc $ _groupChildren g in + f subAcc e + SymbolTree s -> + let subAcc = + F.foldl' go acc . _groupChildren $ _groupOfSymbol s in + f subAcc e + +-- | Helper function mapping every tree element. +mapTree :: (Tree -> Tree) -> Tree -> Tree +mapTree f = go where + go e@None = f e + go e@(UseTree _ _) = f e + go (GroupTree g) = f . GroupTree $ mapGroup g + go (SymbolTree g) = + f . SymbolTree . Symbol . mapGroup $ _groupOfSymbol g + go e@(PathTree _) = f e + go e@(CircleTree _) = f e + go e@(PolyLineTree _) = f e + go e@(PolygonTree _) = f e + go e@(EllipseTree _) = f e + go e@(LineTree _) = f e + go e@(RectangleTree _) = f e + go e@(TextTree _ _) = f e + go e@(ImageTree _) = f e + + mapGroup g = + g { _groupChildren = map go $ _groupChildren g } + +-- | For every element of a svg tree, associate +-- it's SVG tag name. +nameOfTree :: Tree -> T.Text +nameOfTree v = + case v of + None -> "" + UseTree _ _ -> "use" + GroupTree _ -> "g" + SymbolTree _ -> "symbol" + PathTree _ -> "path" + CircleTree _ -> "circle" + PolyLineTree _ -> "polyline" + PolygonTree _ -> "polygon" + EllipseTree _ -> "ellipse" + LineTree _ -> "line" + RectangleTree _ -> "rectangle" + TextTree _ _ -> "text" + ImageTree _ -> "image" + +drawAttrOfTree :: Tree -> DrawAttributes +drawAttrOfTree v = case v of + None -> mempty + UseTree e _ -> e ^. drawAttr + GroupTree e -> e ^. drawAttr + SymbolTree e -> e ^. drawAttr + PathTree e -> e ^. drawAttr + CircleTree e -> e ^. drawAttr + PolyLineTree e -> e ^. drawAttr + PolygonTree e -> e ^. drawAttr + EllipseTree e -> e ^. drawAttr + LineTree e -> e ^. drawAttr + RectangleTree e -> e ^. drawAttr + TextTree _ e -> e ^. drawAttr + ImageTree e -> e ^. drawAttr + +setDrawAttrOfTree :: Tree -> DrawAttributes -> Tree +setDrawAttrOfTree v attr = case v of + None -> None + UseTree e m -> UseTree (e & drawAttr .~ attr) m + GroupTree e -> GroupTree $ e & drawAttr .~ attr + SymbolTree e -> SymbolTree $ e & drawAttr .~ attr + PathTree e -> PathTree $ e & drawAttr .~ attr + CircleTree e -> CircleTree $ e & drawAttr .~ attr + PolyLineTree e -> PolyLineTree $ e & drawAttr .~ attr + PolygonTree e -> PolygonTree $ e & drawAttr .~ attr + EllipseTree e -> EllipseTree $ e & drawAttr .~ attr + LineTree e -> LineTree $ e & drawAttr .~ attr + RectangleTree e -> RectangleTree $ e & drawAttr .~ attr + TextTree a e -> TextTree a $ e & drawAttr .~ attr + ImageTree e -> ImageTree $ e & drawAttr .~ attr + +instance WithDrawAttributes Tree where + drawAttr = lens drawAttrOfTree setDrawAttrOfTree + +instance WithDefaultSvg Tree where + defaultSvg = None + +-- | Define the possible values of various *units attributes +-- used in the definition of the gradients and masks. +data CoordinateUnits + = CoordUserSpace -- ^ `userSpaceOnUse` value + | CoordBoundingBox -- ^ `objectBoundingBox` value + deriving (Eq, Show) + +-- | Define the possible values for the `spreadMethod` +-- values used for the gradient definitions. +data Spread + = SpreadRepeat -- ^ `reapeat` value + | SpreadPad -- ^ `pad` value + | SpreadReflect -- ^ `reflect value` + deriving (Eq, Show) + +-- | Define a color stop for the gradients. Represent +-- the `<stop>` SVG tag. +data GradientStop = GradientStop + { -- | Gradient offset between 0 and 1, correspond + -- to the `offset` attribute. + _gradientOffset :: Float + -- | Color of the gradient stop. Correspond + -- to the `stop-color` attribute. + , _gradientColor :: PixelRGBA8 + } + deriving (Eq, Show) + +-- | Lenses for the GradientStop type. +makeClassy ''GradientStop + +instance WithDefaultSvg GradientStop where + defaultSvg = GradientStop + { _gradientOffset = 0.0 + , _gradientColor = PixelRGBA8 0 0 0 255 + } + +-- | Define a `<linearGradient>` tag. +data LinearGradient = LinearGradient + { -- | Define coordinate system of the gradient, + -- associated to the `gradientUnits` attribute. + _linearGradientUnits :: CoordinateUnits + -- | Point defining the beginning of the line gradient. + -- Associated to the `x1` and `y1` attribute. + , _linearGradientStart :: Point + -- | Point defining the end of the line gradient. + -- Associated to the `x2` and `y2` attribute. + , _linearGradientStop :: Point + -- | Define how to handle the values outside + -- the gradient start and stop. Associated to the + -- `spreadMethod` attribute. + , _linearGradientSpread :: Spread + -- | Define the transformation to apply to the + -- gradient points. Associated to the `gradientTransform` + -- attribute. + , _linearGradientTransform :: [Transformation] + -- | List of color stops of the linear gradient. + , _linearGradientStops :: [GradientStop] + } + deriving (Eq, Show) + +-- | Lenses for the LinearGradient type. +makeClassy ''LinearGradient + +instance WithDefaultSvg LinearGradient where + defaultSvg = LinearGradient + { _linearGradientUnits = CoordBoundingBox + , _linearGradientStart = (Percent 0, Percent 0) + , _linearGradientStop = (Percent 1, Percent 0) + , _linearGradientSpread = SpreadPad + , _linearGradientTransform = [] + , _linearGradientStops = [] + } + +-- | Define a `<radialGradient>` tag. +data RadialGradient = RadialGradient + { -- | Define coordinate system of the gradient, + -- associated to the `gradientUnits` attribute. + _radialGradientUnits :: CoordinateUnits + -- | Center of the radial gradient. Associated to + -- the `cx` and `cy` attributes. + , _radialGradientCenter :: Point + -- | Radius of the radial gradient. Associated to + -- the `r` attribute. + , _radialGradientRadius :: Number + -- | X coordinate of the focus point of the radial + -- gradient. Associated to the `fx` attribute. + , _radialGradientFocusX :: Maybe Number + -- | Y coordinate of the focus point of the radial + -- gradient. Associated to the `fy` attribute. + , _radialGradientFocusY :: Maybe Number + -- | Define how to handle the values outside + -- the gradient start and stop. Associated to the + -- `spreadMethod` attribute. + , _radialGradientSpread :: Spread + -- | Define the transformation to apply to the + -- gradient points. Associated to the `gradientTransform` + -- attribute. + , _radialGradientTransform :: [Transformation] + -- | List of color stops of the radial gradient. + , _radialGradientStops :: [GradientStop] + } + deriving (Eq, Show) + +-- | Lenses for the RadialGradient type. +makeClassy ''RadialGradient + +instance WithDefaultSvg RadialGradient where + defaultSvg = RadialGradient + { _radialGradientUnits = CoordBoundingBox + , _radialGradientCenter = (Percent 0.5, Percent 0.5) + , _radialGradientRadius = Percent 0.5 + , _radialGradientFocusX = Nothing + , _radialGradientFocusY = Nothing + , _radialGradientSpread = SpreadPad + , _radialGradientTransform = [] + , _radialGradientStops = [] + } + +-- | Define a SVG `<mask>` tag. +data Mask = Mask + { -- | Drawing attributes of the Mask + _maskDrawAttributes :: DrawAttributes + -- | Correspond to the `maskContentUnits` attributes. + , _maskContentUnits :: CoordinateUnits + -- | Mapping to the `maskUnits` attribute. + , _maskUnits :: CoordinateUnits + -- | Map to the `x` and `y` attributes. + , _maskPosition :: Point + -- | Map to the `width` attribute + , _maskWidth :: Number + -- | Map to the `height` attribute. + , _maskHeight :: Number + -- | Children of the `<mask>` tag. + , _maskContent :: [Tree] + } + deriving (Eq, Show) + +-- | Lenses for the Mask type. +makeClassy ''Mask + +instance WithDrawAttributes Mask where + drawAttr = maskDrawAttributes + +instance WithDefaultSvg Mask where + defaultSvg = Mask + { _maskDrawAttributes = mempty + , _maskContentUnits = CoordUserSpace + , _maskUnits = CoordBoundingBox + , _maskPosition = (Percent (-0.1), Percent (-0.1)) + , _maskWidth = Percent 1.2 + , _maskHeight = Percent 1.2 + , _maskContent = [] + } + +-- | Define a `<clipPath>` tag. +data ClipPath = ClipPath + { _clipPathDrawAttributes :: DrawAttributes + -- | Maps to the `clipPathUnits` attribute + , _clipPathUnits :: CoordinateUnits + -- | Maps to the content of the tree + , _clipPathContent :: [Tree] + } + deriving (Eq, Show) + +-- | Lenses for the ClipPath type. +makeClassy ''ClipPath + +instance WithDrawAttributes ClipPath where + drawAttr = clipPathDrawAttributes + +instance WithDefaultSvg ClipPath where + defaultSvg = ClipPath + { _clipPathDrawAttributes = mempty + , _clipPathUnits = CoordUserSpace + , _clipPathContent = mempty + } + +-- | Define a `<pattern>` tag. +data Pattern = Pattern + { -- | Pattern drawing attributes. + _patternDrawAttributes :: !DrawAttributes + -- | Possible `viewBox`. + , _patternViewBox :: !(Maybe (Double, Double, Double, Double)) + -- | Width of the pattern tile, mapped to the + -- `width` attribute + , _patternWidth :: !Number + -- | Height of the pattern tile, mapped to the + -- `height` attribute + , _patternHeight :: !Number + -- | Pattern tile base, mapped to the `x` and + -- `y` attributes. + , _patternPos :: !Point + -- | Elements used in the pattern. + , _patternElements :: ![Tree] + -- | Define the cordinate system to use for + -- the pattern. Mapped to the `patternUnits` + -- attribute. + , _patternUnit :: !CoordinateUnits + -- | Value of the "preserveAspectRatio" attribute + , _patternAspectRatio :: !PreserveAspectRatio + } + deriving Show + +-- | Lenses for the Patter type. +makeClassy ''Pattern + +instance WithDrawAttributes Pattern where + drawAttr = patternDrawAttributes + +instance WithDefaultSvg Pattern where + defaultSvg = Pattern + { _patternViewBox = Nothing + , _patternWidth = Num 0 + , _patternHeight = Num 0 + , _patternPos = (Num 0, Num 0) + , _patternElements = [] + , _patternUnit = CoordBoundingBox + , _patternDrawAttributes = mempty + , _patternAspectRatio = defaultSvg + } + +-- | Sum types helping keeping track of all the namable +-- elemens in a SVG document. +data Element + = ElementLinearGradient LinearGradient + | ElementRadialGradient RadialGradient + | ElementGeometry Tree + | ElementPattern Pattern + | ElementMarker Marker + | ElementMask Mask + | ElementClipPath ClipPath + deriving Show + +-- | Represent a full svg document with style, +-- geometry and named elements. +data Document = Document + { _viewBox :: Maybe (Double, Double, Double, Double) + , _width :: Maybe Number + , _height :: Maybe Number + , _elements :: [Tree] + , _definitions :: M.Map String Element + , _description :: String + , _styleRules :: [CssRule] + , _documentLocation :: FilePath + } + deriving Show + +-- | Lenses associated to a SVG document. +makeClassy ''Document + +-- | Calculate the document size in function of the +-- different available attributes in the document. +documentSize :: Dpi -> Document -> (Int, Int) +documentSize _ Document { _viewBox = Just (x1, y1, x2, y2) + , _width = Just (Percent pw) + , _height = Just (Percent ph) + } = + (floor $ dx * pw, floor $ dy * ph) + where + dx = abs $ x2 - x1 + dy = abs $ y2 - y1 +documentSize _ Document { _width = Just (Num w) + , _height = Just (Num h) } = (floor w, floor h) +documentSize dpi doc@(Document { _width = Just w + , _height = Just h }) = + documentSize dpi $ doc + { _width = Just $ toUserUnit dpi w + , _height = Just $ toUserUnit dpi h } +documentSize _ Document { _viewBox = Just (x1, y1, x2, y2) } = + (floor . abs $ x2 - x1, floor . abs $ y2 - y1) +documentSize _ _ = (1, 1) + +mayMerge :: Monoid a => Maybe a -> Maybe a -> Maybe a +mayMerge (Just a) (Just b) = Just $ mappend a b +mayMerge _ b@(Just _) = b +mayMerge a Nothing = a + +instance Monoid DrawAttributes where + mempty = DrawAttributes + { _strokeWidth = Last Nothing + , _strokeColor = Last Nothing + , _strokeOpacity = Nothing + , _strokeLineCap = Last Nothing + , _strokeLineJoin = Last Nothing + , _strokeMiterLimit = Last Nothing + , _fillColor = Last Nothing + , _groupOpacity = Nothing + , _fillOpacity = Nothing + , _fontSize = Last Nothing + , _fontFamily = Last Nothing + , _fontStyle = Last Nothing + , _transform = Nothing + , _fillRule = Last Nothing + , _attrClass = mempty + , _attrId = Nothing + , _strokeOffset = Last Nothing + , _strokeDashArray = Last Nothing + , _textAnchor = Last Nothing + , _maskRef = Last Nothing + , _clipPathRef = Last Nothing + , _clipRule = Last Nothing + + , _markerStart = Last Nothing + , _markerMid = Last Nothing + , _markerEnd = Last Nothing + } + + mappend a b = DrawAttributes + { _strokeWidth = (mappend `on` _strokeWidth) a b + , _strokeColor = (mappend `on` _strokeColor) a b + , _strokeLineCap = (mappend `on` _strokeLineCap) a b + , _strokeOpacity = (opacityMappend `on` _strokeOpacity) a b + , _strokeLineJoin = (mappend `on` _strokeLineJoin) a b + , _strokeMiterLimit = (mappend `on` _strokeMiterLimit) a b + , _fillColor = (mappend `on` _fillColor) a b + , _fillOpacity = (opacityMappend `on` _fillOpacity) a b + , _fontSize = (mappend `on` _fontSize) a b + , _transform = (mayMerge `on` _transform) a b + , _fillRule = (mappend `on` _fillRule) a b + , _attrClass = _attrClass b + , _attrId = _attrId b + , _groupOpacity = _groupOpacity b + , _strokeOffset = (mappend `on` _strokeOffset) a b + , _strokeDashArray = (mappend `on` _strokeDashArray) a b + , _fontFamily = (mappend `on` _fontFamily) a b + , _fontStyle = (mappend `on` _fontStyle) a b + , _textAnchor = (mappend `on` _textAnchor) a b + , _maskRef = (mappend `on` _maskRef) a b + , _clipPathRef = (mappend `on` _clipPathRef) a b + , _clipRule = (mappend `on` _clipRule) a b + , _markerStart = (mappend `on` _markerStart) a b + , _markerMid = (mappend `on` _markerMid) a b + , _markerEnd = (mappend `on` _markerEnd) a b + } + where + opacityMappend Nothing Nothing = Nothing + opacityMappend (Just v) Nothing = Just v + opacityMappend Nothing (Just v) = Just v + opacityMappend (Just v) (Just v2) = Just $ v * v2 + +instance WithDefaultSvg DrawAttributes where + defaultSvg = mempty + +instance CssMatcheable Tree where + cssAttribOf _ _ = Nothing + cssClassOf = view (drawAttr . attrClass) + cssIdOf = fmap T.pack . view (drawAttr . attrId) + cssNameOf = nameOfTree +
src/Graphics/Svg/XmlParser.hs view
@@ -1,1164 +1,1164 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-module Graphics.Svg.XmlParser( xmlOfDocument- , unparseDocument-- , SvgAttributeLens( .. )- , drawAttributesList- ) where---#if !MIN_VERSION_base(4,6,0)-import Text.Read( reads )-#else-import Text.Read( readMaybe )-#endif--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative( pure, (<$>), (<$), (<*>) )-import Data.Foldable( foldMap )-import Data.Monoid( mempty )-#endif--import Control.Applicative( (<|>), many )--import Control.Lens hiding( transform, children, elements, element )-import Control.Monad.State.Strict( State, runState, modify, gets )-import Data.Maybe( fromMaybe, catMaybes )-import Data.Monoid( Last( Last ), getLast, (<>) )-import Data.List( foldl', intercalate )-import Text.XML.Light.Proc( findAttrBy, elChildren, strContent )-import qualified Text.XML.Light as X-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TB-import qualified Data.Map as M-import Data.Attoparsec.Text( Parser, string, parseOnly, many1 )-import Codec.Picture( PixelRGBA8( .. ) )-import Graphics.Svg.Types-import Graphics.Svg.PathParser-import Graphics.Svg.ColorParser-import Graphics.Svg.CssTypes( CssDeclaration( .. )- , CssElement( .. )- , CssRule- , tserialize- )-import Graphics.Svg.CssParser( complexNumber- , num- , ruleSet- , dashArray- , styleString- , numberList )--import Text.Printf( printf )--{-import Debug.Trace-}--#if !MIN_VERSION_base(4,6,0)-readMaybe :: Read a => String -> Maybe a-readMaybe str = case reads str of- [] -> Nothing- (x, _):_ -> Just x-#endif--nodeName :: X.Element -> String-nodeName = X.qName . X.elName--attributeFinder :: String -> X.Element -> Maybe String-attributeFinder str =- findAttrBy (\a -> X.qName a == str)---- | Helper class to help simplify parsing code--- for various attributes.-class ParseableAttribute a where- aparse :: String -> Maybe a- aserialize :: a -> Maybe String--instance ParseableAttribute v => ParseableAttribute (Maybe v) where- aparse = fmap Just . aparse- aserialize = (>>= aserialize)--instance ParseableAttribute v => ParseableAttribute (Last v) where- aparse = fmap Last . aparse- aserialize = aserialize . getLast--instance ParseableAttribute String where- aparse = Just- aserialize = Just--instance ParseableAttribute Number where- aparse = parseMayStartDot complexNumber- aserialize = Just . serializeNumber--instance ParseableAttribute [Number] where- aparse = parse dashArray- aserialize = Just . serializeDashArray--instance ParseableAttribute PixelRGBA8 where- aparse = parse colorParser- aserialize = Just . colorSerializer--instance ParseableAttribute [PathCommand] where- aparse = parse $ many1 command- aserialize = Just . serializeCommands--instance ParseableAttribute [RPoint] where- aparse = parse pointData- aserialize = Just . serializePoints--instance ParseableAttribute Double where- aparse = parseMayStartDot num- aserialize v = Just $ printf "%g" v--instance ParseableAttribute Texture where- aparse = parse textureParser- aserialize = Just . textureSerializer--instance ParseableAttribute [Transformation] where- aparse = parse $ many transformParser- aserialize = Just . serializeTransformations--instance ParseableAttribute Alignment where- aparse s = Just $ case s of- "none" -> AlignNone - "xMinYMin" -> AlignxMinYMin- "xMidYMin" -> AlignxMidYMin- "xMaxYMin" -> AlignxMaxYMin- "xMinYMid" -> AlignxMinYMid- "xMidYMid" -> AlignxMidYMid- "xMaxYMid" -> AlignxMaxYMid- "xMinYMax" -> AlignxMinYMax- "xMidYMax" -> AlignxMidYMax- "xMaxYMax" -> AlignxMaxYMax- _ -> _aspectRatioAlign defaultSvg-- aserialize v = Just $ case v of- AlignNone -> "none" - AlignxMinYMin -> "xMinYMin"- AlignxMidYMin -> "xMidYMin"- AlignxMaxYMin -> "xMaxYMin"- AlignxMinYMid -> "xMinYMid"- AlignxMidYMid -> "xMidYMid"- AlignxMaxYMid -> "xMaxYMid"- AlignxMinYMax -> "xMinYMax"- AlignxMidYMax -> "xMidYMax"- AlignxMaxYMax -> "xMaxYMax"--instance ParseableAttribute MeetSlice where- aparse s = case s of- "meet" -> Just Meet- "slice" -> Just Slice- _ -> Nothing-- aserialize v = Just $ case v of- Meet -> "meet"- Slice -> "slice"--instance ParseableAttribute PreserveAspectRatio where- aserialize v = Just $ defer <> align <> meetSlice where- defer = if _aspectRatioDefer v then "defer " else ""- align = fromMaybe "" . aserialize $ _aspectRatioAlign v- meetSlice = fromMaybe "" $ aserialize =<< _aspectRatioMeetSlice v-- aparse s = case words s of- [] -> Nothing- [align] -> Just $ defaultSvg { _aspectRatioAlign = alignOf align }- ["defer", align] ->- Just $ defaultSvg- { _aspectRatioDefer = True- , _aspectRatioAlign = alignOf align- }- [align, meet] ->- Just $ defaultSvg- { _aspectRatioMeetSlice = aparse meet- , _aspectRatioAlign = alignOf align- }- ["defer", align, meet] ->- Just $ PreserveAspectRatio - { _aspectRatioDefer = True- , _aspectRatioAlign = alignOf align- , _aspectRatioMeetSlice = aparse meet- }- _ -> Nothing- where- alignOf = fromMaybe (_aspectRatioAlign defaultSvg) . aparse--instance ParseableAttribute Cap where- aparse s = case s of- "butt" -> Just CapButt- "round" -> Just CapRound- "square" -> Just CapSquare- _ -> Nothing-- aserialize c = Just $ case c of- CapButt -> "butt"- CapRound -> "round"- CapSquare -> "square"--instance ParseableAttribute TextAnchor where- aparse s = case s of- "middle" -> Just TextAnchorMiddle- "start" -> Just TextAnchorStart- "end" -> Just TextAnchorEnd- _ -> Nothing-- aserialize t = Just $ case t of- TextAnchorMiddle -> "middle"- TextAnchorStart -> "start"- TextAnchorEnd -> "end"--instance ParseableAttribute ElementRef where- aparse s = case parseOnly pa $ T.pack s of- Left _ -> Nothing- Right v -> Just v- where- pa = (RefNone <$ string "none")- <|> (Ref <$> urlRef)-- aserialize c = Just $ case c of- Ref r -> "url(#" <> r <> ")"- RefNone -> "none"--instance ParseableAttribute LineJoin where- aparse s = case s of- "miter" -> Just JoinMiter- "round" -> Just JoinRound- "bevel" -> Just JoinBevel- _ -> Nothing-- aserialize j = Just $ case j of- JoinMiter -> "miter"- JoinRound -> "round"- JoinBevel -> "bevel"--instance ParseableAttribute CoordinateUnits where- aparse s = case s of- "userSpaceOnUse" -> Just CoordUserSpace- "objectBoundingBox" -> Just CoordBoundingBox- _ -> Just CoordBoundingBox-- aserialize uni = Just $ case uni of- CoordUserSpace -> "userSpaceOnUse"- CoordBoundingBox -> "objectBoundingBox"--instance ParseableAttribute Spread where- aparse s = case s of- "pad" -> Just SpreadPad- "reflect" -> Just SpreadReflect- "repeat" -> Just SpreadRepeat- _ -> Nothing-- aserialize s = Just $ case s of- SpreadPad -> "pad"- SpreadReflect -> "reflect"- SpreadRepeat -> "repeat"--instance ParseableAttribute FillRule where- aparse s = case s of- "nonzero" -> Just FillNonZero- "evenodd" -> Just FillEvenOdd- _ -> Nothing-- aserialize f = Just $ case f of- FillNonZero -> "nonzero"- FillEvenOdd -> "evenodd"--instance ParseableAttribute TextAdjust where- aparse s = Just $ case s of- "spacing" -> TextAdjustSpacing- "spacingAndGlyphs" -> TextAdjustSpacingAndGlyphs- _ -> TextAdjustSpacing-- aserialize a = Just $ case a of- TextAdjustSpacing -> "spacing"- TextAdjustSpacingAndGlyphs -> "spacingAndGlyphs"--instance ParseableAttribute MarkerUnit where- aparse s = case s of- "strokeWidth" -> Just MarkerUnitStrokeWidth- "userSpaceOnUse" -> Just MarkerUnitUserSpaceOnUse- _ -> Nothing-- aserialize u = Just $ case u of- MarkerUnitStrokeWidth -> "strokeWidth"- MarkerUnitUserSpaceOnUse -> "userSpaceOnUse"--instance ParseableAttribute Overflow where- aparse s = case s of- "visible" -> Just OverflowVisible- "hidden" -> Just OverflowHidden- _ -> Nothing-- aserialize u = Just $ case u of- OverflowVisible -> "visible"- OverflowHidden -> "hidden"--instance ParseableAttribute MarkerOrientation where- aparse s = case (s, readMaybe s) of- ("auto", _) -> Just OrientationAuto- (_, Just f) -> Just $ OrientationAngle f- _ -> Nothing-- aserialize s = Just $ case s of- OrientationAuto -> "auto"- OrientationAngle f -> show f--instance ParseableAttribute (Double, Double, Double, Double) where- aparse = parse viewBoxParser- aserialize = Just . serializeViewBox--instance ParseableAttribute TextPathMethod where- aparse s = case s of- "align" -> Just TextPathAlign- "stretch" -> Just TextPathStretch- _ -> Nothing- aserialize m = Just $ case m of- TextPathAlign -> "align"- TextPathStretch -> "stretch"--instance ParseableAttribute TextPathSpacing where- aparse s = case s of- "auto" -> Just TextPathSpacingAuto- "exact" -> Just TextPathSpacingExact- _ -> Nothing-- aserialize s = Just $ case s of- TextPathSpacingAuto -> "auto"- TextPathSpacingExact -> "exact"--parse :: Parser a -> String -> Maybe a-parse p str = case parseOnly p (T.pack str) of- Left _ -> Nothing- Right r -> Just r--parseMayStartDot :: Parser a -> String -> Maybe a-parseMayStartDot p l@('.':_) = parse p ('0':l)-parseMayStartDot p l = parse p l--xmlUpdate :: (XMLUpdatable a) => a -> X.Element -> a-xmlUpdate initial el = foldl' grab initial attributes- where- grab value updater =- case attributeFinder (_attributeName updater) el of- Nothing -> value- Just v -> _attributeUpdater updater value v--xmlUnparse :: (XMLUpdatable a) => X.Element -> a-xmlUnparse = xmlUpdate defaultSvg--xmlUnparseWithDrawAttr- :: (XMLUpdatable a, WithDrawAttributes a)- => X.Element -> a-xmlUnparseWithDrawAttr e =- xmlUnparse e & drawAttr .~ xmlUnparse e--data SvgAttributeLens t = SvgAttributeLens- { _attributeName :: String- , _attributeUpdater :: t -> String -> t- , _attributeSerializer :: t -> Maybe String- }--class (WithDefaultSvg treeNode) => XMLUpdatable treeNode where- xmlTagName :: treeNode -> String- attributes :: [SvgAttributeLens treeNode]-- serializeTreeNode :: treeNode -> Maybe X.Element--setChildren :: X.Element -> [X.Content] -> X.Element-setChildren xNode children = xNode { X.elContent = children }--updateWithAccessor :: XMLUpdatable b => (a -> [b]) -> a -> Maybe X.Element -> Maybe X.Element-updateWithAccessor _ _ Nothing = Nothing-updateWithAccessor accessor node (Just xNode) =- Just . setChildren xNode . fmap X.Elem . catMaybes $ serializeTreeNode <$> accessor node--genericSerializeNode :: (XMLUpdatable treeNode) => treeNode -> Maybe X.Element-genericSerializeNode node =- Just . X.unode (xmlTagName node) $ concatMap generateAttribute attributes- where- generateAttribute attr = case _attributeSerializer attr node of- Nothing -> []- Just str -> return X.Attr- { X.attrKey = xName $ _attributeName attr- , X.attrVal = str- }- where- xName "href" =- X.QName { X.qName = "href"- , X.qURI = Nothing- , X.qPrefix = Just "xlink" }- xName h = X.unqual h---mergeAttributes :: X.Element -> X.Element -> X.Element-mergeAttributes thisXml otherXml =- thisXml { X.elAttribs = X.elAttribs otherXml ++ X.elAttribs thisXml }--genericSerializeWithDrawAttr :: (XMLUpdatable treeNode, WithDrawAttributes treeNode)- => treeNode -> Maybe X.Element-genericSerializeWithDrawAttr node = mergeAttributes <$> thisXml <*> drawAttrNode where- thisXml = genericSerializeNode node- drawAttrNode = genericSerializeNode $ node ^. drawAttr--type CssUpdater =- DrawAttributes -> [[CssElement]] -> DrawAttributes--opacitySetter :: String -> Lens' a (Maybe Float) -> SvgAttributeLens a-opacitySetter attribute elLens =- SvgAttributeLens attribute updater serializer- where- serializer a = printf "%g" <$> a ^. elLens- updater el str = case parseMayStartDot num str of- Nothing -> el- Just v -> el & elLens .~ Just (realToFrac v)--type Serializer e = e -> Maybe String--parserSetter :: String -> Lens' a e -> (String -> Maybe e) -> Serializer e- -> SvgAttributeLens a-parserSetter attribute elLens parser serialize =- SvgAttributeLens attribute updater serializer- where- updater el str = case parser str of- Nothing -> el- Just v -> el & elLens .~ v-- serializer a = serialize $ a ^. elLens--parseIn :: (Eq a, WithDefaultSvg s, ParseableAttribute a)- => String -> Lens' s a -> SvgAttributeLens s-parseIn attribute elLens =- SvgAttributeLens attribute updater serializer- where- updater el str = case aparse str of- Nothing -> el- Just v -> el & elLens .~ v-- serializer a- | v /= defaultVal = aserialize v- | otherwise = Nothing- where- v = a ^. elLens- defaultVal = defaultSvg ^. elLens--parserLastSetter :: String -> Lens' a (Last e) -> (String -> Maybe e) -> Serializer e- -> SvgAttributeLens a-parserLastSetter attribute elLens parser serialize =- SvgAttributeLens attribute updater serializer- where- updater el str = case parser str of- Nothing -> el- Just v -> el & elLens .~ Last (Just v)-- serializer a = getLast (a ^. elLens) >>= serialize--classSetter :: SvgAttributeLens DrawAttributes-classSetter = SvgAttributeLens "class" updater serializer- where- updater el str =- el & attrClass .~ (T.split (== ' ') $ T.pack str)-- serializer a = case a ^. attrClass of- [] -> Nothing- lst -> Just . T.unpack $ T.intercalate " " lst--cssUniqueNumber :: ASetter DrawAttributes DrawAttributes- a (Last Number)- -> CssUpdater-cssUniqueNumber setter attr ((CssNumber n:_):_) =- attr & setter .~ Last (Just n)-cssUniqueNumber _ attr _ = attr--cssUniqueFloat :: (Fractional n)- => ASetter DrawAttributes DrawAttributes a (Maybe n)- -> CssUpdater-cssUniqueFloat setter attr ((CssNumber (Num n):_):_) =- attr & setter .~ Just (realToFrac n)-cssUniqueFloat _ attr _ = attr--cssUniqueMayFloat :: ASetter DrawAttributes DrawAttributes a (Last Double)- -> CssUpdater-cssUniqueMayFloat setter attr ((CssNumber (Num n):_):_) =- attr & setter .~ Last (Just n)-cssUniqueMayFloat _ attr _ = attr--cssIdentAttr :: ParseableAttribute a => Lens' DrawAttributes a -> CssUpdater-cssIdentAttr setter attr ((CssIdent i:_):_) = case aparse $ T.unpack i of- Nothing -> attr- Just v -> attr & setter .~ v-cssIdentAttr _ attr _ = attr--fontFamilyParser :: CssUpdater-fontFamilyParser attr (lst:_) = attr & fontFamily .~ fontNames- where- fontNames = Last . Just $ T.unpack <$> extractString lst-- extractString [] = []- extractString (CssIdent n:rest) = n : extractString rest- extractString (CssString n:rest) = n : extractString rest- extractString (_:rest) = extractString rest-fontFamilyParser attr _ = attr---cssUniqueTexture :: ASetter DrawAttributes DrawAttributes- a (Last Texture)- -> CssUpdater-cssUniqueTexture setter attr ((CssIdent "none":_):_) =- attr & setter .~ Last (Just FillNone)-cssUniqueTexture setter attr ((CssColor c:_):_) =- attr & setter .~ Last (Just $ ColorRef c)-cssUniqueTexture setter attr ((CssFunction "url" [CssReference c]:_):_) =- attr & setter .~ Last (Just . TextureRef $ T.unpack c)-cssUniqueTexture _ attr _ = attr--cssElementRefSetter :: Lens' DrawAttributes (Last ElementRef)- -> CssUpdater-cssElementRefSetter setter attr ((CssFunction "url" [CssReference c]:_):_) =- attr & setter .~ Last (Just . Ref $ T.unpack c)-cssElementRefSetter setter attr ((CssIdent "none":_):_) =- attr & setter .~ Last (Just RefNone)-cssElementRefSetter _ attr _ = attr--cssMayStringSetter :: ASetter DrawAttributes DrawAttributes a (Maybe String)- -> CssUpdater-cssMayStringSetter setter attr ((CssIdent i:_):_) =- attr & setter .~ Just (T.unpack i)-cssMayStringSetter setter attr ((CssString i:_):_) =- attr & setter .~ Just (T.unpack i)-cssMayStringSetter _ attr _ = attr--cssNullSetter :: CssUpdater-cssNullSetter attr _ = attr--cssDashArray :: ASetter DrawAttributes DrawAttributes a (Last [Number])- -> CssUpdater-cssDashArray setter attr (lst:_) =- case [n | CssNumber n <- lst ] of- [] -> attr- v -> attr & setter .~ Last (Just v)-cssDashArray _ attr _ = attr---drawAttributesList :: [(SvgAttributeLens DrawAttributes, CssUpdater)]-drawAttributesList =- [("stroke-width" `parseIn` strokeWidth, cssUniqueNumber strokeWidth)- ,("stroke" `parseIn` strokeColor, cssUniqueTexture strokeColor)- ,("fill" `parseIn` fillColor, cssUniqueTexture fillColor)- ,("stroke-linecap" `parseIn` strokeLineCap, cssIdentAttr strokeLineCap)- ,("stroke-linejoin" `parseIn` strokeLineJoin, cssIdentAttr strokeLineJoin)- ,("stroke-miterlimit" `parseIn` strokeMiterLimit,- cssUniqueMayFloat strokeMiterLimit)-- ,("transform" `parseIn` transform, const)- ,(opacitySetter "opacity" groupOpacity, cssUniqueFloat groupOpacity)- ,(opacitySetter "fill-opacity" fillOpacity, cssUniqueFloat fillOpacity)- ,(opacitySetter "stroke-opacity" strokeOpacity, cssUniqueFloat strokeOpacity)- ,("font-size" `parseIn` fontSize, cssUniqueNumber fontSize)- ,(parserLastSetter "font-family" fontFamily (Just . commaSeparate)- (Just . intercalate ", "), fontFamilyParser)-- ,("fill-rule" `parseIn` fillRule, cssIdentAttr fillRule)- ,("clip-rule" `parseIn` clipRule, cssIdentAttr clipRule)- ,("mask" `parseIn` maskRef, cssElementRefSetter maskRef)- ,(classSetter, cssNullSetter) -- can't set class in CSS- ,("id" `parseIn` attrId, cssMayStringSetter attrId)- ,("stroke-dashoffset" `parseIn` strokeOffset,- cssUniqueNumber strokeOffset)- ,("stroke-dasharray" `parseIn` strokeDashArray, cssDashArray strokeDashArray)- ,("text-anchor" `parseIn` textAnchor, cssIdentAttr textAnchor)- ,("clip-path" `parseIn` clipPathRef, cssElementRefSetter clipPathRef)- ,("marker-end" `parseIn` markerEnd, cssElementRefSetter markerEnd)- ,("marker-start" `parseIn` markerStart, cssElementRefSetter markerStart)- ,("marker-mid" `parseIn` markerMid, cssElementRefSetter markerMid)- ]- where- commaSeparate =- fmap (T.unpack . T.strip) . T.split (',' ==) . T.pack--serializeDashArray :: [Number] -> String-serializeDashArray =- intercalate ", " . fmap serializeNumber--instance XMLUpdatable DrawAttributes where- xmlTagName _ = "DRAWATTRIBUTES"- attributes = styleAttribute : fmap fst drawAttributesList- serializeTreeNode = genericSerializeNode--styleAttribute :: SvgAttributeLens DrawAttributes-styleAttribute = SvgAttributeLens- { _attributeName = "style"- , _attributeUpdater = updater- , _attributeSerializer = const Nothing- }- where- updater attrs style = case parse styleString style of- Nothing -> attrs- Just decls -> foldl' applyer attrs decls-- cssUpdaters = [(T.pack $ _attributeName n, u) | (n, u) <- drawAttributesList]- applyer value (CssDeclaration txt elems) =- case lookup txt cssUpdaters of- Nothing -> value- Just f -> f value elems--instance XMLUpdatable Rectangle where- xmlTagName _ = "rect"- serializeTreeNode = genericSerializeWithDrawAttr- attributes =- ["width" `parseIn` rectWidth- ,"height" `parseIn` rectHeight- ,"x" `parseIn` (rectUpperLeftCorner._1)- ,"y" `parseIn` (rectUpperLeftCorner._2)- ,"rx" `parseIn` (rectCornerRadius._1)- ,"ry" `parseIn` (rectCornerRadius._2)- ]--instance XMLUpdatable Image where- xmlTagName _ = "image"- serializeTreeNode = genericSerializeWithDrawAttr- attributes =- ["width" `parseIn` imageWidth- ,"height" `parseIn` imageHeight- ,"x" `parseIn` (imageCornerUpperLeft._1)- ,"y" `parseIn` (imageCornerUpperLeft._2)- ,parserSetter "href" imageHref (Just . dropSharp) Just- ,"preserveAspectRatio" `parseIn` imageAspectRatio- ]--instance XMLUpdatable Line where- xmlTagName _ = "line"- serializeTreeNode = genericSerializeWithDrawAttr- attributes =- ["x1" `parseIn` (linePoint1._1)- ,"y1" `parseIn` (linePoint1._2)- ,"x2" `parseIn` (linePoint2._1)- ,"y2" `parseIn` (linePoint2._2)- ]--instance XMLUpdatable Ellipse where- xmlTagName _ = "ellipse"- serializeTreeNode = genericSerializeWithDrawAttr- attributes =- ["cx" `parseIn` (ellipseCenter._1)- ,"cy" `parseIn` (ellipseCenter._2)- ,"rx" `parseIn` ellipseXRadius- ,"ry" `parseIn` ellipseYRadius- ]--instance XMLUpdatable Circle where- xmlTagName _ = "circle"- serializeTreeNode = genericSerializeWithDrawAttr- attributes =- ["cx" `parseIn` (circleCenter._1)- ,"cy" `parseIn` (circleCenter._2)- ,"r" `parseIn` circleRadius- ]--instance XMLUpdatable Mask where- xmlTagName _ = "mask"- serializeTreeNode node =- updateWithAccessor _maskContent node $- genericSerializeWithDrawAttr node-- attributes =- ["x" `parseIn` (maskPosition._1)- ,"y" `parseIn` (maskPosition._2)- ,"width" `parseIn` maskWidth- ,"height" `parseIn` maskHeight- ,"maskContentUnits" `parseIn` maskContentUnits- ,"maskUnits" `parseIn` maskUnits- ]--instance XMLUpdatable ClipPath where- xmlTagName _ = "clipPath"- serializeTreeNode node =- updateWithAccessor _clipPathContent node $- genericSerializeWithDrawAttr node- attributes =- ["clipPathUnits" `parseIn` clipPathUnits]--instance XMLUpdatable Polygon where- xmlTagName _ = "polygon"- serializeTreeNode = genericSerializeWithDrawAttr- attributes = ["points" `parseIn` polygonPoints]--instance XMLUpdatable PolyLine where- xmlTagName _ = "polyline"- serializeTreeNode = genericSerializeWithDrawAttr- attributes = ["points" `parseIn` polyLinePoints]--instance XMLUpdatable Path where- xmlTagName _ = "path"- serializeTreeNode = genericSerializeWithDrawAttr- attributes = ["d" `parseIn` pathDefinition]--instance XMLUpdatable LinearGradient where- xmlTagName _ = "linearGradient"- serializeTreeNode node =- updateWithAccessor _linearGradientStops node $ genericSerializeNode node-- attributes =- ["gradientTransform" `parseIn` linearGradientTransform- ,"gradientUnits" `parseIn` linearGradientUnits- ,"spreadMethod" `parseIn` linearGradientSpread- ,"x1" `parseIn` (linearGradientStart._1)- ,"y1" `parseIn` (linearGradientStart._2)- ,"x2" `parseIn` (linearGradientStop._1)- ,"y2" `parseIn` (linearGradientStop._2)- ]--instance XMLUpdatable Tree where- xmlTagName _ = "TREE"- attributes = []- serializeTreeNode e = case e of- None -> Nothing- UseTree u _ -> serializeTreeNode u- GroupTree g -> serializeTreeNode g- SymbolTree s -> serializeTreeNode s- PathTree p -> serializeTreeNode p- CircleTree c -> serializeTreeNode c- PolyLineTree p -> serializeTreeNode p- PolygonTree p -> serializeTreeNode p- EllipseTree el -> serializeTreeNode el- LineTree l -> serializeTreeNode l- RectangleTree r -> serializeTreeNode r- TextTree Nothing t -> serializeTreeNode t- ImageTree i -> serializeTreeNode i- TextTree (Just p) t -> do- textNode <- serializeTreeNode t- pathNode <- serializeTreeNode p- let sub = [X.Elem . setChildren pathNode $ X.elContent textNode]- return $ setChildren textNode sub---isNotNone :: Tree -> Bool-isNotNone None = False-isNotNone _ = True--instance XMLUpdatable (Group Tree) where- xmlTagName _ = "g"- serializeTreeNode node =- updateWithAccessor (filter isNotNone . _groupChildren) node $- genericSerializeWithDrawAttr node- attributes = []--instance XMLUpdatable (Symbol Tree) where- xmlTagName _ = "symbol"- serializeTreeNode node =- updateWithAccessor (filter isNotNone . _groupChildren . _groupOfSymbol) node $- genericSerializeWithDrawAttr node- attributes =- ["viewBox" `parseIn` (groupOfSymbol . groupViewBox)- ,"preserveAspectRatio" `parseIn` (groupOfSymbol . groupAspectRatio)- ]---instance XMLUpdatable RadialGradient where- xmlTagName _ = "radialGradient"- serializeTreeNode node =- updateWithAccessor _radialGradientStops node $ genericSerializeNode node- attributes =- ["gradientTransform" `parseIn` radialGradientTransform- ,"gradientUnits" `parseIn` radialGradientUnits- ,"spreadMethod" `parseIn` radialGradientSpread- ,"cx" `parseIn` (radialGradientCenter._1)- ,"cy" `parseIn` (radialGradientCenter._2)- ,"r" `parseIn` radialGradientRadius- ,"fx" `parseIn` radialGradientFocusX- ,"fy" `parseIn` radialGradientFocusY- ]--instance XMLUpdatable Use where- xmlTagName _ = "use"- serializeTreeNode = genericSerializeWithDrawAttr- attributes =- ["x" `parseIn` (useBase._1)- ,"y" `parseIn` (useBase._2)- ,"width" `parseIn` useWidth- ,"height" `parseIn` useHeight- ,parserSetter "href" useName (Just . dropSharp) (Just . ('#':))- ]--dropSharp :: String -> String-dropSharp ('#':rest) = rest-dropSharp a = a--instance XMLUpdatable TextInfo where- xmlTagName _ = "tspan"- serializeTreeNode = genericSerializeNode- attributes =- [parserSetter "x" textInfoX (parse dashArray) dashNotEmpty- ,parserSetter "y" textInfoY (parse dashArray) dashNotEmpty- ,parserSetter "dx" textInfoDX (parse dashArray) dashNotEmpty- ,parserSetter "dy" textInfoDY (parse dashArray) dashNotEmpty- ,parserSetter "rotate" textInfoRotate- (parse numberList)- rotateNotEmpty- ,"textLength" `parseIn` textInfoLength- ]- where- dashNotEmpty [] = Nothing- dashNotEmpty lst = Just $ serializeDashArray lst-- rotateNotEmpty [] = Nothing- rotateNotEmpty lst =- Just . unwords $ printf "%g" <$> lst---instance XMLUpdatable TextPath where- xmlTagName _ = "textPath"- serializeTreeNode = genericSerializeNode- attributes =- ["startOffset" `parseIn` textPathStartOffset- ,"method" `parseIn` textPathMethod- ,"spacing" `parseIn` textPathSpacing- ,parserSetter "href" textPathName (Just . dropSharp) (Just . ('#':))- ]--instance XMLUpdatable Text where- xmlTagName _ = "text"- serializeTreeNode = serializeText- attributes = ["lengthAdjust" `parseIn` textAdjust]---instance XMLUpdatable Pattern where- xmlTagName _ = "pattern"- serializeTreeNode node =- updateWithAccessor _patternElements node $ genericSerializeWithDrawAttr node- attributes =- ["viewBox" `parseIn` patternViewBox- ,"patternUnits" `parseIn` patternUnit- ,"width" `parseIn` patternWidth- ,"height" `parseIn` patternHeight- ,"x" `parseIn` (patternPos._1)- ,"y" `parseIn` (patternPos._2)- ,"preserveAspectRatio" `parseIn` patternAspectRatio- ]--instance XMLUpdatable Marker where- xmlTagName _ = "marker"- serializeTreeNode node =- updateWithAccessor _markerElements node $ genericSerializeWithDrawAttr node- attributes =- ["refX" `parseIn` (markerRefPoint._1)- ,"refY" `parseIn` (markerRefPoint._2)- ,"markerWidth" `parseIn` markerWidth- ,"markerHeight" `parseIn` markerHeight- ,"patternUnits" `parseIn` markerUnits- ,"orient" `parseIn` markerOrient- ,"viewBox" `parseIn` markerViewBox- ,"overflow" `parseIn` markerOverflow- ,"preserveAspectRatio" `parseIn` markerAspectRatio- ]--serializeText :: Text -> Maybe X.Element-serializeText topText = namedNode where- namedNode = fmap (\x -> x { X.elName = X.unqual "text" }) topNode- topNode = serializeSpan $ _textRoot topText-- serializeSpan tspan = case (info, drawInfo) of- (Nothing, Nothing) -> Nothing- (Just a, Nothing) -> Just $ setChildren a subContent- (Nothing, Just b) -> Just $ setChildren b subContent- (Just a, Just b) -> - Just $ setChildren (mergeAttributes a b) subContent- where- info = genericSerializeNode $ _spanInfo tspan- drawInfo = genericSerializeNode $ _spanDrawAttributes tspan- subContent = catMaybes $ serializeContent <$> _spanContent tspan-- serializeContent (SpanText t) = Just . X.Text $ X.blank_cdata { X.cdData = T.unpack t }- serializeContent (SpanTextRef _t) = Just . X.Text $ X.blank_cdata { X.cdData = "" }- serializeContent (SpanSub sub) = X.Elem <$> serializeSpan sub--unparseText :: [X.Content] -> ([TextSpanContent], Maybe TextPath)-unparseText = extractResult . go True- where- extractResult (a, b, _) = (a, b)-- go startStrip [] = ([], Nothing, startStrip)- go startStrip (X.CRef _:rest) = go startStrip rest- go startStrip (X.Elem e@(nodeName -> "tspan"):rest) =- (SpanSub spans : trest, mpath, retStrip)- where- (trest, mpath, retStrip) = go restStrip rest- (sub, _, restStrip) = go startStrip $ X.elContent e- spans = TextSpan (xmlUnparse e) (xmlUnparse e) sub-- go startStrip (X.Elem e@(nodeName -> "tref"):rest) =- case attributeFinder "href" e of- Nothing -> go startStrip rest- Just v -> (SpanTextRef v : trest, mpath, stripRet)- where (trest, mpath, stripRet) = go startStrip rest-- go startStrip (X.Elem e@(nodeName -> "textPath"):rest) =- case attributeFinder "href" e of- Nothing -> go startStrip rest- Just v -> (tsub ++ trest, pure p, retStrp)- where- p = (xmlUnparse e) { _textPathName = dropSharp v }- (trest, _, retStrp) = go restStrip rest- (tsub, _, restStrip) = go startStrip $ X.elContent e-- go startStrip (X.Elem _:rest) = go startStrip rest- go startStrip (X.Text t:rest)- | T.length cleanText == 0 = go startStrip rest- | otherwise =- (SpanText cleanText : trest, mpath, stripRet)- where- (trest, mpath, stripRet) = go subShouldStrip rest-- subShouldStrip = T.pack " " `T.isSuffixOf` cleanText-- space = T.singleton ' '- singulariseSpaces tt- | space `T.isPrefixOf` tt = space- | otherwise = tt-- stripStart | startStrip = T.stripStart- | otherwise = id-- cleanText = stripStart- . T.concat- . fmap singulariseSpaces- . T.groupBy (\a b -> (a /= ' ' && b /= ' ') || a == b)- . T.filter (\c -> c /= '\n' && c /= '\r')- . T.map (\c -> if c == '\t' then ' ' else c)- . T.pack- $ X.cdData t--gradientOffsetSetter :: SvgAttributeLens GradientStop-gradientOffsetSetter = SvgAttributeLens "offset" setter serialize- where- serialize a = Just $ printf "%d%%" percentage- where percentage = floor . (100 *) $ a ^. gradientOffset :: Int-- setter el str = el & gradientOffset .~ val- where- val = realToFrac $ case parseMayStartDot complexNumber str of- Nothing -> 0- Just (Num n) -> n- Just (Px n) -> n- Just (Percent n) -> n- Just (Em n) -> n- Just (Pc n) -> n- Just (Mm n) -> n- Just (Cm n) -> n- Just (Point n) -> n- Just (Inches n) -> n--instance XMLUpdatable GradientStop where- xmlTagName _ = "stop"- serializeTreeNode = genericSerializeNode- attributes =- [gradientOffsetSetter- ,"stop-color" `parseIn` gradientColor- ]---data Symbols = Symbols- { symbols :: !(M.Map String Element)- , cssStyle :: [CssRule]- }--emptyState :: Symbols-emptyState = Symbols mempty mempty--parseGradientStops :: X.Element -> [GradientStop]-parseGradientStops = concatMap unStop . elChildren- where- unStop e@(nodeName -> "stop") = [xmlUnparse e]- unStop _ = []--withId :: X.Element -> (X.Element -> Element)- -> State Symbols Tree-withId el f = case attributeFinder "id" el of- Nothing -> return None- Just elemId -> do- modify $ \s ->- s { symbols = M.insert elemId (f el) $ symbols s }- return None--unparseDefs :: X.Element -> State Symbols Tree-unparseDefs e@(nodeName -> "pattern") = do- subElements <- mapM unparse $ elChildren e- withId e . const . ElementPattern $ pat { _patternElements = subElements}- where- pat = xmlUnparse e-unparseDefs e@(nodeName -> "marker") = do- subElements <- mapM unparse $ elChildren e- withId e . const . ElementMarker $ mark {_markerElements = subElements }- where- mark = xmlUnparseWithDrawAttr e-unparseDefs e@(nodeName -> "mask") = do- children <- mapM unparse $ elChildren e- let realChildren = filter isNotNone children- parsedMask = xmlUnparseWithDrawAttr e- withId e . const . ElementMask $ parsedMask { _maskContent = realChildren }--unparseDefs e@(nodeName -> "clipPath") = do- children <- mapM unparse $ elChildren e- let realChildren = filter isNotNone children- parsedClip = xmlUnparseWithDrawAttr e- withId e . const . ElementClipPath $ parsedClip { _clipPathContent = realChildren }--unparseDefs e@(nodeName -> "linearGradient") =- withId e $ ElementLinearGradient . unparser- where- unparser ee =- xmlUnparse ee & linearGradientStops .~ parseGradientStops ee-unparseDefs e@(nodeName -> "radialGradient") =- withId e $ ElementRadialGradient . unparser- where- unparser ee =- xmlUnparse ee & radialGradientStops .~ parseGradientStops ee-unparseDefs e = do- el <- unparse e- withId e (const $ ElementGeometry el)--unparse :: X.Element -> State Symbols Tree-unparse e@(nodeName -> "style") = do- case parseOnly (many1 ruleSet) . T.pack $ strContent e of- Left _ -> return ()- Right rules ->- modify $ \s -> s { cssStyle = cssStyle s ++ rules }- return None-unparse e@(nodeName -> "defs") = do- mapM_ unparseDefs $ elChildren e- return None-unparse e@(nodeName -> "symbol") = do- symbolChildren <- mapM unparse $ elChildren e- let realChildren = filter isNotNone symbolChildren- pure . SymbolTree . Symbol $ groupNode & groupChildren .~ realChildren- where- groupNode :: Group Tree- groupNode = _groupOfSymbol $ xmlUnparseWithDrawAttr e--unparse e@(nodeName -> "g") = do- children <- mapM unparse $ elChildren e- let realChildren = filter isNotNone children-- groupNode :: Group Tree- groupNode = xmlUnparseWithDrawAttr e-- pure $ GroupTree $ groupNode & groupChildren .~ realChildren--unparse e@(nodeName -> "text") = do- pathWithGeometry <- pathGeomtryOf tPath- pure . TextTree pathWithGeometry $ xmlUnparse e & textRoot .~ root- where- (textContent, tPath) = unparseText $ X.elContent e-- pathGeomtryOf Nothing = pure Nothing- pathGeomtryOf (Just pathInfo) = do- pathElem <- gets $ M.lookup (_textPathName pathInfo) . symbols- case pathElem of- Nothing -> pure Nothing- Just (ElementLinearGradient _) -> pure Nothing- Just (ElementRadialGradient _) -> pure Nothing- Just (ElementPattern _) -> pure Nothing- Just (ElementMask _) -> pure Nothing- Just (ElementClipPath _) -> pure Nothing- Just (ElementMarker _) -> pure Nothing- Just (ElementGeometry (PathTree p)) ->- pure . Just $ pathInfo { _textPathData = _pathDefinition p }- Just (ElementGeometry _) -> pure Nothing-- root = TextSpan- { _spanInfo = xmlUnparse e- , _spanDrawAttributes = xmlUnparse e- , _spanContent = textContent- }--unparse e = pure $ case nodeName e of- "image" -> ImageTree parsed- "ellipse" -> EllipseTree parsed- "rect" -> RectangleTree parsed- "polyline" -> PolyLineTree parsed- "polygon" -> PolygonTree parsed- "circle"-> CircleTree parsed- "line" -> LineTree parsed- "path" -> PathTree parsed- "use" -> UseTree parsed Nothing- _ -> None- where- parsed :: (XMLUpdatable a, WithDrawAttributes a) => a- parsed = xmlUnparseWithDrawAttr e--unparseDocument :: FilePath -> X.Element -> Maybe Document-unparseDocument rootLocation e@(nodeName -> "svg") = Just Document- { _viewBox =- attributeFinder "viewBox" e >>= parse viewBoxParser- , _elements = parsedElements- , _width = lengthFind "width"- , _height = lengthFind "height"- , _definitions = symbols named- , _description = ""- , _styleRules = cssStyle named- , _documentLocation = rootLocation- }- where- (parsedElements, named) =- runState (mapM unparse $ elChildren e) emptyState- lengthFind n =- attributeFinder n e >>= parse complexNumber-unparseDocument _ _ = Nothing---- | Transform a SVG document to a XML node.-xmlOfDocument :: Document -> X.Element-xmlOfDocument doc =- X.node (X.unqual "svg") (attrs, descTag ++ styleTag ++ defsTag ++ children)- where- attr name = X.Attr (X.unqual name)- children = catMaybes [serializeTreeNode el | el <- _elements doc]-- defsTag | null defs = []- | otherwise = [X.node (X.unqual "defs") defs]-- defs = catMaybes [elementRender k e | (k, e) <- M.assocs $ _definitions doc]-- elementRender k e = case e of- ElementGeometry t -> serialize t- ElementMarker m -> serialize m- ElementMask m -> serialize m- ElementClipPath c -> serialize c- ElementPattern p -> serialize p- ElementLinearGradient lg -> addId $ serializeTreeNode lg- ElementRadialGradient rg -> addId $ serializeTreeNode rg- where- addId = fmap (X.add_attr $ attr "id" k)-- serialize :: (WithDrawAttributes e, XMLUpdatable e) => e -> Maybe X.Element- serialize el = case el^.drawAttr.attrId of- Nothing -> addId $ serializeTreeNode el- Just _id ->- let newNode = el & drawAttr.attrId .~ Just k in- serializeTreeNode newNode-- docViewBox = case _viewBox doc of- Nothing -> []- Just b -> [attr "viewBox" $ serializeViewBox b]-- descTag = case _description doc of- "" -> []- txt -> [X.node (X.unqual "desc") txt]-- styleTag = case _styleRules doc of- [] -> []- rules -> [X.node (X.unqual "style")- ([attr "type" "text/css"], txt)]- where txt = TL.unpack . TB.toLazyText $ foldMap tserialize rules-- attrs =- docViewBox ++- [attr "xmlns" "http://www.w3.org/2000/svg"- ,attr "xmlns:xlink" "http://www.w3.org/1999/xlink"- ,attr "version" "1.1"] ++- catMaybes [attr "width" . serializeNumber <$> _width doc- ,attr "height" . serializeNumber <$> _height doc- ]-+{-# LANGUAGE CPP #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FlexibleContexts #-} +module Graphics.Svg.XmlParser( xmlOfDocument + , unparseDocument + + , SvgAttributeLens( .. ) + , drawAttributesList + ) where + + +#if !MIN_VERSION_base(4,6,0) +import Text.Read( reads ) +#else +import Text.Read( readMaybe ) +#endif + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative( pure, (<$>), (<$), (<*>) ) +import Data.Foldable( foldMap ) +import Data.Monoid( mempty ) +#endif + +import Control.Applicative( (<|>), many ) + +import Control.Lens hiding( transform, children, elements, element ) +import Control.Monad.State.Strict( State, runState, modify, gets ) +import Data.Maybe( fromMaybe, catMaybes ) +import Data.Monoid( Last( Last ), getLast, (<>) ) +import Data.List( foldl', intercalate ) +import Text.XML.Light.Proc( findAttrBy, elChildren, strContent ) +import qualified Text.XML.Light as X +import qualified Data.Text as T +import qualified Data.Text.Lazy as TL +import qualified Data.Text.Lazy.Builder as TB +import qualified Data.Map as M +import Data.Attoparsec.Text( Parser, string, parseOnly, many1 ) +import Codec.Picture( PixelRGBA8( .. ) ) +import Graphics.Svg.Types +import Graphics.Svg.PathParser +import Graphics.Svg.ColorParser +import Graphics.Svg.CssTypes( CssDeclaration( .. ) + , CssElement( .. ) + , CssRule + , tserialize + ) +import Graphics.Svg.CssParser( complexNumber + , num + , ruleSet + , dashArray + , styleString + , numberList ) + +import Text.Printf( printf ) + +{-import Debug.Trace-} + +#if !MIN_VERSION_base(4,6,0) +readMaybe :: Read a => String -> Maybe a +readMaybe str = case reads str of + [] -> Nothing + (x, _):_ -> Just x +#endif + +nodeName :: X.Element -> String +nodeName = X.qName . X.elName + +attributeFinder :: String -> X.Element -> Maybe String +attributeFinder str = + findAttrBy (\a -> X.qName a == str) + +-- | Helper class to help simplify parsing code +-- for various attributes. +class ParseableAttribute a where + aparse :: String -> Maybe a + aserialize :: a -> Maybe String + +instance ParseableAttribute v => ParseableAttribute (Maybe v) where + aparse = fmap Just . aparse + aserialize = (>>= aserialize) + +instance ParseableAttribute v => ParseableAttribute (Last v) where + aparse = fmap Last . aparse + aserialize = aserialize . getLast + +instance ParseableAttribute String where + aparse = Just + aserialize = Just + +instance ParseableAttribute Number where + aparse = parseMayStartDot complexNumber + aserialize = Just . serializeNumber + +instance ParseableAttribute [Number] where + aparse = parse dashArray + aserialize = Just . serializeDashArray + +instance ParseableAttribute PixelRGBA8 where + aparse = parse colorParser + aserialize = Just . colorSerializer + +instance ParseableAttribute [PathCommand] where + aparse = parse pathParser + aserialize = Just . serializeCommands + +instance ParseableAttribute [RPoint] where + aparse = parse pointData + aserialize = Just . serializePoints + +instance ParseableAttribute Double where + aparse = parseMayStartDot num + aserialize v = Just $ printf "%g" v + +instance ParseableAttribute Texture where + aparse = parse textureParser + aserialize = Just . textureSerializer + +instance ParseableAttribute [Transformation] where + aparse = parse $ many transformParser + aserialize = Just . serializeTransformations + +instance ParseableAttribute Alignment where + aparse s = Just $ case s of + "none" -> AlignNone + "xMinYMin" -> AlignxMinYMin + "xMidYMin" -> AlignxMidYMin + "xMaxYMin" -> AlignxMaxYMin + "xMinYMid" -> AlignxMinYMid + "xMidYMid" -> AlignxMidYMid + "xMaxYMid" -> AlignxMaxYMid + "xMinYMax" -> AlignxMinYMax + "xMidYMax" -> AlignxMidYMax + "xMaxYMax" -> AlignxMaxYMax + _ -> _aspectRatioAlign defaultSvg + + aserialize v = Just $ case v of + AlignNone -> "none" + AlignxMinYMin -> "xMinYMin" + AlignxMidYMin -> "xMidYMin" + AlignxMaxYMin -> "xMaxYMin" + AlignxMinYMid -> "xMinYMid" + AlignxMidYMid -> "xMidYMid" + AlignxMaxYMid -> "xMaxYMid" + AlignxMinYMax -> "xMinYMax" + AlignxMidYMax -> "xMidYMax" + AlignxMaxYMax -> "xMaxYMax" + +instance ParseableAttribute MeetSlice where + aparse s = case s of + "meet" -> Just Meet + "slice" -> Just Slice + _ -> Nothing + + aserialize v = Just $ case v of + Meet -> "meet" + Slice -> "slice" + +instance ParseableAttribute PreserveAspectRatio where + aserialize v = Just $ defer <> align <> meetSlice where + defer = if _aspectRatioDefer v then "defer " else "" + align = fromMaybe "" . aserialize $ _aspectRatioAlign v + meetSlice = fromMaybe "" $ aserialize =<< _aspectRatioMeetSlice v + + aparse s = case words s of + [] -> Nothing + [align] -> Just $ defaultSvg { _aspectRatioAlign = alignOf align } + ["defer", align] -> + Just $ defaultSvg + { _aspectRatioDefer = True + , _aspectRatioAlign = alignOf align + } + [align, meet] -> + Just $ defaultSvg + { _aspectRatioMeetSlice = aparse meet + , _aspectRatioAlign = alignOf align + } + ["defer", align, meet] -> + Just $ PreserveAspectRatio + { _aspectRatioDefer = True + , _aspectRatioAlign = alignOf align + , _aspectRatioMeetSlice = aparse meet + } + _ -> Nothing + where + alignOf = fromMaybe (_aspectRatioAlign defaultSvg) . aparse + +instance ParseableAttribute Cap where + aparse s = case s of + "butt" -> Just CapButt + "round" -> Just CapRound + "square" -> Just CapSquare + _ -> Nothing + + aserialize c = Just $ case c of + CapButt -> "butt" + CapRound -> "round" + CapSquare -> "square" + +instance ParseableAttribute TextAnchor where + aparse s = case s of + "middle" -> Just TextAnchorMiddle + "start" -> Just TextAnchorStart + "end" -> Just TextAnchorEnd + _ -> Nothing + + aserialize t = Just $ case t of + TextAnchorMiddle -> "middle" + TextAnchorStart -> "start" + TextAnchorEnd -> "end" + +instance ParseableAttribute ElementRef where + aparse s = case parseOnly pa $ T.pack s of + Left _ -> Nothing + Right v -> Just v + where + pa = (RefNone <$ string "none") + <|> (Ref <$> urlRef) + + aserialize c = Just $ case c of + Ref r -> "url(#" <> r <> ")" + RefNone -> "none" + +instance ParseableAttribute LineJoin where + aparse s = case s of + "miter" -> Just JoinMiter + "round" -> Just JoinRound + "bevel" -> Just JoinBevel + _ -> Nothing + + aserialize j = Just $ case j of + JoinMiter -> "miter" + JoinRound -> "round" + JoinBevel -> "bevel" + +instance ParseableAttribute CoordinateUnits where + aparse s = case s of + "userSpaceOnUse" -> Just CoordUserSpace + "objectBoundingBox" -> Just CoordBoundingBox + _ -> Just CoordBoundingBox + + aserialize uni = Just $ case uni of + CoordUserSpace -> "userSpaceOnUse" + CoordBoundingBox -> "objectBoundingBox" + +instance ParseableAttribute Spread where + aparse s = case s of + "pad" -> Just SpreadPad + "reflect" -> Just SpreadReflect + "repeat" -> Just SpreadRepeat + _ -> Nothing + + aserialize s = Just $ case s of + SpreadPad -> "pad" + SpreadReflect -> "reflect" + SpreadRepeat -> "repeat" + +instance ParseableAttribute FillRule where + aparse s = case s of + "nonzero" -> Just FillNonZero + "evenodd" -> Just FillEvenOdd + _ -> Nothing + + aserialize f = Just $ case f of + FillNonZero -> "nonzero" + FillEvenOdd -> "evenodd" + +instance ParseableAttribute TextAdjust where + aparse s = Just $ case s of + "spacing" -> TextAdjustSpacing + "spacingAndGlyphs" -> TextAdjustSpacingAndGlyphs + _ -> TextAdjustSpacing + + aserialize a = Just $ case a of + TextAdjustSpacing -> "spacing" + TextAdjustSpacingAndGlyphs -> "spacingAndGlyphs" + +instance ParseableAttribute MarkerUnit where + aparse s = case s of + "strokeWidth" -> Just MarkerUnitStrokeWidth + "userSpaceOnUse" -> Just MarkerUnitUserSpaceOnUse + _ -> Nothing + + aserialize u = Just $ case u of + MarkerUnitStrokeWidth -> "strokeWidth" + MarkerUnitUserSpaceOnUse -> "userSpaceOnUse" + +instance ParseableAttribute Overflow where + aparse s = case s of + "visible" -> Just OverflowVisible + "hidden" -> Just OverflowHidden + _ -> Nothing + + aserialize u = Just $ case u of + OverflowVisible -> "visible" + OverflowHidden -> "hidden" + +instance ParseableAttribute MarkerOrientation where + aparse s = case (s, readMaybe s) of + ("auto", _) -> Just OrientationAuto + (_, Just f) -> Just $ OrientationAngle f + _ -> Nothing + + aserialize s = Just $ case s of + OrientationAuto -> "auto" + OrientationAngle f -> show f + +instance ParseableAttribute (Double, Double, Double, Double) where + aparse = parse viewBoxParser + aserialize = Just . serializeViewBox + +instance ParseableAttribute TextPathMethod where + aparse s = case s of + "align" -> Just TextPathAlign + "stretch" -> Just TextPathStretch + _ -> Nothing + aserialize m = Just $ case m of + TextPathAlign -> "align" + TextPathStretch -> "stretch" + +instance ParseableAttribute TextPathSpacing where + aparse s = case s of + "auto" -> Just TextPathSpacingAuto + "exact" -> Just TextPathSpacingExact + _ -> Nothing + + aserialize s = Just $ case s of + TextPathSpacingAuto -> "auto" + TextPathSpacingExact -> "exact" + +parse :: Parser a -> String -> Maybe a +parse p str = case parseOnly p (T.pack str) of + Left _ -> Nothing + Right r -> Just r + +parseMayStartDot :: Parser a -> String -> Maybe a +parseMayStartDot p l@('.':_) = parse p ('0':l) +parseMayStartDot p l = parse p l + +xmlUpdate :: (XMLUpdatable a) => a -> X.Element -> a +xmlUpdate initial el = foldl' grab initial attributes + where + grab value updater = + case attributeFinder (_attributeName updater) el of + Nothing -> value + Just v -> _attributeUpdater updater value v + +xmlUnparse :: (XMLUpdatable a) => X.Element -> a +xmlUnparse = xmlUpdate defaultSvg + +xmlUnparseWithDrawAttr + :: (XMLUpdatable a, WithDrawAttributes a) + => X.Element -> a +xmlUnparseWithDrawAttr e = + xmlUnparse e & drawAttr .~ xmlUnparse e + +data SvgAttributeLens t = SvgAttributeLens + { _attributeName :: String + , _attributeUpdater :: t -> String -> t + , _attributeSerializer :: t -> Maybe String + } + +class (WithDefaultSvg treeNode) => XMLUpdatable treeNode where + xmlTagName :: treeNode -> String + attributes :: [SvgAttributeLens treeNode] + + serializeTreeNode :: treeNode -> Maybe X.Element + +setChildren :: X.Element -> [X.Content] -> X.Element +setChildren xNode children = xNode { X.elContent = children } + +updateWithAccessor :: XMLUpdatable b => (a -> [b]) -> a -> Maybe X.Element -> Maybe X.Element +updateWithAccessor _ _ Nothing = Nothing +updateWithAccessor accessor node (Just xNode) = + Just . setChildren xNode . fmap X.Elem . catMaybes $ serializeTreeNode <$> accessor node + +genericSerializeNode :: (XMLUpdatable treeNode) => treeNode -> Maybe X.Element +genericSerializeNode node = + Just . X.unode (xmlTagName node) $ concatMap generateAttribute attributes + where + generateAttribute attr = case _attributeSerializer attr node of + Nothing -> [] + Just str -> return X.Attr + { X.attrKey = xName $ _attributeName attr + , X.attrVal = str + } + where + xName "href" = + X.QName { X.qName = "href" + , X.qURI = Nothing + , X.qPrefix = Just "xlink" } + xName h = X.unqual h + + +mergeAttributes :: X.Element -> X.Element -> X.Element +mergeAttributes thisXml otherXml = + thisXml { X.elAttribs = X.elAttribs otherXml ++ X.elAttribs thisXml } + +genericSerializeWithDrawAttr :: (XMLUpdatable treeNode, WithDrawAttributes treeNode) + => treeNode -> Maybe X.Element +genericSerializeWithDrawAttr node = mergeAttributes <$> thisXml <*> drawAttrNode where + thisXml = genericSerializeNode node + drawAttrNode = genericSerializeNode $ node ^. drawAttr + +type CssUpdater = + DrawAttributes -> [[CssElement]] -> DrawAttributes + +opacitySetter :: String -> Lens' a (Maybe Float) -> SvgAttributeLens a +opacitySetter attribute elLens = + SvgAttributeLens attribute updater serializer + where + serializer a = printf "%g" <$> a ^. elLens + updater el str = case parseMayStartDot num str of + Nothing -> el + Just v -> el & elLens .~ Just (realToFrac v) + +type Serializer e = e -> Maybe String + +parserSetter :: String -> Lens' a e -> (String -> Maybe e) -> Serializer e + -> SvgAttributeLens a +parserSetter attribute elLens parser serialize = + SvgAttributeLens attribute updater serializer + where + updater el str = case parser str of + Nothing -> el + Just v -> el & elLens .~ v + + serializer a = serialize $ a ^. elLens + +parseIn :: (Eq a, WithDefaultSvg s, ParseableAttribute a) + => String -> Lens' s a -> SvgAttributeLens s +parseIn attribute elLens = + SvgAttributeLens attribute updater serializer + where + updater el str = case aparse str of + Nothing -> el + Just v -> el & elLens .~ v + + serializer a + | v /= defaultVal = aserialize v + | otherwise = Nothing + where + v = a ^. elLens + defaultVal = defaultSvg ^. elLens + +parserLastSetter :: String -> Lens' a (Last e) -> (String -> Maybe e) -> Serializer e + -> SvgAttributeLens a +parserLastSetter attribute elLens parser serialize = + SvgAttributeLens attribute updater serializer + where + updater el str = case parser str of + Nothing -> el + Just v -> el & elLens .~ Last (Just v) + + serializer a = getLast (a ^. elLens) >>= serialize + +classSetter :: SvgAttributeLens DrawAttributes +classSetter = SvgAttributeLens "class" updater serializer + where + updater el str = + el & attrClass .~ (T.split (== ' ') $ T.pack str) + + serializer a = case a ^. attrClass of + [] -> Nothing + lst -> Just . T.unpack $ T.intercalate " " lst + +cssUniqueNumber :: ASetter DrawAttributes DrawAttributes + a (Last Number) + -> CssUpdater +cssUniqueNumber setter attr ((CssNumber n:_):_) = + attr & setter .~ Last (Just n) +cssUniqueNumber _ attr _ = attr + +cssUniqueFloat :: (Fractional n) + => ASetter DrawAttributes DrawAttributes a (Maybe n) + -> CssUpdater +cssUniqueFloat setter attr ((CssNumber (Num n):_):_) = + attr & setter .~ Just (realToFrac n) +cssUniqueFloat _ attr _ = attr + +cssUniqueMayFloat :: ASetter DrawAttributes DrawAttributes a (Last Double) + -> CssUpdater +cssUniqueMayFloat setter attr ((CssNumber (Num n):_):_) = + attr & setter .~ Last (Just n) +cssUniqueMayFloat _ attr _ = attr + +cssIdentAttr :: ParseableAttribute a => Lens' DrawAttributes a -> CssUpdater +cssIdentAttr setter attr ((CssIdent i:_):_) = case aparse $ T.unpack i of + Nothing -> attr + Just v -> attr & setter .~ v +cssIdentAttr _ attr _ = attr + +fontFamilyParser :: CssUpdater +fontFamilyParser attr (lst:_) = attr & fontFamily .~ fontNames + where + fontNames = Last . Just $ T.unpack <$> extractString lst + + extractString [] = [] + extractString (CssIdent n:rest) = n : extractString rest + extractString (CssString n:rest) = n : extractString rest + extractString (_:rest) = extractString rest +fontFamilyParser attr _ = attr + + +cssUniqueTexture :: ASetter DrawAttributes DrawAttributes + a (Last Texture) + -> CssUpdater +cssUniqueTexture setter attr ((CssIdent "none":_):_) = + attr & setter .~ Last (Just FillNone) +cssUniqueTexture setter attr ((CssColor c:_):_) = + attr & setter .~ Last (Just $ ColorRef c) +cssUniqueTexture setter attr ((CssFunction "url" [CssReference c]:_):_) = + attr & setter .~ Last (Just . TextureRef $ T.unpack c) +cssUniqueTexture _ attr _ = attr + +cssElementRefSetter :: Lens' DrawAttributes (Last ElementRef) + -> CssUpdater +cssElementRefSetter setter attr ((CssFunction "url" [CssReference c]:_):_) = + attr & setter .~ Last (Just . Ref $ T.unpack c) +cssElementRefSetter setter attr ((CssIdent "none":_):_) = + attr & setter .~ Last (Just RefNone) +cssElementRefSetter _ attr _ = attr + +cssMayStringSetter :: ASetter DrawAttributes DrawAttributes a (Maybe String) + -> CssUpdater +cssMayStringSetter setter attr ((CssIdent i:_):_) = + attr & setter .~ Just (T.unpack i) +cssMayStringSetter setter attr ((CssString i:_):_) = + attr & setter .~ Just (T.unpack i) +cssMayStringSetter _ attr _ = attr + +cssNullSetter :: CssUpdater +cssNullSetter attr _ = attr + +cssDashArray :: ASetter DrawAttributes DrawAttributes a (Last [Number]) + -> CssUpdater +cssDashArray setter attr (lst:_) = + case [n | CssNumber n <- lst ] of + [] -> attr + v -> attr & setter .~ Last (Just v) +cssDashArray _ attr _ = attr + + +drawAttributesList :: [(SvgAttributeLens DrawAttributes, CssUpdater)] +drawAttributesList = + [("stroke-width" `parseIn` strokeWidth, cssUniqueNumber strokeWidth) + ,("stroke" `parseIn` strokeColor, cssUniqueTexture strokeColor) + ,("fill" `parseIn` fillColor, cssUniqueTexture fillColor) + ,("stroke-linecap" `parseIn` strokeLineCap, cssIdentAttr strokeLineCap) + ,("stroke-linejoin" `parseIn` strokeLineJoin, cssIdentAttr strokeLineJoin) + ,("stroke-miterlimit" `parseIn` strokeMiterLimit, + cssUniqueMayFloat strokeMiterLimit) + + ,("transform" `parseIn` transform, const) + ,(opacitySetter "opacity" groupOpacity, cssUniqueFloat groupOpacity) + ,(opacitySetter "fill-opacity" fillOpacity, cssUniqueFloat fillOpacity) + ,(opacitySetter "stroke-opacity" strokeOpacity, cssUniqueFloat strokeOpacity) + ,("font-size" `parseIn` fontSize, cssUniqueNumber fontSize) + ,(parserLastSetter "font-family" fontFamily (Just . commaSeparate) + (Just . intercalate ", "), fontFamilyParser) + + ,("fill-rule" `parseIn` fillRule, cssIdentAttr fillRule) + ,("clip-rule" `parseIn` clipRule, cssIdentAttr clipRule) + ,("mask" `parseIn` maskRef, cssElementRefSetter maskRef) + ,(classSetter, cssNullSetter) -- can't set class in CSS + ,("id" `parseIn` attrId, cssMayStringSetter attrId) + ,("stroke-dashoffset" `parseIn` strokeOffset, + cssUniqueNumber strokeOffset) + ,("stroke-dasharray" `parseIn` strokeDashArray, cssDashArray strokeDashArray) + ,("text-anchor" `parseIn` textAnchor, cssIdentAttr textAnchor) + ,("clip-path" `parseIn` clipPathRef, cssElementRefSetter clipPathRef) + ,("marker-end" `parseIn` markerEnd, cssElementRefSetter markerEnd) + ,("marker-start" `parseIn` markerStart, cssElementRefSetter markerStart) + ,("marker-mid" `parseIn` markerMid, cssElementRefSetter markerMid) + ] + where + commaSeparate = + fmap (T.unpack . T.strip) . T.split (',' ==) . T.pack + +serializeDashArray :: [Number] -> String +serializeDashArray = + intercalate ", " . fmap serializeNumber + +instance XMLUpdatable DrawAttributes where + xmlTagName _ = "DRAWATTRIBUTES" + attributes = styleAttribute : fmap fst drawAttributesList + serializeTreeNode = genericSerializeNode + +styleAttribute :: SvgAttributeLens DrawAttributes +styleAttribute = SvgAttributeLens + { _attributeName = "style" + , _attributeUpdater = updater + , _attributeSerializer = const Nothing + } + where + updater attrs style = case parse styleString style of + Nothing -> attrs + Just decls -> foldl' applyer attrs decls + + cssUpdaters = [(T.pack $ _attributeName n, u) | (n, u) <- drawAttributesList] + applyer value (CssDeclaration txt elems) = + case lookup txt cssUpdaters of + Nothing -> value + Just f -> f value elems + +instance XMLUpdatable Rectangle where + xmlTagName _ = "rect" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = + ["width" `parseIn` rectWidth + ,"height" `parseIn` rectHeight + ,"x" `parseIn` (rectUpperLeftCorner._1) + ,"y" `parseIn` (rectUpperLeftCorner._2) + ,"rx" `parseIn` (rectCornerRadius._1) + ,"ry" `parseIn` (rectCornerRadius._2) + ] + +instance XMLUpdatable Image where + xmlTagName _ = "image" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = + ["width" `parseIn` imageWidth + ,"height" `parseIn` imageHeight + ,"x" `parseIn` (imageCornerUpperLeft._1) + ,"y" `parseIn` (imageCornerUpperLeft._2) + ,parserSetter "href" imageHref (Just . dropSharp) Just + ,"preserveAspectRatio" `parseIn` imageAspectRatio + ] + +instance XMLUpdatable Line where + xmlTagName _ = "line" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = + ["x1" `parseIn` (linePoint1._1) + ,"y1" `parseIn` (linePoint1._2) + ,"x2" `parseIn` (linePoint2._1) + ,"y2" `parseIn` (linePoint2._2) + ] + +instance XMLUpdatable Ellipse where + xmlTagName _ = "ellipse" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = + ["cx" `parseIn` (ellipseCenter._1) + ,"cy" `parseIn` (ellipseCenter._2) + ,"rx" `parseIn` ellipseXRadius + ,"ry" `parseIn` ellipseYRadius + ] + +instance XMLUpdatable Circle where + xmlTagName _ = "circle" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = + ["cx" `parseIn` (circleCenter._1) + ,"cy" `parseIn` (circleCenter._2) + ,"r" `parseIn` circleRadius + ] + +instance XMLUpdatable Mask where + xmlTagName _ = "mask" + serializeTreeNode node = + updateWithAccessor _maskContent node $ + genericSerializeWithDrawAttr node + + attributes = + ["x" `parseIn` (maskPosition._1) + ,"y" `parseIn` (maskPosition._2) + ,"width" `parseIn` maskWidth + ,"height" `parseIn` maskHeight + ,"maskContentUnits" `parseIn` maskContentUnits + ,"maskUnits" `parseIn` maskUnits + ] + +instance XMLUpdatable ClipPath where + xmlTagName _ = "clipPath" + serializeTreeNode node = + updateWithAccessor _clipPathContent node $ + genericSerializeWithDrawAttr node + attributes = + ["clipPathUnits" `parseIn` clipPathUnits] + +instance XMLUpdatable Polygon where + xmlTagName _ = "polygon" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = ["points" `parseIn` polygonPoints] + +instance XMLUpdatable PolyLine where + xmlTagName _ = "polyline" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = ["points" `parseIn` polyLinePoints] + +instance XMLUpdatable Path where + xmlTagName _ = "path" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = ["d" `parseIn` pathDefinition] + +instance XMLUpdatable LinearGradient where + xmlTagName _ = "linearGradient" + serializeTreeNode node = + updateWithAccessor _linearGradientStops node $ genericSerializeNode node + + attributes = + ["gradientTransform" `parseIn` linearGradientTransform + ,"gradientUnits" `parseIn` linearGradientUnits + ,"spreadMethod" `parseIn` linearGradientSpread + ,"x1" `parseIn` (linearGradientStart._1) + ,"y1" `parseIn` (linearGradientStart._2) + ,"x2" `parseIn` (linearGradientStop._1) + ,"y2" `parseIn` (linearGradientStop._2) + ] + +instance XMLUpdatable Tree where + xmlTagName _ = "TREE" + attributes = [] + serializeTreeNode e = case e of + None -> Nothing + UseTree u _ -> serializeTreeNode u + GroupTree g -> serializeTreeNode g + SymbolTree s -> serializeTreeNode s + PathTree p -> serializeTreeNode p + CircleTree c -> serializeTreeNode c + PolyLineTree p -> serializeTreeNode p + PolygonTree p -> serializeTreeNode p + EllipseTree el -> serializeTreeNode el + LineTree l -> serializeTreeNode l + RectangleTree r -> serializeTreeNode r + TextTree Nothing t -> serializeTreeNode t + ImageTree i -> serializeTreeNode i + TextTree (Just p) t -> do + textNode <- serializeTreeNode t + pathNode <- serializeTreeNode p + let sub = [X.Elem . setChildren pathNode $ X.elContent textNode] + return $ setChildren textNode sub + + +isNotNone :: Tree -> Bool +isNotNone None = False +isNotNone _ = True + +instance XMLUpdatable (Group Tree) where + xmlTagName _ = "g" + serializeTreeNode node = + updateWithAccessor (filter isNotNone . _groupChildren) node $ + genericSerializeWithDrawAttr node + attributes = [] + +instance XMLUpdatable (Symbol Tree) where + xmlTagName _ = "symbol" + serializeTreeNode node = + updateWithAccessor (filter isNotNone . _groupChildren . _groupOfSymbol) node $ + genericSerializeWithDrawAttr node + attributes = + ["viewBox" `parseIn` (groupOfSymbol . groupViewBox) + ,"preserveAspectRatio" `parseIn` (groupOfSymbol . groupAspectRatio) + ] + + +instance XMLUpdatable RadialGradient where + xmlTagName _ = "radialGradient" + serializeTreeNode node = + updateWithAccessor _radialGradientStops node $ genericSerializeNode node + attributes = + ["gradientTransform" `parseIn` radialGradientTransform + ,"gradientUnits" `parseIn` radialGradientUnits + ,"spreadMethod" `parseIn` radialGradientSpread + ,"cx" `parseIn` (radialGradientCenter._1) + ,"cy" `parseIn` (radialGradientCenter._2) + ,"r" `parseIn` radialGradientRadius + ,"fx" `parseIn` radialGradientFocusX + ,"fy" `parseIn` radialGradientFocusY + ] + +instance XMLUpdatable Use where + xmlTagName _ = "use" + serializeTreeNode = genericSerializeWithDrawAttr + attributes = + ["x" `parseIn` (useBase._1) + ,"y" `parseIn` (useBase._2) + ,"width" `parseIn` useWidth + ,"height" `parseIn` useHeight + ,parserSetter "href" useName (Just . dropSharp) (Just . ('#':)) + ] + +dropSharp :: String -> String +dropSharp ('#':rest) = rest +dropSharp a = a + +instance XMLUpdatable TextInfo where + xmlTagName _ = "tspan" + serializeTreeNode = genericSerializeNode + attributes = + [parserSetter "x" textInfoX (parse dashArray) dashNotEmpty + ,parserSetter "y" textInfoY (parse dashArray) dashNotEmpty + ,parserSetter "dx" textInfoDX (parse dashArray) dashNotEmpty + ,parserSetter "dy" textInfoDY (parse dashArray) dashNotEmpty + ,parserSetter "rotate" textInfoRotate + (parse numberList) + rotateNotEmpty + ,"textLength" `parseIn` textInfoLength + ] + where + dashNotEmpty [] = Nothing + dashNotEmpty lst = Just $ serializeDashArray lst + + rotateNotEmpty [] = Nothing + rotateNotEmpty lst = + Just . unwords $ printf "%g" <$> lst + + +instance XMLUpdatable TextPath where + xmlTagName _ = "textPath" + serializeTreeNode = genericSerializeNode + attributes = + ["startOffset" `parseIn` textPathStartOffset + ,"method" `parseIn` textPathMethod + ,"spacing" `parseIn` textPathSpacing + ,parserSetter "href" textPathName (Just . dropSharp) (Just . ('#':)) + ] + +instance XMLUpdatable Text where + xmlTagName _ = "text" + serializeTreeNode = serializeText + attributes = ["lengthAdjust" `parseIn` textAdjust] + + +instance XMLUpdatable Pattern where + xmlTagName _ = "pattern" + serializeTreeNode node = + updateWithAccessor _patternElements node $ genericSerializeWithDrawAttr node + attributes = + ["viewBox" `parseIn` patternViewBox + ,"patternUnits" `parseIn` patternUnit + ,"width" `parseIn` patternWidth + ,"height" `parseIn` patternHeight + ,"x" `parseIn` (patternPos._1) + ,"y" `parseIn` (patternPos._2) + ,"preserveAspectRatio" `parseIn` patternAspectRatio + ] + +instance XMLUpdatable Marker where + xmlTagName _ = "marker" + serializeTreeNode node = + updateWithAccessor _markerElements node $ genericSerializeWithDrawAttr node + attributes = + ["refX" `parseIn` (markerRefPoint._1) + ,"refY" `parseIn` (markerRefPoint._2) + ,"markerWidth" `parseIn` markerWidth + ,"markerHeight" `parseIn` markerHeight + ,"patternUnits" `parseIn` markerUnits + ,"orient" `parseIn` markerOrient + ,"viewBox" `parseIn` markerViewBox + ,"overflow" `parseIn` markerOverflow + ,"preserveAspectRatio" `parseIn` markerAspectRatio + ] + +serializeText :: Text -> Maybe X.Element +serializeText topText = namedNode where + namedNode = fmap (\x -> x { X.elName = X.unqual "text" }) topNode + topNode = serializeSpan $ _textRoot topText + + serializeSpan tspan = case (info, drawInfo) of + (Nothing, Nothing) -> Nothing + (Just a, Nothing) -> Just $ setChildren a subContent + (Nothing, Just b) -> Just $ setChildren b subContent + (Just a, Just b) -> + Just $ setChildren (mergeAttributes a b) subContent + where + info = genericSerializeNode $ _spanInfo tspan + drawInfo = genericSerializeNode $ _spanDrawAttributes tspan + subContent = catMaybes $ serializeContent <$> _spanContent tspan + + serializeContent (SpanText t) = Just . X.Text $ X.blank_cdata { X.cdData = T.unpack t } + serializeContent (SpanTextRef _t) = Just . X.Text $ X.blank_cdata { X.cdData = "" } + serializeContent (SpanSub sub) = X.Elem <$> serializeSpan sub + +unparseText :: [X.Content] -> ([TextSpanContent], Maybe TextPath) +unparseText = extractResult . go True + where + extractResult (a, b, _) = (a, b) + + go startStrip [] = ([], Nothing, startStrip) + go startStrip (X.CRef _:rest) = go startStrip rest + go startStrip (X.Elem e@(nodeName -> "tspan"):rest) = + (SpanSub spans : trest, mpath, retStrip) + where + (trest, mpath, retStrip) = go restStrip rest + (sub, _, restStrip) = go startStrip $ X.elContent e + spans = TextSpan (xmlUnparse e) (xmlUnparse e) sub + + go startStrip (X.Elem e@(nodeName -> "tref"):rest) = + case attributeFinder "href" e of + Nothing -> go startStrip rest + Just v -> (SpanTextRef v : trest, mpath, stripRet) + where (trest, mpath, stripRet) = go startStrip rest + + go startStrip (X.Elem e@(nodeName -> "textPath"):rest) = + case attributeFinder "href" e of + Nothing -> go startStrip rest + Just v -> (tsub ++ trest, pure p, retStrp) + where + p = (xmlUnparse e) { _textPathName = dropSharp v } + (trest, _, retStrp) = go restStrip rest + (tsub, _, restStrip) = go startStrip $ X.elContent e + + go startStrip (X.Elem _:rest) = go startStrip rest + go startStrip (X.Text t:rest) + | T.length cleanText == 0 = go startStrip rest + | otherwise = + (SpanText cleanText : trest, mpath, stripRet) + where + (trest, mpath, stripRet) = go subShouldStrip rest + + subShouldStrip = T.pack " " `T.isSuffixOf` cleanText + + space = T.singleton ' ' + singulariseSpaces tt + | space `T.isPrefixOf` tt = space + | otherwise = tt + + stripStart | startStrip = T.stripStart + | otherwise = id + + cleanText = stripStart + . T.concat + . fmap singulariseSpaces + . T.groupBy (\a b -> (a /= ' ' && b /= ' ') || a == b) + . T.filter (\c -> c /= '\n' && c /= '\r') + . T.map (\c -> if c == '\t' then ' ' else c) + . T.pack + $ X.cdData t + +gradientOffsetSetter :: SvgAttributeLens GradientStop +gradientOffsetSetter = SvgAttributeLens "offset" setter serialize + where + serialize a = Just $ printf "%d%%" percentage + where percentage = floor . (100 *) $ a ^. gradientOffset :: Int + + setter el str = el & gradientOffset .~ val + where + val = realToFrac $ case parseMayStartDot complexNumber str of + Nothing -> 0 + Just (Num n) -> n + Just (Px n) -> n + Just (Percent n) -> n + Just (Em n) -> n + Just (Pc n) -> n + Just (Mm n) -> n + Just (Cm n) -> n + Just (Point n) -> n + Just (Inches n) -> n + +instance XMLUpdatable GradientStop where + xmlTagName _ = "stop" + serializeTreeNode = genericSerializeNode + attributes = + [gradientOffsetSetter + ,"stop-color" `parseIn` gradientColor + ] + + +data Symbols = Symbols + { symbols :: !(M.Map String Element) + , cssStyle :: [CssRule] + } + +emptyState :: Symbols +emptyState = Symbols mempty mempty + +parseGradientStops :: X.Element -> [GradientStop] +parseGradientStops = concatMap unStop . elChildren + where + unStop e@(nodeName -> "stop") = [xmlUnparse e] + unStop _ = [] + +withId :: X.Element -> (X.Element -> Element) + -> State Symbols Tree +withId el f = case attributeFinder "id" el of + Nothing -> return None + Just elemId -> do + modify $ \s -> + s { symbols = M.insert elemId (f el) $ symbols s } + return None + +unparseDefs :: X.Element -> State Symbols Tree +unparseDefs e@(nodeName -> "pattern") = do + subElements <- mapM unparse $ elChildren e + withId e . const . ElementPattern $ pat { _patternElements = subElements} + where + pat = xmlUnparse e +unparseDefs e@(nodeName -> "marker") = do + subElements <- mapM unparse $ elChildren e + withId e . const . ElementMarker $ mark {_markerElements = subElements } + where + mark = xmlUnparseWithDrawAttr e +unparseDefs e@(nodeName -> "mask") = do + children <- mapM unparse $ elChildren e + let realChildren = filter isNotNone children + parsedMask = xmlUnparseWithDrawAttr e + withId e . const . ElementMask $ parsedMask { _maskContent = realChildren } + +unparseDefs e@(nodeName -> "clipPath") = do + children <- mapM unparse $ elChildren e + let realChildren = filter isNotNone children + parsedClip = xmlUnparseWithDrawAttr e + withId e . const . ElementClipPath $ parsedClip { _clipPathContent = realChildren } + +unparseDefs e@(nodeName -> "linearGradient") = + withId e $ ElementLinearGradient . unparser + where + unparser ee = + xmlUnparse ee & linearGradientStops .~ parseGradientStops ee +unparseDefs e@(nodeName -> "radialGradient") = + withId e $ ElementRadialGradient . unparser + where + unparser ee = + xmlUnparse ee & radialGradientStops .~ parseGradientStops ee +unparseDefs e = do + el <- unparse e + withId e (const $ ElementGeometry el) + +unparse :: X.Element -> State Symbols Tree +unparse e@(nodeName -> "style") = do + case parseOnly (many1 ruleSet) . T.pack $ strContent e of + Left _ -> return () + Right rules -> + modify $ \s -> s { cssStyle = cssStyle s ++ rules } + return None +unparse e@(nodeName -> "defs") = do + mapM_ unparseDefs $ elChildren e + return None +unparse e@(nodeName -> "symbol") = do + symbolChildren <- mapM unparse $ elChildren e + let realChildren = filter isNotNone symbolChildren + pure . SymbolTree . Symbol $ groupNode & groupChildren .~ realChildren + where + groupNode :: Group Tree + groupNode = _groupOfSymbol $ xmlUnparseWithDrawAttr e + +unparse e@(nodeName -> "g") = do + children <- mapM unparse $ elChildren e + let realChildren = filter isNotNone children + + groupNode :: Group Tree + groupNode = xmlUnparseWithDrawAttr e + + pure $ GroupTree $ groupNode & groupChildren .~ realChildren + +unparse e@(nodeName -> "text") = do + pathWithGeometry <- pathGeomtryOf tPath + pure . TextTree pathWithGeometry $ xmlUnparse e & textRoot .~ root + where + (textContent, tPath) = unparseText $ X.elContent e + + pathGeomtryOf Nothing = pure Nothing + pathGeomtryOf (Just pathInfo) = do + pathElem <- gets $ M.lookup (_textPathName pathInfo) . symbols + case pathElem of + Nothing -> pure Nothing + Just (ElementLinearGradient _) -> pure Nothing + Just (ElementRadialGradient _) -> pure Nothing + Just (ElementPattern _) -> pure Nothing + Just (ElementMask _) -> pure Nothing + Just (ElementClipPath _) -> pure Nothing + Just (ElementMarker _) -> pure Nothing + Just (ElementGeometry (PathTree p)) -> + pure . Just $ pathInfo { _textPathData = _pathDefinition p } + Just (ElementGeometry _) -> pure Nothing + + root = TextSpan + { _spanInfo = xmlUnparse e + , _spanDrawAttributes = xmlUnparse e + , _spanContent = textContent + } + +unparse e = pure $ case nodeName e of + "image" -> ImageTree parsed + "ellipse" -> EllipseTree parsed + "rect" -> RectangleTree parsed + "polyline" -> PolyLineTree parsed + "polygon" -> PolygonTree parsed + "circle"-> CircleTree parsed + "line" -> LineTree parsed + "path" -> PathTree parsed + "use" -> UseTree parsed Nothing + _ -> None + where + parsed :: (XMLUpdatable a, WithDrawAttributes a) => a + parsed = xmlUnparseWithDrawAttr e + +unparseDocument :: FilePath -> X.Element -> Maybe Document +unparseDocument rootLocation e@(nodeName -> "svg") = Just Document + { _viewBox = + attributeFinder "viewBox" e >>= parse viewBoxParser + , _elements = parsedElements + , _width = lengthFind "width" + , _height = lengthFind "height" + , _definitions = symbols named + , _description = "" + , _styleRules = cssStyle named + , _documentLocation = rootLocation + } + where + (parsedElements, named) = + runState (mapM unparse $ elChildren e) emptyState + lengthFind n = + attributeFinder n e >>= parse complexNumber +unparseDocument _ _ = Nothing + +-- | Transform a SVG document to a XML node. +xmlOfDocument :: Document -> X.Element +xmlOfDocument doc = + X.node (X.unqual "svg") (attrs, descTag ++ styleTag ++ defsTag ++ children) + where + attr name = X.Attr (X.unqual name) + children = catMaybes [serializeTreeNode el | el <- _elements doc] + + defsTag | null defs = [] + | otherwise = [X.node (X.unqual "defs") defs] + + defs = catMaybes [elementRender k e | (k, e) <- M.assocs $ _definitions doc] + + elementRender k e = case e of + ElementGeometry t -> serialize t + ElementMarker m -> serialize m + ElementMask m -> serialize m + ElementClipPath c -> serialize c + ElementPattern p -> serialize p + ElementLinearGradient lg -> addId $ serializeTreeNode lg + ElementRadialGradient rg -> addId $ serializeTreeNode rg + where + addId = fmap (X.add_attr $ attr "id" k) + + serialize :: (WithDrawAttributes e, XMLUpdatable e) => e -> Maybe X.Element + serialize el = case el^.drawAttr.attrId of + Nothing -> addId $ serializeTreeNode el + Just _id -> + let newNode = el & drawAttr.attrId .~ Just k in + serializeTreeNode newNode + + docViewBox = case _viewBox doc of + Nothing -> [] + Just b -> [attr "viewBox" $ serializeViewBox b] + + descTag = case _description doc of + "" -> [] + txt -> [X.node (X.unqual "desc") txt] + + styleTag = case _styleRules doc of + [] -> [] + rules -> [X.node (X.unqual "style") + ([attr "type" "text/css"], txt)] + where txt = TL.unpack . TB.toLazyText $ foldMap tserialize rules + + attrs = + docViewBox ++ + [attr "xmlns" "http://www.w3.org/2000/svg" + ,attr "xmlns:xlink" "http://www.w3.org/1999/xlink" + ,attr "version" "1.1"] ++ + catMaybes [attr "width" . serializeNumber <$> _width doc + ,attr "height" . serializeNumber <$> _height doc + ] +
svg-tree.cabal view
@@ -1,61 +1,61 @@-name: svg-tree-version: 0.5.1.1-synopsis: SVG file loader and serializer-description:- svg-tree provides types representing a SVG document,- and allows to load and save it.- .- The types definition are aimed at rendering,- so they are rather comple. For simpler SVG document building,- look after `lucid-svg`.- .- To render an svg document you can use the `rasterific-svg` package---license: BSD3-author: Vincent Berthoux-maintainer: Vincent Berthoux--- copyright:-category: Graphics, Svg-build-type: Simple-cabal-version: >=1.10--extra-source-files: changelog.md--Source-Repository head- Type: git- Location: git://github.com/Twinside/svg-tree.git--Source-Repository this- Type: git- Location: git://github.com/Twinside/svg-tree.git- Tag: v0.5.1.1--library- hs-source-dirs: src- Ghc-options: -O3 -Wall- default-language: Haskell2010- exposed-modules: Graphics.Svg- , Graphics.Svg.CssTypes- , Graphics.Svg.Types-- other-modules: Graphics.Svg.NamedColors- , Graphics.Svg.XmlParser- , Graphics.Svg.PathParser- , Graphics.Svg.CssParser- , Graphics.Svg.ColorParser-- build-depends: base >= 4.5 && < 5- , JuicyPixels >= 3.2- , attoparsec >= 0.12- , scientific >= 0.3- , containers >= 0.4- , xml >= 1.3- , bytestring >= 0.10- , linear >= 1.20- , vector >= 0.10- , text >= 1.1- , transformers >= 0.3 && < 0.6- , mtl >= 2.1 && < 2.3- , lens >= 4.6 && < 5-+name: svg-tree +version: 0.5.1.2 +synopsis: SVG file loader and serializer +description: + svg-tree provides types representing a SVG document, + and allows to load and save it. + . + The types definition are aimed at rendering, + so they are rather comple. For simpler SVG document building, + look after `lucid-svg`. + . + To render an svg document you can use the `rasterific-svg` package + + +license: BSD3 +author: Vincent Berthoux +maintainer: Vincent Berthoux +-- copyright: +category: Graphics, Svg +build-type: Simple +cabal-version: >=1.10 + +extra-source-files: changelog.md + +Source-Repository head + Type: git + Location: git://github.com/Twinside/svg-tree.git + +Source-Repository this + Type: git + Location: git://github.com/Twinside/svg-tree.git + Tag: v0.5.1.2 + +library + hs-source-dirs: src + Ghc-options: -O3 -Wall + default-language: Haskell2010 + exposed-modules: Graphics.Svg + , Graphics.Svg.CssTypes + , Graphics.Svg.Types + + other-modules: Graphics.Svg.NamedColors + , Graphics.Svg.XmlParser + , Graphics.Svg.PathParser + , Graphics.Svg.CssParser + , Graphics.Svg.ColorParser + + build-depends: base >= 4.5 && < 5 + , JuicyPixels >= 3.2 + , attoparsec >= 0.12 + , scientific >= 0.3 + , containers >= 0.4 + , xml >= 1.3 + , bytestring >= 0.10 + , linear >= 1.20 + , vector >= 0.10 + , text >= 1.1 + , transformers >= 0.3 && < 0.6 + , mtl >= 2.1 && < 2.3 + , lens >= 4.6 && < 5 +