@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering.
353 lines (305 loc) • 8.93 kB
text/typescript
import { Size, KeyValue } from '../types'
import { Rectangle } from '../geometry'
import { NumberExt, JQuery, Dom, Unit, Vector } from '../util'
import { Base } from './base'
export class PrintManager extends Base {
show(options: Partial<PrintManager.Options> = {}) {
const localOptions = {
...PrintManager.defaultOptions,
...options,
} as PrintManager.Options
const $pages = this.createPrintPages(localOptions)
localOptions.ready(
$pages,
($pages) => this.showPrintWindow($pages, localOptions),
{
sheetSize: this.getSheetSize(localOptions),
},
)
}
protected get className() {
return this.view.prefixClassName('graph-print')
}
protected showPrintWindow(
$pages: JQuery<HTMLElement>[] | false,
options: PrintManager.Options,
) {
if ($pages) {
const $body = JQuery(document.body)
const $container = JQuery(this.view.container)
const bodyClassName = this.view.prefixClassName('graph-printing')
$body.addClass(bodyClassName)
const $detached = $container.children().detach()
$pages.forEach(($page) => {
$page
.removeClass(`${this.className}-preview`)
.addClass(`${this.className}-ready`)
.appendTo($body)
})
let ret = false
const cb = () => {
if (!ret) {
ret = true
$body.removeClass(bodyClassName)
$pages.forEach(($page) => $page.remove())
$container.append($detached)
JQuery(`#${this.styleSheetId}`).remove()
this.graph.trigger('after:print', options)
JQuery(window).off('afterprint', cb)
}
}
JQuery(window).one('afterprint', cb)
setTimeout(cb, 200)
window.print()
}
}
protected createPrintPage(
pageArea: Rectangle.RectangleLike,
options: PrintManager.Options,
) {
this.graph.trigger('before:print', options)
const $page = JQuery('<div/>').addClass(this.className)
const $wrap = JQuery('<div/>')
.addClass(this.view.prefixClassName('graph-print-inner'))
.css('position', 'relative')
if (options.size) {
$page.addClass(`${this.className}-size-${options.size}`)
}
const vSVG = Vector.create(this.view.svg).clone()
const vStage = vSVG.findOne(
`.${this.view.prefixClassName('graph-svg-stage')}`,
)!
$wrap.append(vSVG.node)
const sheetSize = this.getSheetSize(options)
const graphArea = this.graph.transform.getGraphArea()
const s = this.graph.transform.getScale()
const ts = this.graph.translate()
const matrix = Dom.createSVGMatrix().translate(ts.tx / s.sx, ts.ty / s.sy)
const info = this.getPageInfo(graphArea, pageArea, sheetSize)
const scale = info.scale
const bbox = info.bbox
$wrap.css({
left: 0,
top: 0,
})
vSVG.attr({
width: bbox.width * scale,
height: bbox.height * scale,
style: 'position:relative',
viewBox: [bbox.x, bbox.y, bbox.width, bbox.height].join(' '),
})
vStage.attr('transform', Dom.matrixToTransformString(matrix))
$page.append($wrap)
$page.addClass(`${this.className}-preview`)
return {
$page,
sheetSize,
}
}
protected createPrintPages(options: PrintManager.Options) {
let ret
const area = this.getPrintArea(options)
const $pages = []
if (options.page) {
const pageSize = this.getPageSize(area, options.page)
const pageAreas = this.getPageAreas(area, pageSize)
pageAreas.forEach((pageArea) => {
ret = this.createPrintPage(pageArea, options)
$pages.push(ret.$page)
})
} else {
ret = this.createPrintPage(area, options)
$pages.push(ret.$page)
}
if (ret) {
const size = {
width: ret.sheetSize.cssWidth,
height: ret.sheetSize.cssHeight,
}
this.updatePrintStyle(size, options)
}
return $pages
}
protected get styleSheetId() {
return this.view.prefixClassName('graph-print-style')
}
protected updatePrintStyle(
size: KeyValue<string>,
options: PrintManager.Options,
) {
const sizeCSS = Object.keys(size).reduce(
(memo, key) => `${memo} ${key}:${size[key]};`,
'',
)
const margin = NumberExt.normalizeSides(options.margin)
const marginUnit = options.marginUnit || ''
const sheetUnit = options.sheetUnit || ''
const css = `
@media print {
.${this.className}.${this.className}-ready {
${sizeCSS}
}
@page {
margin:
${[
margin.top + marginUnit,
margin.right + marginUnit,
margin.bottom + marginUnit,
margin.left + marginUnit,
].join(' ')};
size: ${options.sheet.width + sheetUnit} ${
options.sheet.height + sheetUnit
};
.${this.className}.${this.className}-preview {
${sizeCSS}
}
}`
const id = this.styleSheetId
const $style = JQuery(`#${id}`)
if ($style.length) {
$style.html(css)
} else {
JQuery('head').append(
`'<style type="text/css" id="${id}">${css}</style>'`, // lgtm[js/html-constructed-from-input]
)
}
}
protected getPrintArea(options: PrintManager.Options) {
let area = options.area
if (!area) {
const padding = NumberExt.normalizeSides(options.padding)
area = this.graph.getContentArea().moveAndExpand({
x: -padding.left,
y: -padding.top,
width: padding.left + padding.right,
height: padding.top + padding.bottom,
})
}
return area
}
protected getPageSize(
area: Rectangle.RectangleLike,
poster: PrintManager.Page,
): Size {
if (typeof poster === 'object') {
const raw = poster as any
const page = {
width: raw.width,
height: raw.height,
}
if (page.width == null) {
page.width = Math.ceil(area.width / (raw.columns || 1))
}
if (page.height == null) {
page.height = Math.ceil(area.height / (raw.rows || 1))
}
return page
}
return {
width: area.width,
height: area.height,
}
}
protected getPageAreas(area: Rectangle.RectangleLike, pageSize: Size) {
const pages = []
const width = pageSize.width
const height = pageSize.height
for (let w = 0, n = 0; w < area.height && n < 200; w += height, n += 1) {
for (let h = 0, m = 0; h < area.width && m < 200; h += width, m += 1) {
pages.push(new Rectangle(area.x + h, area.y + w, width, height))
}
}
return pages
}
protected getSheetSize(
options: PrintManager.Options,
): PrintManager.SheetSize {
const sheet = options.sheet
const margin = NumberExt.normalizeSides(options.margin)
const marginUnit = options.marginUnit || ''
const sheetUnit = options.sheetUnit || ''
const cssWidth =
// eslint-disable-next-line
`calc(${sheet.width}${sheetUnit} - ${
margin.left + margin.right
}${marginUnit})`
const cssHeight =
// eslint-disable-next-line
`calc(${sheet.height}${sheetUnit} - ${
margin.top + margin.bottom
}${marginUnit})`
const ret = Unit.measure(cssWidth, cssHeight)
return {
cssWidth,
cssHeight,
width: ret.width,
height: ret.height,
}
}
protected getPageInfo(
graphArea: Rectangle.RectangleLike,
pageArea: Rectangle.RectangleLike,
sheetSize: Size,
) {
const bbox = new Rectangle(
pageArea.x - graphArea.x,
pageArea.y - graphArea.y,
pageArea.width,
pageArea.height,
)
const pageRatio = bbox.width / bbox.height
const graphRatio = sheetSize.width / sheetSize.height
return {
bbox,
scale:
graphRatio < pageRatio
? sheetSize.width / bbox.width
: sheetSize.height / bbox.height,
fitHorizontal: graphRatio < pageRatio,
}
}
.dispose()
dispose() {}
}
export namespace PrintManager {
export type Page =
| boolean
| Size
| {
rows: number
columns: number
}
export interface Options {
size?: string
sheet: Size
sheetUnit?: Unit
area?: Rectangle.RectangleLike
page?: Page
margin?: NumberExt.SideOptions
marginUnit?: Unit
padding?: NumberExt.SideOptions
ready: (
$pages: JQuery<HTMLElement>[],
readyToPrint: ($pages: JQuery<HTMLElement>[] | false) => any,
options: {
sheetSize: SheetSize
},
) => any
}
export interface SheetSize extends Size {
cssWidth: string
cssHeight: string
}
export const defaultOptions: Options = {
page: false,
sheet: {
width: 210,
height: 297,
},
sheetUnit: 'mm',
margin: 0.4,
marginUnit: 'in',
padding: 5,
ready: ($pages, readyToPrint) => readyToPrint($pages),
}
}