import { Rasterizer } from "./Rasterizer" ; import { Workspace } from "./Workspace" ; import { Enum , Layer , LayerOptions } from "bp/global" ; import { PaperUtil } from "bp/view/util/PaperUtil" ; import { Constants } from "bp/content/json" ; import type { IPoint } from "bp/math" ; import type { Studio } from ".." ; export interface DisplaySetting { showGrid : boolean ; showHinge : boolean ; showRidge : boolean ; showAxialParallel : boolean ; showLabel : boolean ; showDot : boolean ; includeHiddenElement : boolean ; } /**
 *  Display 物件負責管理 Canvas 的基本設置以及跟 paper.js 之間的串接。
 *
 * 這個物件有好幾個繼承層次，這是為了一方面顧及封裝和關注點分離、
 * 另一方面又便於這些層次之間的計算屬性共用。
  @ shrewd export class Display extends Workspace { public readonly $boundary : paper . Path ; public $rasterizer : Rasterizer ; private readonly _project : paper . Project ; public readonly $paper : paper . PaperScope ; @ shrewd public $settings : DisplaySetting = { showAxialParallel : true , showGrid : true , showHinge : true , showRidge : true , showLabel : true , showDot : true , includeHiddenElement : false , } ; constructor ( studio ) { super ( studio ) ; const canvas = document . createElement ( "canvas" ) ; this . _el . appendChild ( canvas ) ; this . $rasterizer = new Rasterizer ( this , this . _createImg ( ) ) ; this . $paper = new paper . PaperScope ( ) ; this . $paper . setup ( canvas ) ; this . $paper . settings . insertItems = false ; this . _project = this . $paper . project ; this . _project . view . autoUpdate = false ; this . _project . currentStyle . strokeColor = PaperUtil . $black ; this . _project . currentStyle . strokeScaling = false ; for ( let l of Enum . values ( Layer ) ) { this . _project . addLayer ( new paper . Layer ( { name : Layer [ l ] } ) ) ; } this . $boundary = new paper . Path . Rectangle ( { from : [ 0 , 0 ] , to : [ 0 , 0 ] , } ) ; for ( let l of Enum . values ( Layer ) ) { if ( LayerOptions [ l ] . clipped ) { this . $addToLayer ( this . $boundary . clone ( ) , l ) ; this . _project . layers [ l ] . clipped = true ; } } } public $zoom ( zoom , relativeCenter ? ) { if ( zoom < Constants . $FULL_ZOOM ) zoom = Constants . $FULL_ZOOM ; let sheet = this . _design ?. sheet ; if ( ! sheet || sheet . zoom == zoom ) return ; let absoluteCenter ; if ( relativeCenter ) { let rect = this . _el . getBoundingClientRect ( ) ; absoluteCenter = { x : relativeCenter . x - rect . left , y : relativeCenter . y - rect . top } ; } else { absoluteCenter = { x : this . _viewWidth / 2 , y : this . _viewHeight / 2 } ; } super . $zoom ( zoom , absoluteCenter ) ; } /** 刷新繪製 */ public $update ( ) { this . _project . view . update ( ) ; } public $addToLayer ( item , layer ) { this . _project . layers [ layer ] . addChild ( item ) ; } @ shrewd private _renderSetting ( ) { let notLayout = this . _design ?. mode != "layout" ?? false ; this . _project . layers [ Layer . $hinge ] . visible = this . $settings . showHinge ; this . _project . layers [ Layer . $ridge ] . visible = this . $settings . showRidge || notLayout ; this . _project . layers [ Layer . $axisParallels ] . visible = this . $settings . showAxialParallel ; this . _project . layers [ Layer . $label ] . visible = this . $settings . showLabel ; this . _project . layers [ Layer . $dot ] . visible = this . $settings . showDot || notLayout ; } @ shrewd public $render ( ) { let width = 0 , height = 0 , scale = this . $scale ; if ( this . _design && this . _design . sheet ) { ( { width , height } = this . _design . sheet ) ; } PaperUtil . $setRectangleSize ( this . $boundary , width , height ) ; this . $isScrollable ( ) ; this . _setupViewport ( this . _project . view . viewSize ) ; let { x , y } = this . _offset ; this . _project . view . matrix . set ( scale , 0 , 0 , - scale , x , y ) ; for ( let l of Enum . values ( Layer ) ) { let layer = this . _project . layers [ l ] ; if ( LayerOptions [ l ] . clipped ) { ( layer . children [ 0 ] as paper . Path ) . set ( { segments : this . $boundary . segments , } ) ; } if ( ! LayerOptions [ l ] . scaled ) { layer . applyMatrix = false ; layer . matrix . set ( 1 / scale , 0 , 0 , - 1 / scale , 0 , 0 ) ; } } } /** 產生 SVG 檔案連結 */ public $createSvgBlob ( ) { let rect = this . _getBound ( ) ; let svg = this . _project . exportSVG ( { bounds : rect , matrix : this . _project . view . matrix , } ) as SVGElement ; if ( ! this . $settings . includeHiddenElement ) this . _removeHidden ( svg ) ; return new Blob ( [ svg . outerHTML ] , { type : "image/svg+xml" } ) ; } /** 遞迴移除所有具有 `visibility="hidden"` 屬性的標籤 */ private _removeHidden ( node ) { let children = Array . from ( node . children ) ; for ( let c of children ) { if ( c . getAttribute ( 'visibility' ) == 'hidden' ) node . removeChild ( c ) ; else this . _removeHidden ( c ) ; } } }