import { SVG } from "../util" ; import { SVGPathBuilder } from "../util/SVG" ; import { EMFJSError , Helper } from "./Helper" ; import { Obj , PointL , PointS , RectL } from "./Primitives" ; import { CreateSimpleRegion , Region } from "./Region" ; import { Brush , ColorRef , Font , Pen } from "./Style" ; interface ISelectedStyle { brush ? : Brush ; pen ? : Pen ; font ? : Font ; region ? : Region ; path ? : Path ; [ key ] : Obj | undefined ; } class Path extends Obj { public svgPath : SVGPathBuilder ; constructor ( svgPath , copy ? ) { super ( "path" ) ; if ( svgPath != null ) { this . svgPath = svgPath ; } else { this . svgPath = copy . svgPath ; } } public clone ( ) { return new Path ( null , this ) ; } public toString ( ) { return "{[path]}" ; } } function createStockObjects ( ) { const createSolidBrush = ( r , g , b ) => { return new Brush ( null , { style : Helper . GDI . BrushStyle . BS_SOLID , color : new ColorRef ( null , r , g , b ) , } ) ; } ; const createSolidPen = ( r , g , b ) => { return new Pen ( null , Helper . GDI . PenStyle . PS_SOLID , 1 , new ColorRef ( null , r , g , b ) , null ) ; } ; const stockObjs = { WHITE_BRUSH : createSolidBrush ( 255 , 255 , 255 ) , LTGRAY_BRUSH : createSolidBrush ( 212 , 208 , 200 ) , GRAY_BRUSH : createSolidBrush ( 128 , 128 , 128 ) , DKGRAY_BRUSH : createSolidBrush ( 64 , 64 , 64 ) , BLACK_BRUSH : createSolidBrush ( 0 , 0 , 0 ) , NULL_BRUSH : new Brush ( null , { style : Helper . GDI . BrushStyle . BS_NULL , } ) , WHITE_PEN : createSolidPen ( 255 , 255 , 255 ) , BLACK_PEN : createSolidPen ( 0 , 0 , 0 ) , NULL_PEN : new Pen ( null , Helper . GDI . PenStyle . PS_NULL , 0 , null , null ) , OEM_FIXED_FONT : null , ANSI_FIXED_FONT : null , ANSI_VAR_FONT : null , SYSTEM_FONT : null , DEVICE_DEFAULT_FONT : null , DEFAULT_PALETTE : null , SYSTEM_FIXED_FONT : null , DEFAULT_GUI_FONT : null , } ; const objs = { } ; for ( const t in stockObjs ) { const stockObjects = Helper . GDI . StockObject as { [ key ] : number ; } ; const idx = stockObjects [ t ] - 0x80000000 ; objs [ idx . toString ( ) ] = stockObjs [ t ] ; } return objs ; } const _StockObjects = createStockObjects ( ) ; class GDIContextState { public _svggroup : SVGElement ; public _svgclipChanged : boolean ; public _svgtextbkfilter : SVGFilterElement ; public mapmode : number ; public stretchmode : number ; public textalign : number ; public bkmode : number ; public textcolor : ColorRef ; public bkcolor : ColorRef ; public polyfillmode : number ; public miterlimit : number ; public wx : number ; public wy : number ; public ww : number ; public wh : number ; public vx : number ; public vy : number ; public vw : number ; public vh : number ; public x : number ; public y : number ; public nextbrx : number ; public nextbry : number ; public brx : number ; public bry : number ; public clip : Region ; public ownclip : boolean ; public selected : ISelectedStyle ; constructor ( copy , defObjects ? ) { if ( copy != null ) { this . _svggroup = copy . _svggroup ; this . _svgclipChanged = copy . _svgclipChanged ; this . _svgtextbkfilter = copy . _svgtextbkfilter ; this . mapmode = copy . mapmode ; this . stretchmode = copy . stretchmode ; this . textalign = copy . textalign ; this . bkmode = copy . bkmode ; this . textcolor = copy . textcolor . clone ( ) ; this . bkcolor = copy . bkcolor . clone ( ) ; this . polyfillmode = copy . polyfillmode ; this . miterlimit = copy . miterlimit ; this . wx = copy . wx ; this . wy = copy . wy ; this . ww = copy . ww ; this . wh = copy . wh ; this . vx = copy . vx ; this . vy = copy . vy ; this . vw = copy . vw ; this . vh = copy . vh ; this . x = copy . x ; this . y = copy . y ; this . nextbrx = copy . nextbrx ; this . nextbry = copy . nextbry ; this . brx = copy . brx ; this . bry = copy . bry ; this . clip = copy . clip ; this . ownclip = false ; this . selected = { } ; for ( const type in copy . selected ) { this . selected [ type ] = copy . selected [ type ] ; } } else { this . _svggroup = null ; this . _svgclipChanged = false ; this . _svgtextbkfilter = null ; this . mapmode = Helper . GDI . MapMode . MM_ANISOTROPIC ; this . stretchmode = Helper . GDI . StretchMode . COLORONCOLOR ; this . textalign = 0 ; this . bkmode = Helper . GDI . MixMode . OPAQUE ; this . textcolor = new ColorRef ( null , 0 , 0 , 0 ) ; this . bkcolor = new ColorRef ( null , 255 , 255 , 255 ) ; this . polyfillmode = Helper . GDI . PolygonFillMode . ALTERNATE ; this . miterlimit = 10 ; this . wx = 0 ; this . wy = 0 ; this . ww = 0 ; this . wh = 0 ; this . vx = 0 ; this . vy = 0 ; this . vw = 0 ; this . vh = 0 ; this . x = 0 ; this . y = 0 ; this . nextbrx = 0 ; this . nextbry = 0 ; this . brx = 0 ; this . bry = 0 ; this . clip = null ; this . ownclip = false ; this . selected = { } ; for ( const type in defObjects ) { const defObj = defObjects [ type ] ; this . selected [ type ] = defObj != null ? defObj . clone ( ) : null ; } } } } export class GDIContext { private _svg : SVG ; private _svgdefs : SVGDefsElement ; private _svgPatterns : { [ key ] : Brush ; } ; private _svgClipPaths : { [ key ] : Region ; } ; private _svgPath : SVGPathBuilder ; private defObjects : ISelectedStyle ; private state : GDIContextState ; private statestack : GDIContextState [ ] ; private objects : { [ key ] : Obj ; } ; constructor ( svg ) { this . _svg = svg ; this . _svgdefs = null ; this . _svgPatterns = { } ; this . _svgClipPaths = { } ; this . _svgPath = null ; this . defObjects = { brush : new Brush ( null , { style : Helper . GDI . BrushStyle . BS_SOLID , color : new ColorRef ( null , 0 , 0 , 0 ) , } ) , pen : new Pen ( null , Helper . GDI . PenStyle . PS_SOLID , 1 , new ColorRef ( null , 0 , 0 , 0 ) , null ) , font : new Font ( null , null ) , palette : null , region : null , } ; this . state = new GDIContextState ( null , this . defObjects ) ; this . statestack = [ this . state ] ; this . objects = { } ; } public setMapMode ( mode ) { Helper . log ( "[gdi] setMapMode: mode=" + mode ) ; this . state . mapmode = mode ; this . state . _svggroup = null ; } public setWindowOrgEx ( x , y ) { Helper . log ( "[gdi] setWindowOrgEx: x=" + x + " y=" + y ) ; this . state . wx = x ; this . state . wy = y ; this . state . _svggroup = null ; } public setWindowExtEx ( x , y ) { Helper . log ( "[gdi] setWindowExtEx: x=" + x + " y=" + y ) ; this . state . ww = x ; this . state . wh = y ; this . state . _svggroup = null ; } public setViewportOrgEx ( x , y ) { Helper . log ( "[gdi] setViewportOrgEx: x=" + x + " y=" + y ) ; this . state . vx = x ; this . state . vy = y ; this . state . _svggroup = null ; } public setViewportExtEx ( x , y ) { Helper . log ( "[gdi] setViewportExtEx: x=" + x + " y=" + y ) ; this . state . vw = x ; this . state . vh = y ; this . state . _svggroup = null ; } public setBrushOrgEx ( origin ) { Helper . log ( "[gdi] setBrushOrgEx: x=" + origin . x + " y=" + origin . y ) ; this . state . nextbrx = origin . x ; this . state . nextbry = origin . y ; } public saveDC ( ) { Helper . log ( "[gdi] saveDC" ) ; const prevstate = this . state ; this . state = new GDIContextState ( this . state ) ; this . statestack . push ( prevstate ) ; this . state . _svggroup = null ; } public restoreDC ( saved ) { Helper . log ( "[gdi] restoreDC: saved=" + saved ) ; if ( this . statestack . length > 1 ) { if ( saved === - 1 ) { this . state = this . statestack . pop ( ) ; } else if ( saved < - 1 ) { throw new EMFJSError ( "restoreDC: relative restore not implemented" ) ; } else if ( saved > 1 ) { throw new EMFJSError ( "restoreDC: absolute restore not implemented" ) ; } } else { throw new EMFJSError ( "No saved contexts" ) ; } this . state . _svggroup = null ; } public setStretchBltMode ( stretchMode ) { Helper . log ( "[gdi] setStretchBltMode: stretchMode=" + stretchMode ) ; } public rectangle ( rect , rw , rh ) { Helper . log ( "[gdi] rectangle: rect=" + rect . toString ( ) + " with pen " + this . state . selected . pen . toString ( ) + " and brush " + this . state . selected . brush . toString ( ) ) ; const bottom = this . _todevY ( rect . bottom ) ; const right = this . _todevX ( rect . right ) ; const top = this . _todevY ( rect . top ) ; const left = this . _todevX ( rect . left ) ; rw = this . _todevH ( rw ) ; rh = this . _todevH ( rh ) ; Helper . log ( "[gdi] rectangle: TRANSLATED: bottom=" + bottom + " right=" + right + " top=" + top + " left=" + left + " rh=" + rh + " rw=" + rw ) ; this . _pushGroup ( ) ; const opts = this . _applyOpts ( null , true , true , false ) ; this . _svg . rect ( this . state . _svggroup , left , top , right - left , bottom - top , rw / 2 , rh / 2 , opts ) ; } public lineTo ( x , y ) { Helper . log ( "[gdi] lineTo: x=" + x + " y=" + y + " with pen " + this . state . selected . pen . toString ( ) ) ; const toX = this . _todevX ( x ) ; const toY = this . _todevY ( y ) ; const fromX = this . _todevX ( this . state . x ) ; const fromY = this . _todevY ( this . state . y ) ; this . state . x = x ; this . state . y = y ; Helper . log ( "[gdi] lineTo: TRANSLATED: toX=" + toX + " toY=" + toY + " fromX=" + fromX + " fromY=" + fromY ) ; this . _pushGroup ( ) ; const opts = this . _applyOpts ( null , true , false , false ) ; this . _svg . line ( this . state . _svggroup , fromX , fromY , toX , toY , opts ) ; } public moveToEx ( x , y ) { Helper . log ( "[gdi] moveToEx: x=" + x + " y=" + y ) ; this . state . x = x ; this . state . y = y ; if ( this . _svgPath != null ) { this . _svgPath . move ( this . state . x , this . state . y ) ; Helper . log ( "[gdi] new path: " + this . _svgPath . path ( ) ) ; } } public polygon ( points , bounds , first ) { Helper . log ( "[gdi] polygon: points=" + points + " with pen " + this . state . selected . pen . toString ( ) + " and brush " + this . state . selected . brush . toString ( ) ) ; const pts = [ ] ; for ( let i = 0 ; i < points . length ; i ++ ) { const point = points [ i ] ; pts . push ( [ this . _todevX ( point . x ) , this . _todevY ( point . y ) ] ) ; } if ( first ) { this . _pushGroup ( ) ; } const opts = { "fill-rule" : this . state . polyfillmode === Helper . GDI . PolygonFillMode . ALTERNATE ? "evenodd" : "nonzero" , } ; this . _applyOpts ( opts , true , true , false ) ; this . _svg . polygon ( this . state . _svggroup , pts , opts ) ; } public polyPolygon ( polygons , bounds ) { Helper . log ( "[gdi] polyPolygon: polygons.length=" + polygons . length + " with pen " + this . state . selected . pen . toString ( ) + " and brush " + this . state . selected . brush . toString ( ) ) ; const cnt = polygons . length ; for ( let i = 0 ; i < cnt ; i ++ ) { this . polygon ( polygons [ i ] , bounds , i === 0 ) ; } } public polyline ( isLineTo , points , bounds ) { Helper . log ( "[gdi] polyline: isLineTo=" + isLineTo . toString ( ) + ", points=" + points + ", bounds=" + bounds . toString ( ) + " with pen " + this . state . selected . pen . toString ( ) ) ; const pts = [ ] ; for ( let i = 0 ; i < points . length ; i ++ ) { const point = points [ i ] ; pts . push ( [ this . _todevX ( point . x ) , this . _todevY ( point . y ) ] ) ; } if ( this . _svgPath != null ) { if ( ! isLineTo || pts . length === 0 ) { this . _svgPath . move ( this . _todevX ( this . state . x ) , this . _todevY ( this . state . y ) ) ; } else { const firstPts = pts [ 0 ] ; this . _svgPath . move ( firstPts [ 0 ] , firstPts [ 1 ] ) ; } this . _svgPath . line ( pts ) ; Helper . log ( "[gdi] new path: " + this . _svgPath . path ( ) ) ; } else { this . _pushGroup ( ) ; const opts = this . _applyOpts ( null , true , false , false ) ; if ( isLineTo && points . length > 0 ) { const firstPt = points [ 0 ] ; if ( firstPt . x !== this . state . x || firstPt . y !== this . state . y ) { pts . unshift ( [ this . _todevX ( this . state . x ) , this . _todevY ( this . state . y ) ] ) ; } } this . _svg . polyline ( this . state . _svggroup , pts , opts ) ; } if ( points . length > 0 ) { const lastPt = points [ points . length - 1 ] ; this . state . x = lastPt . x ; this . state . y = lastPt . y ; } } public polybezier ( isPolyBezierTo , points , bounds ) { Helper . log ( "[gdi] polybezier: isPolyBezierTo=" + isPolyBezierTo . toString ( ) + ", points=" + points + ", bounds=" + bounds . toString ( ) + " with pen " + this . state . selected . pen . toString ( ) ) ; const pts = [ ] ; for ( let i = 0 ; i < points . length ; i ++ ) { const point = points [ i ] ; pts . push ( { x : this . _todevX ( point . x ) , y : this . _todevY ( point . y ) } ) ; } if ( this . _svgPath != null ) { if ( isPolyBezierTo && pts . length > 0 ) { const firstPts = pts [ 0 ] ; this . _svgPath . move ( firstPts . x , firstPts . y ) ; } else { this . _svgPath . move ( this . _todevX ( this . state . x ) , this . _todevY ( this . state . y ) ) ; } if ( pts . length < ( isPolyBezierTo ? 3 : 4 ) ) { throw new EMFJSError ( "Not enough points to draw bezier" ) ; } for ( let i = isPolyBezierTo ? 1 : 0 ; i + 3 <= pts . length ; i += 3 ) { const cp1 = pts [ i ] ; const cp2 = pts [ i + 1 ] ; const ep = pts [ i + 2 ] ; this . _svgPath . curveC ( cp1 . x , cp1 . y , cp2 . x , cp2 . y , ep . x , ep . y ) ; } Helper . log ( "[gdi] new path: " + this . _svgPath . path ( ) ) ; } else { throw new EMFJSError ( "polybezier not implemented (not a path)" ) ; } if ( points . length > 0 ) { const lastPt = points [ points . length - 1 ] ; this . state . x = lastPt . x ; this . state . y = lastPt . y ; } } public selectClipPath ( rgnMode ) { Helper . log ( "[gdi] selectClipPath: rgnMode=0x" + rgnMode . toString ( 16 ) ) ; } public selectClipRgn ( rgnMode , region ) { Helper . log ( "[gdi] selectClipRgn: rgnMode=0x" + rgnMode . toString ( 16 ) ) ; if ( rgnMode === Helper . GDI . RegionMode . RGN_COPY ) { this . state . selected . region = region ; this . state . clip = null ; this . state . ownclip = false ; } else { if ( region == null ) { throw new EMFJSError ( "No clip region to select" ) ; } throw new EMFJSError ( "Not implemented: rgnMode=0x" + rgnMode . toString ( 16 ) ) ; } this . state . _svgclipChanged = true ; } public offsetClipRgn ( offset ) { Helper . log ( "[gdi] offsetClipRgn: offset=" + offset . toString ( ) ) ; this . _getClipRgn ( ) . offset ( offset . x , offset . y ) ; } public setTextAlign ( textAlignmentMode ) { Helper . log ( "[gdi] setTextAlign: textAlignmentMode=0x" + textAlignmentMode . toString ( 16 ) ) ; this . state . textalign = textAlignmentMode ; } public setMiterLimit ( miterLimit ) { Helper . log ( "[gdi] setMiterLimit: miterLimit=" + miterLimit ) ; this . state . miterlimit = miterLimit ; } public setBkMode ( bkMode ) { Helper . log ( "[gdi] setBkMode: bkMode=0x" + bkMode . toString ( 16 ) ) ; this . state . bkmode = bkMode ; } public setBkColor ( bkColor ) { Helper . log ( "[gdi] setBkColor: bkColor=" + bkColor . toString ( ) ) ; this . state . bkcolor = bkColor ; this . state . _svgtextbkfilter = null ; } public setPolyFillMode ( polyFillMode ) { Helper . log ( "[gdi] setPolyFillMode: polyFillMode=" + polyFillMode ) ; this . state . polyfillmode = polyFillMode ; } public createBrush ( index , brush ) { const idx = this . _storeObject ( brush , index ) ; Helper . log ( "[gdi] createBrush: brush=" + brush . toString ( ) + " with handle " + idx ) ; } public createPen ( index , pen ) { const idx = this . _storeObject ( pen , index ) ; Helper . log ( "[gdi] createPen: pen=" + pen . toString ( ) + " width handle " + idx ) ; } public createPenEx ( index , pen ) { const idx = this . _storeObject ( pen , index ) ; Helper . log ( "[gdi] createPenEx: pen=" + pen . toString ( ) + " width handle " + idx ) ; } public selectObject ( objIdx , checkType ) { const obj = this . _getObject ( objIdx ) ; if ( obj != null && ( checkType == null || obj . type === checkType ) ) { this . _selectObject ( obj ) ; Helper . log ( "[gdi] selectObject: objIdx=" + objIdx + ( obj ? " selected " + obj . type + ": " + obj . toString ( ) : "[invalid index]" ) ) ; } else { Helper . log ( "[gdi] selectObject: objIdx=" + objIdx + ( obj ? " invalid object type: " + obj . type : "[invalid index]" ) ) ; } } public abortPath ( ) { Helper . log ( "[gdi] abortPath" ) ; if ( this . _svgPath != null ) { this . _svgPath = null ; } } public beginPath ( ) { Helper . log ( "[gdi] beginPath" ) ; if ( this . _svgPath != null ) { this . _svgPath = null ; } this . _svgPath = this . _svg . createPath ( ) ; } public closeFigure ( ) { Helper . log ( "[gdi] closeFigure" ) ; if ( this . _svgPath == null ) { throw new EMFJSError ( "No path bracket: cannot close figure" ) ; } this . _svgPath . close ( ) ; } public fillPath ( bounds ) { Helper . log ( "[gdi] fillPath" ) ; if ( this . state . selected . path == null ) { throw new EMFJSError ( "No path selected" ) ; } const selPath = this . state . selected . path ; const opts = this . _applyOpts ( null , true , true , false ) ; this . _svg . path ( this . state . _svggroup , selPath . svgPath , opts ) ; this . _pushGroup ( ) ; this . state . selected . path = null ; } public strokePath ( bounds ) { Helper . log ( "[gdi] strokePath" ) ; if ( this . state . selected . path == null ) { throw new EMFJSError ( "No path selected" ) ; } const selPath = this . state . selected . path ; const opts = this . _applyOpts ( { fill : "none" } , true , false , false ) ; this . _svg . path ( this . state . _svggroup , selPath . svgPath , opts ) ; this . _pushGroup ( ) ; this . state . selected . path = null ; } public endPath ( ) { Helper . log ( "[gdi] endPath" ) ; if ( this . _svgPath == null ) { throw new EMFJSError ( "No path bracket: cannot end path" ) ; } this . _pushGroup ( ) ; this . _selectObject ( new Path ( this . _svgPath ) ) ; this . _svgPath = null ; } public deleteObject ( objIdx ) { const ret = this . _deleteObject ( objIdx ) ; Helper . log ( "[gdi] deleteObject: objIdx=" + objIdx + ( ret ? " deleted object" : "[invalid index]" ) ) ; } private _pushGroup ( ) { if ( this . state . _svggroup == null || this . state . _svgclipChanged ) { this . state . _svgclipChanged = false ; this . state . _svgtextbkfilter = null ; const settings = { viewBox : [ this . state . vx , this . state . vy , this . state . vw , this . state . vh ] . join ( " " ) , preserveAspectRatio : "none" , } ; if ( this . state . clip != null ) { Helper . log ( "[gdi] new svg x=" + this . state . vx + " y=" + this . state . vy + " width=" + this . state . vw + " height=" + this . state . vh + " with clipping" ) ; settings [ "clip-path" ] = "url(#" + this . _getSvgClipPathForRegion ( this . state . clip ) + ")" ; } else { Helper . log ( "[gdi] new svg x=" + this . state . vx + " y=" + this . state . vy + " width=" + this . state . vw + " height=" + this . state . vh + " without clipping" ) ; } this . state . _svggroup = this . _svg . svg ( this . state . _svggroup , this . state . vx , this . state . vy , this . state . vw , this . state . vh , settings ) ; } } private _getStockObject ( idx ) { if ( idx >= 0x80000000 && idx <= 0x80000011 ) { return _StockObjects [ ( idx - 0x80000000 ) . toString ( ) ] ; } else if ( idx === Helper . GDI . StockObject . DC_BRUSH ) { return this . state . selected . brush ; } else if ( idx === Helper . GDI . StockObject . DC_PEN ) { return this . state . selected . pen ; } return null ; } private _storeObject ( obj , idx ) { if ( ! idx ) { idx = 0 ; while ( this . objects [ idx . toString ( ) ] != null && idx <= 65535 ) { idx ++ ; } if ( idx > 65535 ) { Helper . log ( "[gdi] Too many objects!" ) ; return - 1 ; } } this . objects [ idx . toString ( ) ] = obj ; return idx ; } private _getObject ( objIdx ) { let obj = this . objects [ objIdx . toString ( ) ] ; if ( obj == null ) { obj = this . _getStockObject ( objIdx ) ; if ( obj == null ) { Helper . log ( "[gdi] No object with handle " + objIdx ) ; } } return obj ; } private _getSvgDef ( ) { if ( this . _svgdefs == null ) { this . _svgdefs = this . _svg . defs ( ) ; } return this . _svgdefs ; } private _getSvgClipPathForRegion ( region ) { for ( const existingId in this . _svgClipPaths ) { const rgn = this . _svgClipPaths [ existingId ] ; if ( rgn === region ) { return existingId ; } } const id = Helper . _makeUniqueId ( "c" ) ; const sclip = this . _svg . clipPath ( this . _getSvgDef ( ) , id , "userSpaceOnUse" ) ; switch ( region . complexity ) { case 1 : this . _svg . rect ( sclip , this . _todevX ( region . bounds . left ) , this . _todevY ( region . bounds . top ) , this . _todevW ( region . bounds . right - region . bounds . left ) , this . _todevH ( region . bounds . bottom - region . bounds . top ) , { "fill" : "black" , "stroke-width" : 0 } ) ; break ; case 2 : for ( let i = 0 ; i < region . scans . length ; i ++ ) { const scan = region . scans [ i ] ; for ( let j = 0 ; j < scan . scanlines . length ; j ++ ) { const scanline = scan . scanlines [ j ] ; this . _svg . rect ( sclip , this . _todevX ( scanline . left ) , this . _todevY ( scan . top ) , this . _todevW ( scanline . right - scanline . left ) , this . _todevH ( scan . bottom - scan . top ) , { "fill" : "black" , "stroke-width" : 0 } ) ; } } break ; } this . _svgClipPaths [ id ] = region ; return id ; } private _getSvgPatternForBrush ( brush ) { for ( const existingId in this . _svgPatterns ) { const pat = this . _svgPatterns [ existingId ] ; if ( pat === brush ) { return existingId ; } } let width ; let height ; let img ; switch ( brush . style ) { case Helper . GDI . BrushStyle . BS_PATTERN : width = brush . pattern . getWidth ( ) ; height = brush . pattern . getHeight ( ) ; break ; case Helper . GDI . BrushStyle . BS_DIBPATTERNPT : width = brush . dibpatternpt . getWidth ( ) ; height = brush . dibpatternpt . getHeight ( ) ; img = brush . dibpatternpt . base64ref ( ) ; break ; default : throw new EMFJSError ( "Invalid brush style" ) ; } const id = Helper . _makeUniqueId ( "p" ) ; const spat = this . _svg . pattern ( this . _getSvgDef ( ) , id , this . state . brx , this . state . bry , width , height , { patternUnits : "userSpaceOnUse" } ) ; this . _svg . image ( spat , 0 , 0 , width , height , img ) ; this . _svgPatterns [ id ] = brush ; return id ; } private _selectObject ( obj ) { this . state . selected [ obj . type ] = obj ; switch ( obj . type ) { case "region" : this . state . _svgclipChanged = true ; break ; case "brush" : this . state . brx = this . state . nextbrx ; this . state . bry = this . state . nextbry ; break ; } } private _deleteObject ( objIdx ) { const obj = this . objects [ objIdx . toString ( ) ] ; if ( obj != null ) { for ( let i = 0 ; i < this . statestack . length ; i ++ ) { const state = this . statestack [ i ] ; if ( state . selected [ obj . type ] === obj ) { state . selected [ obj . type ] = this . defObjects [ obj . type ] . clone ( ) ; } } delete this . objects [ objIdx . toString ( ) ] ; return true ; } Helper . log ( "[gdi] Cannot delete object with invalid handle " + objIdx ) ; return false ; } private _getClipRgn ( ) { if ( this . state . clip != null ) { if ( ! this . state . ownclip ) { this . state . clip = this . state . clip . clone ( ) ; } } else { if ( this . state . selected . region != null ) { this . state . clip = this . state . selected . region . clone ( ) ; } else { this . state . clip = CreateSimpleRegion ( this . state . wx , this . state . wy , this . state . wx + this . state . ww , this . state . wy + this . state . wh ) ; } } this . state . ownclip = true ; return this . state . clip ; } private _todevX ( val ) { return Math . floor ( ( val - this . state . wx ) * ( this . state . vw / this . state . ww ) ) + this . state . vx ; } private _todevY ( val ) { return Math . floor ( ( val - this . state . wy ) * ( this . state . vh / this . state . wh ) ) + this . state . vy ; } private _todevW ( val ) { return Math . floor ( val * ( this . state . vw / this . state . ww ) ) + this . state . vx ; } private _todevH ( val ) { return Math . floor ( val * ( this . state . vh / this . state . wh ) ) + this . state . vy ; } private _tologicalX ( val ) { return Math . floor ( ( val - this . state . vx ) / ( this . state . vw / this . state . ww ) ) + this . state . wx ; } private _tologicalY ( val ) { return Math . floor ( ( val - this . state . vy ) / ( this . state . vh / this . state . wh ) ) + this . state . wy ; } private _tologicalW ( val ) { return Math . floor ( val / ( this . state . vw / this . state . ww ) ) + this . state . wx ; } private _tologicalH ( val ) { return Math . floor ( val / ( this . state . vh / this . state . wh ) ) + this . state . wy ; } private _applyOpts ( opts , usePen , useBrush , useFont ) { if ( opts == null ) { opts = { } ; } if ( usePen ) { const pen = this . state . selected . pen ; if ( pen . style !== Helper . GDI . PenStyle . PS_NULL ) { opts . stroke = "#" + pen . color . toHex ( ) ; opts [ "stroke-width" ] = pen . width ; opts [ "stroke-miterlimit" ] = this . state . miterlimit ; opts [ "stroke-linecap" ] = "round" ; const dotWidth = 1 ; opts [ "stroke-linejoin" ] = "round" ; const dashWidth = opts [ "stroke-width" ] * 4 ; const dotSpacing = opts [ "stroke-width" ] * 2 ; switch ( pen . style ) { case Helper . GDI . PenStyle . PS_DASH : opts [ "stroke-dasharray" ] = [ dashWidth , dotSpacing ] . toString ( ) ; break ; case Helper . GDI . PenStyle . PS_DOT : opts [ "stroke-dasharray" ] = [ dotWidth , dotSpacing ] . toString ( ) ; break ; case Helper . GDI . PenStyle . PS_DASHDOT : opts [ "stroke-dasharray" ] = [ dashWidth , dotSpacing , dotWidth , dotSpacing ] . toString ( ) ; break ; case Helper . GDI . PenStyle . PS_DASHDOTDOT : opts [ "stroke-dasharray" ] = [ dashWidth , dotSpacing , dotWidth , dotSpacing , dotWidth , dotSpacing ] . toString ( ) ; break ; } } } if ( useBrush ) { const brush = this . state . selected . brush ; switch ( brush . style ) { case Helper . GDI . BrushStyle . BS_SOLID : opts . fill = "#" + brush . color . toHex ( ) ; break ; case Helper . GDI . BrushStyle . BS_PATTERN : case Helper . GDI . BrushStyle . BS_DIBPATTERNPT : opts . fill = "url(#" + this . _getSvgPatternForBrush ( brush ) + ")" ; break ; case Helper . GDI . BrushStyle . BS_NULL : opts . fill = "none" ; break ; default : Helper . log ( "[gdi] unsupported brush style: " + brush . style ) ; opts . fill = "none" ; break ; } } if ( useFont ) { const font = this . state . selected . font ; opts [ "font-family" ] = font . facename ; opts [ "font-size" ] = Math . abs ( font . height ) ; opts . fill = "#" + this . state . textcolor . toHex ( ) ; } return opts ; } }