2022-08-17 13:11:22 -05:00
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
import SVGtoPDF from 'svg-to-pdfkit'
import fileSaver from 'file-saver'
2022-08-24 00:23:45 -05:00
import { logo } from '@freesewing/plugin-logo'
2022-08-17 13:11:22 -05:00
2022-08-24 00:23:45 -05:00
const logoSvg = ` <svg viewBox="-22 -36 46 50">
< style > path { fill : none ; stroke : # 555555 ; stroke - width : 0.5 } < / s t y l e >
< defs > $ { logo } < / d e f s >
< use xlink : href = "#logo" x = "0" y = "0" > < / u s e >
< / s v g > `
2022-08-17 13:11:22 -05:00
/ * *
* PdfKit , the library we ' re using for pdf generation , uses points as a unit , so when we tell it things like where to put the svg and how big the svg is , we need those numbers to be in points
* The svg uses mm internally , so when we do spatial reasoning inside the svg , we need to know values in mm
* * /
2022-08-21 10:26:35 +01:00
const mmToPoints = 2.834645669291339
2022-08-17 13:11:22 -05:00
/ * *
* Freesewing ' s first explicit class ?
* handles pdf exporting
* /
export default class Exporter {
2022-08-24 11:03:11 -05:00
/** the svg as text to embed in the pdf */
2022-08-17 13:11:22 -05:00
svg
/** the document configuration */
settings
/** the pdfKit instance that is writing the document */
pdf
2022-08-24 11:03:11 -05:00
/** the export buffer to hold pdfKit output */
2022-08-21 10:26:35 +01:00
buffers
2022-08-24 11:03:11 -05:00
/** translated strings to add to the cover page */
2022-08-24 00:23:45 -05:00
strings
2022-08-17 13:11:22 -05:00
/** the usable width (excluding margin) of the pdf page, in points */
pageWidth
/** the usable height (excluding margin) of the pdf page, in points */
pageHeight
/** the page margin, in points */
margin
/** the number of columns of pages in the svg */
columns
/** the number of rows of pages in the svg */
rows
2022-08-24 11:03:11 -05:00
/** the width of the entire svg, in points */
2022-08-17 13:11:22 -05:00
svgWidth
2022-08-24 11:03:11 -05:00
/** the height of the entire svg, in points */
2022-08-17 13:11:22 -05:00
svgHeight
2022-08-24 00:23:45 -05:00
constructor ( { svg , settings , pages , strings } ) {
2022-08-17 13:11:22 -05:00
this . settings = settings
2022-08-21 10:26:35 +01:00
this . pagesWithContent = pages . withContent ;
this . svg = svg
2022-08-24 00:23:45 -05:00
this . strings = strings
2022-08-17 13:11:22 -05:00
this . createPdf ( )
this . margin = this . settings . margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points
2022-08-21 10:26:35 +01:00
this . pageHeight = this . pdf . page . height - this . margin * 2 // this is in points because it comes from pdfKit
this . pageWidth = this . pdf . page . width - this . margin * 2 // this is in points because it comes from pdfKit
2022-08-17 13:11:22 -05:00
// get the pages data
this . columns = pages . cols
this . rows = pages . rows
// then set the svg's width and height in points to include all pages in full (the original svg will have been set to show only as much page as is occupied)
2022-08-21 10:26:35 +01:00
this . svgWidth = this . columns * pages . width * mmToPoints
this . svgHeight = this . rows * pages . height * mmToPoints
2022-08-17 13:11:22 -05:00
}
/** create the pdf document */
createPdf ( ) {
// instantiate with the correct size and orientation
this . pdf = new PDFDocument ( {
size : this . settings . size . toUpperCase ( ) ,
layout : this . settings . orientation
} )
// PdfKit wants to flush the buffer on each new page.
// We don't want to try to save the document until it's complete, so we have to manage the buffers ourselves
2022-08-21 10:26:35 +01:00
this . buffers = [ ] ;
2022-08-17 13:11:22 -05:00
// add new data to our buffer storage
2022-08-21 10:26:35 +01:00
this . pdf . on ( 'data' , this . buffers . push . bind ( this . buffers ) ) ;
2022-08-17 13:11:22 -05:00
}
/** export to pdf */
2022-08-21 10:26:35 +01:00
async export ( onComplete ) {
// when the end event fires, then we save the whole thing
this . pdf . on ( 'end' , ( ) => {
// convert buffers to a blob
const blob = new Blob ( this . buffers , {
type : 'application/pdf'
} )
onComplete ( blob )
} ) ;
2022-08-17 13:11:22 -05:00
await this . generateCoverPage ( )
await this . generatePages ( ) ;
this . save ( )
}
/** generate the cover page for the pdf */
async generateCoverPage ( ) {
// don't make one if it's not requested
if ( ! this . settings . coverPage ) {
return
}
2022-08-24 11:03:11 -05:00
this . generateCoverPageTitle ( )
//abitrary margin for visual space
let coverMargin = 85
let coverHeight = this . pdf . page . height - coverMargin * 2
let coverWidth = this . pdf . page . width - coverMargin * 2
// add the entire pdf to the page, so that it fills the available space as best it can
await SVGtoPDF ( this . pdf , this . svg , coverMargin , coverMargin * 1.5 , {
width : coverWidth ,
height : coverHeight ,
assumePt : false ,
// use aspect ratio to center it
preserveAspectRatio : 'xMidYMid meet'
} ) ;
}
async generateCoverPageTitle ( ) {
2022-08-24 00:23:45 -05:00
let lineLevel = 50
let lineStart = 50
2022-08-24 11:03:11 -05:00
this . pdf . fotSize ( 28 )
2022-08-24 00:23:45 -05:00
this . pdf . text ( 'FreeSewing' , lineStart , lineLevel )
lineLevel += 28
this . pdf . fontSize ( 12 )
this . pdf . text ( this . strings . tagline , lineStart , lineLevel )
lineLevel += 12 + 20
this . pdf . fontSize ( 48 )
this . pdf . text ( this . strings . design , lineStart , lineLevel )
lineLevel += 48
await SVGtoPDF ( this . pdf , logoSvg , this . pdf . page . width - lineStart - 100 , lineStart , {
width : 100 ,
height : lineLevel - lineStart - 8 ,
preserveAspectRatio : 'xMaxYMin meet'
} )
this . pdf . lineWidth ( 1 )
this . pdf . moveTo ( lineStart , lineLevel - 8 )
. lineTo ( this . pdf . page . width - lineStart , lineLevel - 8 )
. stroke ( )
this . pdf . fillColor ( '#888888' )
this . pdf . fontSize ( 10 )
this . pdf . text ( this . strings . url , lineStart , lineLevel )
2022-08-17 13:11:22 -05:00
}
/** generate the pages of the pdf */
async generatePages ( ) {
// pass the same options to the svg converter for each page
const options = {
assumePt : true ,
width : this . svgWidth ,
height : this . svgHeight ,
2022-08-21 10:26:35 +01:00
preserveAspectRatio : 'xMinYMin slice' ,
2022-08-17 13:11:22 -05:00
}
// everything is offset by half a margin so that it's centered on the page
2022-08-21 10:26:35 +01:00
const startMargin = this . margin
2022-08-17 13:11:22 -05:00
for ( var h = 0 ; h < this . rows ; h ++ ) {
for ( var w = 0 ; w < this . columns ; w ++ ) {
// skip empty pages
if ( ! this . pagesWithContent [ h ] [ w ] ) continue ;
// position it
let x = - w * this . pageWidth + startMargin
let y = - h * this . pageHeight + startMargin
// if there was no cover page, the first page already exists
if ( this . settings . coverPage || h + w > 0 ) {
// otherwise make a new page
this . pdf . addPage ( )
}
// add the pdf to the page, offset by the page distances
2022-08-21 10:26:35 +01:00
await SVGtoPDF ( this . pdf , this . svg , x , y , options )
2022-08-17 13:11:22 -05:00
}
}
}
/** download the pdf */
save ( ) {
this . pdf . end ( ) ;
}
}