namespace pxt . svgUtil { export type Map < T > = { [ index ] : T ; } ; export type PointerHandler = ( ) => void ; export enum PatternUnits { userSpaceOnUse = 0 , objectBoundingBox = 1 } export enum LengthUnit { em , ex , px , in , cm , mm , pt , pc , percent } const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink" ; export class BaseElement < T extends SVGElement > { el : T ; protected titleElement : SVGTitleElement ; constructor ( type ) { this . el = elt ( type ) as T ; } attr ( attributes ) { Object . keys ( attributes ) . forEach ( at => { this . setAttribute ( at , attributes [ at ] ) ; } ) ; return this ; } setAttribute ( name , value ) { this . el . setAttribute ( name , value . toString ( ) ) ; return this ; } setAttributeNS ( ns , name , value ) { this . el . setAttributeNS ( ns , name , value . toString ( ) ) ; return this ; } id ( id ) { return this . setAttribute ( "id" , id ) ; } setClass ( ... classes ) { return this . setAttribute ( "class" , classes . join ( " " ) ) ; } appendClass ( className ) { pxt . BrowserUtils . addClass ( this . el , className ) ; return this ; } removeClass ( className ) { pxt . BrowserUtils . removeClass ( this . el , className ) ; } title ( text ) { if ( ! this . titleElement ) { this . titleElement = elt ( "title" ) ; if ( this . el . firstChild ) { this . el . insertBefore ( this . titleElement , this . el . firstChild ) ; } else { this . el . appendChild ( this . titleElement ) ; } } this . titleElement . textContent = text ; } setVisible ( visible ) { return this . setAttribute ( "visibility" , visible ? "visible" : "hidden" ) ; } } export class DrawContext < T extends SVGElement > extends BaseElement < T > { draw ( type ) ; draw ( type ) ; draw ( type ) ; draw ( type ) ; draw ( type ) ; draw ( type ) ; draw ( type ) ; draw ( type ) { const el = drawable ( type as any ) ; this . el . appendChild ( el . el ) ; return el ; } element ( type , cb ) ; element ( type , cb ) ; element ( type , cb ) ; element ( type , cb ) ; element ( type , cb ) ; element ( type , cb ) ; element ( type , cb ) ; element ( type , cb ) { cb ( this . draw ( type as any ) ) ; return this ; } group ( ) { const g = new Group ( ) ; this . el . appendChild ( g . el ) ; return g ; } appendChild < T extends SVGElement > ( child ) { this . el . appendChild ( child . el ) ; } onDown ( handler ) { events . down ( this . el , handler ) ; return this ; } onUp ( handler ) { events . up ( this . el , handler ) ; return this ; } onMove ( handler ) { events . move ( this . el , handler ) ; return this ; } onEnter ( handler ) { events . enter ( this . el , handler ) ; return this ; } onLeave ( handler ) { events . leave ( this . el , handler ) ; return this ; } onClick ( handler ) { events . click ( this . el , handler ) ; return this ; } } export class SVG extends DrawContext < SVGSVGElement > { defs : DefsElement ; constructor ( parent ? ) { super ( "svg" ) ; if ( parent ) { parent . appendChild ( this . el ) ; } } define ( cb ) { if ( ! this . defs ) { this . defs = new DefsElement ( this . el ) ; } cb ( this . defs ) ; return this ; } } export class Group extends DrawContext < SVGGElement > { top : number ; left : number ; scaleFactor : number ; constructor ( parent ? ) { super ( "g" ) ; if ( parent ) { parent . appendChild ( this . el ) ; } } translate ( x , y ) { this . left = x ; this . top = y ; return this . updateTransform ( ) ; } scale ( factor ) { this . scaleFactor = factor ; return this . updateTransform ( ) ; } def ( ) { return new DefsElement ( this . el ) ; } style ( ) { return new StyleElement ( this . el ) ; } private updateTransform ( ) { let transform = "" ; if ( this . left != undefined ) { transform += `translate(${ this . left } ${ this . top })` ; } if ( this . scaleFactor != undefined ) { transform += ` scale(${ this . scaleFactor })` ; } this . setAttribute ( "transform" , transform ) ; return this ; } } export class Pattern extends DrawContext < SVGPatternElement > { constructor ( ) { super ( "pattern" ) ; } units ( kind ) { return this . setAttribute ( "patternUnits" , kind === PatternUnits . objectBoundingBox ? "objectBoundingBox" : "userSpaceOnUse" ) ; } contentUnits ( kind ) { return this . setAttribute ( "patternContentUnits" , kind === PatternUnits . objectBoundingBox ? "objectBoundingBox" : "userSpaceOnUse" ) ; } size ( width , height ) { this . setAttribute ( "width" , width ) ; this . setAttribute ( "height" , height ) ; return this ; } } export class DefsElement extends BaseElement < SVGDefsElement > { constructor ( parent ) { super ( "defs" ) ; parent . appendChild ( this . el ) ; } create ( type , id ) ; create ( type , id ) ; create ( type , id ) ; create ( type , id ) ; create ( type , id ) ; create ( type , id ) { let el ; switch ( type ) { case "path" : el = new Path ( ) ; break ; case "pattern" : el = new Pattern ( ) ; break ; case "radialGradient" : el = new RadialGradient ( ) ; break ; case "linearGradient" : el = new LinearGradient ( ) ; break ; case "clipPath" : el = new ClipPath ( ) ; break ; default : el = new BaseElement ( type ) ; } el . id ( id ) ; this . el . appendChild ( el . el ) ; return el ; } } export class StyleElement extends BaseElement < SVGStyleElement > { constructor ( parent ) { super ( "style" ) ; parent . appendChild ( this . el ) ; } content ( css ) { this . el . textContent = css ; } } export class Drawable < T extends SVGElement > extends DrawContext < T > { at ( x , y ) { this . setAttribute ( "x" , x ) ; this . setAttribute ( "y" , y ) ; return this ; } moveTo ( x , y ) { return this . at ( x , y ) ; } fill ( color , opacity ? ) { this . setAttribute ( "fill" , color ) ; if ( opacity != undefined ) { this . opacity ( opacity ) ; } return this ; } opacity ( opacity ) { return this . setAttribute ( "fill-opacity" , opacity ) ; } stroke ( color , width ? ) { this . setAttribute ( "stroke" , color ) ; if ( width != undefined ) { this . strokeWidth ( width ) ; } return this ; } strokeWidth ( width ) { return this . setAttribute ( "stroke-width" , width ) ; } strokeOpacity ( opacity ) { return this . setAttribute ( "stroke-opacity" , opacity ) ; } clipPath ( url ) { return this . setAttribute ( "clip-path" , url ) ; } } export class Text extends Drawable < SVGTextElement > { constructor ( text ? ) { super ( "text" ) ; if ( text != undefined ) { this . text ( text ) ; } } text ( text ) { this . el . textContent = text ; return this ; } fontFamily ( family ) { return this . setAttribute ( "font-family" , family ) ; } fontSize ( size , units ) { return this . setAttribute ( "font-size" , lengthWithUnits ( size , units ) ) ; } offset ( dx , dy , units ) { if ( dx !== 0 ) { this . setAttribute ( "dx" , lengthWithUnits ( dx , units ) ) ; } if ( dy !== 0 ) { this . setAttribute ( "dy" , lengthWithUnits ( dy , units ) ) ; } return this ; } anchor ( type ) { return this . setAttribute ( "text-anchor" , type ) ; } } export class Rect extends Drawable < SVGRectElement > { constructor ( ) { super ( "rect" ) ; } ; width ( width , unit = LengthUnit . px ) { return this . setAttribute ( "width" , lengthWithUnits ( width , unit ) ) ; } height ( height , unit = LengthUnit . px ) { return this . setAttribute ( "height" , lengthWithUnits ( height , unit ) ) ; } corner ( radius ) { return this . corners ( radius , radius ) ; } corners ( rx , ry ) { this . setAttribute ( "rx" , rx ) ; this . setAttribute ( "ry" , ry ) ; return this ; } size ( width , height , unit = LengthUnit . px ) { this . width ( width , unit ) ; this . height ( height , unit ) ; return this ; } } export class Circle extends Drawable < SVGCircleElement > { constructor ( ) { super ( "circle" ) ; } at ( cx , cy ) { this . setAttribute ( "cx" , cx ) ; this . setAttribute ( "cy" , cy ) ; return this ; } radius ( r ) { return this . setAttribute ( "r" , r ) ; } } class Ellipse extends Drawable < SVGEllipseElement > { constructor ( ) { super ( "ellipse" ) ; } at ( cx , cy ) { this . setAttribute ( "cx" , cx ) ; this . setAttribute ( "cy" , cy ) ; return this ; } radius ( rx , ry ) { this . setAttribute ( "rx" , rx ) ; this . setAttribute ( "ry" , ry ) ; return this ; } } export class Line extends Drawable < SVGLineElement > { constructor ( ) { super ( "line" ) ; } at ( x1 , y1 , x2 ? , y2 ? ) { this . from ( x1 , y1 ) ; if ( x2 != undefined && y2 != undefined ) { this . to ( x2 , y2 ) ; } return this ; } from ( x1 , y1 ) { this . setAttribute ( "x1" , x1 ) ; this . setAttribute ( "y1" , y1 ) ; return this ; } to ( x2 , y2 ) { this . setAttribute ( "x2" , x2 ) ; this . setAttribute ( "y2" , y2 ) ; return this ; } } export class PolyElement < T extends SVGPolygonElement | SVGPolylineElement > extends Drawable < T > { points ( points ) { return this . setAttribute ( "points" , points ) ; } with ( points ) { return this . points ( points . map ( ( { x , y } ) => x + " " + y ) . join ( "," ) ) ; } } export class Polyline extends PolyElement < SVGPolylineElement > { constructor ( ) { super ( "polyline" ) ; } } export class Polygon extends PolyElement < SVGPolygonElement > { constructor ( ) { super ( "polygon" ) ; } } export class Path extends Drawable < SVGPathElement > { d : PathContext ; constructor ( ) { super ( "path" ) ; this . d = new PathContext ( ) ; } update ( ) { return this . setAttribute ( "d" , this . d . toAttribute ( ) ) ; } path ( cb ) { cb ( this . d ) ; return this . update ( ) ; } } export class Image extends Drawable < SVGImageElement > { constructor ( ) { super ( "image" ) ; } src ( url ) { return this . setAttributeNS ( XLINK_NAMESPACE , "href" , url ) ; } width ( width , unit = LengthUnit . px ) { return this . setAttribute ( "width" , lengthWithUnits ( width , unit ) ) ; } height ( height , unit = LengthUnit . px ) { return this . setAttribute ( "height" , lengthWithUnits ( height , unit ) ) ; } size ( width , height , unit = LengthUnit . px ) { this . width ( width , unit ) ; this . height ( height , unit ) ; return this ; } } export class Gradient < T extends SVGGradientElement > extends BaseElement < T > { units ( kind ) { return this . setAttribute ( "gradientUnits" , kind === PatternUnits . objectBoundingBox ? "objectBoundingBox" : "userSpaceOnUse" ) ; } stop ( offset , color ? , opacity ? ) { const s = elt ( "stop" ) ; s . setAttribute ( "offset" , offset + "%" ) ; if ( color != undefined ) { s . setAttribute ( "stop-color" , color ) ; } if ( opacity != undefined ) { s . setAttribute ( "stop-opacity" , opacity ) ; } this . el . appendChild ( s ) ; return this ; } } export class LinearGradient extends Gradient < SVGLinearGradientElement > { constructor ( ) { super ( "linearGradient" ) ; } start ( x1 , y1 ) { this . setAttribute ( "x1" , x1 ) ; this . setAttribute ( "y1" , y1 ) ; return this ; } end ( x2 , y2 ) { this . setAttribute ( "x2" , x2 ) ; this . setAttribute ( "y2" , y2 ) ; return this ; } } export class RadialGradient extends Gradient < SVGRadialGradientElement > { constructor ( ) { super ( "radialGradient" ) ; } center ( cx , cy ) { this . setAttribute ( "cx" , cx ) ; this . setAttribute ( "cy" , cy ) ; return this ; } focus ( fx , fy , fr ) { this . setAttribute ( "fx" , fx ) ; this . setAttribute ( "fy" , fy ) ; this . setAttribute ( "fr" , fr ) ; return this ; } radius ( r ) { return this . setAttribute ( "r" , r ) ; } } export class ClipPath extends DrawContext < SVGClipPathElement > { constructor ( ) { super ( "clipPath" ) ; } clipPathUnits ( objectBoundingBox ) { if ( objectBoundingBox ) { return this . setAttribute ( "clipPathUnits" , "objectBoundingBox" ) ; } else { return this . setAttribute ( "clipPathUnits" , "userSpaceOnUse" ) ; } } } function elt ( type ) { let el = document . createElementNS ( "http://www.w3.org/2000/svg" , type ) ; return el ; } function drawable ( type ) ; function drawable ( type ) ; function drawable ( type ) ; function drawable ( type ) ; function drawable ( type ) ; function drawable ( type ) ; function drawable ( type ) ; function drawable ( type ) { switch ( type ) { case "text" : return new Text ( ) ; case "circle" : return new Circle ( ) ; case "rect" : return new Rect ( ) ; case "line" : return new Line ( ) ; case "polygon" : return new Polygon ( ) ; case "polyline" : return new Polyline ( ) ; case "path" : return new Path ( ) ; default : return new Drawable ( type ) ; } } export type OperatorSymbol = "m" | "M" | "l" | "L" | "c" | "C" | "q" | "Q" | "T" | "t" | "S" | "s" | "z" | "Z" | "A" | "a" ; export interface PathOp { op : OperatorSymbol ; args : number [ ] ; } export class PathContext { private ops : PathOp [ ] = [ ] ; clear ( ) { this . ops = [ ] ; } moveTo ( x , y ) { return this . op ( "M" , x , y ) ; } moveBy ( dx , dy ) { return this . op ( "m" , dx , dy ) ; } lineTo ( x , y ) { return this . op ( "L" , x , y ) ; } lineBy ( dx , dy ) { return this . op ( "l" , dx , dy ) ; } cCurveTo ( c1x , c1y , c2x , c2y , x , y ) { return this . op ( "C" , c1x , c1y , c2x , c2y , x , y ) ; } cCurveBy ( dc1x , dc1y , dc2x , dc2y , dx , dy ) { return this . op ( "c" , dc1x , dc1y , dc2x , dc2y , dx , dy ) ; } qCurveTo ( cx , cy , x , y ) { return this . op ( "Q" , cx , cy , x , y ) ; } qCurveBy ( dcx , dcy , dx , dy ) { return this . op ( "q" , dcx , dcy , dx , dy ) ; } sCurveTo ( cx , cy , x , y ) { return this . op ( "S" , cx , cy , x , y ) ; } sCurveBy ( dcx , dcy , dx , dy ) { return this . op ( "s" , dcx , dcy , dx , dy ) ; } tCurveTo ( x , y ) { return this . op ( "T" , x , y ) ; } tCurveBy ( dx , dy ) { return this . op ( "t" , dx , dy ) ; } arcTo ( rx , ry , xRotate , large , sweepClockwise , x , y ) { return this . op ( "A" , rx , ry , xRotate , large ? 1 : 0 , sweepClockwise ? 1 : 0 , x , y ) ; } arcBy ( rx , ry , xRotate , large , sweepClockwise , x , y ) { return this . op ( "a" , rx , ry , xRotate , large ? 1 : 0 , sweepClockwise ? 1 : 0 , x , y ) ; } close ( ) { return this . op ( "z" ) ; } toAttribute ( ) { return this . ops . map ( op => op . op + " " + op . args . join ( " " ) ) . join ( " " ) ; } private op ( op , ... args ) { this . ops . push ( { op , args } ) ; return this ; } } function lengthWithUnits ( value , unit ) { switch ( unit ) { case LengthUnit . em : return value + "em" ; case LengthUnit . ex : return value + "ex" ; case LengthUnit . px : return value + "px" ; case LengthUnit . in : return value + "in" ; case LengthUnit . cm : return value + "cm" ; case LengthUnit . mm : return value + "mm" ; case LengthUnit . pt : return value + "pt" ; case LengthUnit . pc : return value + "pc" ; case LengthUnit . percent : return value + "%" ; default : return value . toString ( ) ; } } } namespace pxt . svgUtil . events { export function isTouchEnabled ( ) { return typeof window !== "undefined" && ( 'ontouchstart' in window || ( navigator && navigator . maxTouchPoints > 0 ) ) ; } export function hasPointerEvents ( ) { return typeof window != "undefined" && ! ! ( window as any ) . PointerEvent ; } export function down ( el , handler ) { if ( hasPointerEvents ( ) ) { el . addEventListener ( "pointerdown" , handler ) ; } else if ( isTouchEnabled ( ) ) { el . addEventListener ( "mousedown" , handler ) ; el . addEventListener ( "touchstart" , handler ) ; } else { el . addEventListener ( "mousedown" , handler ) ; } } export function up ( el , handler ) { if ( hasPointerEvents ( ) ) { el . addEventListener ( "pointerup" , handler ) ; } else if ( isTouchEnabled ( ) ) { el . addEventListener ( "mouseup" , handler ) ; } else { el . addEventListener ( "mouseup" , handler ) ; } } export function enter ( el , handler ) { if ( hasPointerEvents ( ) ) { el . addEventListener ( "pointerover" , e => { handler ( ! ! ( e . buttons & 1 ) ) ; } ) ; } else if ( isTouchEnabled ( ) ) { el . addEventListener ( "touchstart" , e => { handler ( true ) ; } ) ; } else { el . addEventListener ( "mouseover" , e => { handler ( ! ! ( e . buttons & 1 ) ) ; } ) ; } } export function leave ( el , handler ) { if ( hasPointerEvents ( ) ) { el . addEventListener ( "pointerleave" , handler ) ; } else if ( isTouchEnabled ( ) ) { el . addEventListener ( "touchend" , handler ) ; } else { el . addEventListener ( "mouseleave" , handler ) ; } } export function move ( el , handler ) { if ( hasPointerEvents ( ) ) { el . addEventListener ( "pointermove" , handler ) ; } else if ( isTouchEnabled ( ) ) { el . addEventListener ( "touchmove" , handler ) ; } else { el . addEventListener ( "mousemove" , handler ) ; } } export function click ( el , handler ) { el . addEventListener ( "click" , handler ) ; } } namespace pxt . svgUtil . helpers { export class CenteredText extends Text { protected cx : number ; protected cy : number ; protected fontSizePixels : number ; public at ( cx , cy ) { this . cx = cx ; this . cy = cy ; this . rePosition ( ) ; return this ; } public text ( text , fontSizePixels = 12 ) { super . text ( text ) ; this . fontSizePixels = fontSizePixels ; this . setAttribute ( "font-size" , fontSizePixels + "px" ) ; this . rePosition ( ) ; return this ; } protected rePosition ( ) { if ( this . cx == undefined || this . cy == undefined || this . fontSizePixels == undefined ) { return ; } this . setAttribute ( "x" , this . cx ) ; this . setAttribute ( "y" , this . cy ) ; this . setAttribute ( "text-anchor" , "middle" ) ; this . setAttribute ( "alignment-baseline" , "middle" ) ; } } }