documenting and cleanup
This commit is contained in:
parent
6b840d81dd
commit
3d54cbbc52
7 changed files with 374 additions and 219 deletions
|
@ -13,6 +13,11 @@ const colors = {
|
|||
contrast: '#ec4899'
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a stylesheet
|
||||
* scale: the scale of the markings
|
||||
* stripped: should nested declarations be stripped out? Necessary for svgToPdfkit
|
||||
* */
|
||||
export default (scale, stripped) => `
|
||||
${!stripped ? '/* Reset */' : ''}
|
||||
${!stripped ? 'svg.freesewing ' : ''}path,
|
||||
|
|
|
@ -92,6 +92,14 @@ export default {
|
|||
.attr('data-text-class', 'fill-current font-bold')
|
||||
.attr('data-text-transform', transform(so.at.shift(-90 - so.rotation, shift * so.scale)))
|
||||
}
|
||||
shift += 6
|
||||
const dateformat = require('dateformat')
|
||||
const now = new Date()
|
||||
this.points[`_${prefix}_exportDate`] = so.at
|
||||
.shift(-90 - so.rotation, shift * so.scale)
|
||||
.attr('data-text', dateformat(now, 'yyyymmdd"T"HHMMo'))
|
||||
.attr('data-text-class', 'text-sm')
|
||||
.attr('data-text-transform', transform(so.at.shift(-90 - so.rotation, shift * so.scale)))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
import {sizes} from '../layout/print/plugin'
|
||||
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
|
||||
import SVGtoPDF from 'svg-to-pdfkit'
|
||||
import fileSaver from 'file-saver'
|
||||
|
||||
const pointsPerPx = (72/96);
|
||||
const pxPerMm = 3.77953
|
||||
const pointsPerMm = pxPerMm * pointsPerPx
|
||||
|
||||
export default class Exporter {
|
||||
designName
|
||||
svg
|
||||
settings
|
||||
pattern
|
||||
|
||||
pdf
|
||||
pageWidth
|
||||
pageHeight
|
||||
margin
|
||||
wPages
|
||||
hPages
|
||||
svgWidth
|
||||
svgHeight
|
||||
pagesWithContent = {}
|
||||
|
||||
|
||||
constructor(designName, pattern, svg, settings) {
|
||||
this.designName = designName || 'freesewing'
|
||||
this.settings = settings
|
||||
this.pattern = pattern
|
||||
this.createPdf()
|
||||
|
||||
this.pageHeight = this.pdf.page.height
|
||||
this.pageWidth = this.pdf.page.width
|
||||
this.margin = this.settings.margin * pointsPerMm
|
||||
|
||||
let marginInMm = this.settings.margin
|
||||
// let pageWidthInPx = this.pageWidth / pointsPerMm - marginInMm
|
||||
// let pageHeightInPx = this.pageHeight / pointsPerMm - marginInMm
|
||||
|
||||
const divElem = document.createElement('div');
|
||||
divElem.innerHTML = svg;
|
||||
this.svg = divElem.firstElementChild;
|
||||
|
||||
const viewBox = this.svg.viewBox.baseVal
|
||||
this.svgWidth = viewBox.width + viewBox.x
|
||||
this.svgHeight = viewBox.height + viewBox.y
|
||||
|
||||
this.wPages = Math.ceil(this.svgWidth/this.pageWidthInPx)
|
||||
this.hPages = Math.ceil(this.svgHeight/this.pageHeightInPx)
|
||||
|
||||
this.svgWidth = this.wPages * (this.pageWidth - this.margin)
|
||||
this.svgHeight = this.hPages * (this.pageHeight - this.margin)
|
||||
|
||||
this.svg.setAttribute('height', this.svgWidth + 'pt')
|
||||
this.svg.setAttribute('width', this.svgHeight + 'pt')
|
||||
this.svg.setAttribute('viewBox', `0 0 ${this.wPages * this.pageWidthInPx} ${this.hPages * this.pageHeightInPx}`)
|
||||
}
|
||||
|
||||
get pageWidthInPx() { return this.pageWidth / pointsPerMm - this.settings.margin}
|
||||
get pageHeightInPx() { return this.pageHeight / pointsPerMm - this.settings.margin}
|
||||
|
||||
|
||||
createPdf() {
|
||||
this.pdf = new PDFDocument({
|
||||
size: this.settings.size.toUpperCase(),
|
||||
layout: this.settings.orientation
|
||||
})
|
||||
|
||||
const buffers = [];
|
||||
this.pdf.on('data', buffers.push.bind(buffers));
|
||||
this.pdf.on('end', () => {
|
||||
const blob = new Blob(buffers, {
|
||||
type: 'application/pdf'
|
||||
})
|
||||
fileSaver.saveAs(blob, `freesewing-${this.designName}.pdf`)
|
||||
});
|
||||
}
|
||||
|
||||
scanPages() {
|
||||
const layout = typeof this.pattern.settings.layout === 'object' ? this.pattern.settings.layout : this.pattern.autoLayout;
|
||||
// const usableWidth = this.pageWidth - this.margin
|
||||
// const usableHeight = this.pageHeight - this.margin
|
||||
|
||||
for (var h = 0; h < this.hPages; h++) {
|
||||
this.pagesWithContent[h] = {}
|
||||
for (var w = 0; w < this.wPages; w++) {
|
||||
let x = w * this.pageWidthInPx
|
||||
let y = h * this.pageHeightInPx
|
||||
|
||||
let hasContent = false
|
||||
for (var p in layout.parts) {
|
||||
let part = this.pattern.parts[p]
|
||||
if (p === 'pages' || part.render === false) continue
|
||||
|
||||
let partLayout = layout.parts[p]
|
||||
let partX = partLayout.move.x + part.topLeft.x
|
||||
let partY = partLayout.move.y + part.topLeft.y
|
||||
|
||||
if (
|
||||
partX < x + this.pageWidthInPx &&
|
||||
partX + part.width > x &&
|
||||
partY < y + this.pageHeightInPx &&
|
||||
partY + part.height > y
|
||||
) {
|
||||
hasContent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.pagesWithContent[h][w] = hasContent
|
||||
if (!hasContent) {
|
||||
let pageName = `_pages__row${h}-col${w}`
|
||||
this.removeElem(pageName, 'circle', 'text')
|
||||
this.removeElem(pageName + '-row-marker', 'text')
|
||||
this.removeElem(pageName + '-col-marker', 'text')
|
||||
this.removeElem(pageName + '-x-ruler', 'text')
|
||||
this.removeElem(pageName + '-y-ruler', 'text')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeElem(pathId, ...suffixes) {
|
||||
const that = this;
|
||||
const elem = that.svg.getElementById(pathId)
|
||||
if (elem) elem.setAttribute('class', 'hidden')
|
||||
suffixes.forEach((s) => that.removeElem(`${pathId}-${s}`))
|
||||
}
|
||||
|
||||
async export() {
|
||||
this.scanPages()
|
||||
await this.generateCoverPage()
|
||||
await this.generatePages();
|
||||
this.save()
|
||||
}
|
||||
|
||||
async generateCoverPage() {
|
||||
if (!this.settings.coverPage) {
|
||||
return
|
||||
}
|
||||
|
||||
let coverMargin = 100
|
||||
|
||||
let coverHeight = this.pageHeight - coverMargin * 2
|
||||
let coverWidth = this.pageWidth - coverMargin * 2
|
||||
|
||||
await SVGtoPDF(this.pdf, this.svg.outerHTML, coverMargin, coverMargin, {
|
||||
width: coverWidth,
|
||||
height: coverHeight,
|
||||
assumePt: true,
|
||||
preserveAspectRatio: 'xMidYMid meet'
|
||||
});
|
||||
}
|
||||
|
||||
async generatePages() {
|
||||
const options = {
|
||||
assumePt: true,
|
||||
width: this.svgWidth,
|
||||
height: this.svgHeight,
|
||||
preserveAspectRatio: 'xMinYMin slice'
|
||||
}
|
||||
|
||||
for (var h = 0; h < this.hPages; h++) {
|
||||
for (var w = 0; w < this.wPages; w++) {
|
||||
let x = -w * this.pageWidth + (0.5 + w) * this.margin
|
||||
let y = -h * this.pageHeight + (0.5 + h) * this.margin
|
||||
|
||||
if (!this.pagesWithContent[h][w]) continue;
|
||||
// if there was no cover page, the first page already exists
|
||||
if (this.settings.coverPage || h+w > 0) {
|
||||
this.pdf.addPage()
|
||||
}
|
||||
|
||||
|
||||
await SVGtoPDF(this.pdf, this.svg.outerHTML, x, y, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
this.pdf.end();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { useState} from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import fileSaver from 'file-saver'
|
||||
import yaml from 'js-yaml'
|
||||
|
@ -7,7 +7,7 @@ import Popout from 'shared/components/popout'
|
|||
import WebLink from 'shared/components/web-link'
|
||||
import theme from '@freesewing/plugin-theme'
|
||||
import {pagesPlugin} from '../layout/print/plugin'
|
||||
import PdfExporter from './exporter'
|
||||
import PdfExporter from './pdfExporter'
|
||||
|
||||
export const exports = {
|
||||
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'],
|
||||
|
@ -24,8 +24,11 @@ export const defaultPdfSettings = {
|
|||
|
||||
export const handleExport = (format, gist, design, t, app, setLink, setFormat) => {
|
||||
|
||||
// handle state setting if there's supposed to be any
|
||||
setLink && setLink(false)
|
||||
setFormat && setFormat(format)
|
||||
|
||||
// handle the data exports
|
||||
if (exports.exportAsData.indexOf(format) !== -1) {
|
||||
if (format === 'json') exportJson(gist)
|
||||
else if (format === 'yaml') exportYaml(gist)
|
||||
|
@ -36,8 +39,12 @@ export const handleExport = (format, gist, design, t, app, setLink, setFormat) =
|
|||
|
||||
gist.embed=false
|
||||
let svg = ''
|
||||
|
||||
// make a pattern instance for export rendering
|
||||
const layout = gist.layouts?.printingLayout || gist.layout || true
|
||||
let pattern = new design({...gist, layout})
|
||||
|
||||
// add the theme and translation to the pattern
|
||||
pattern.use(theme, {stripped: format !== 'svg'})
|
||||
pattern.use({
|
||||
hooks: {
|
||||
|
@ -45,23 +52,25 @@ export const handleExport = (format, gist, design, t, app, setLink, setFormat) =
|
|||
}
|
||||
},{t})
|
||||
|
||||
// pdf settings
|
||||
const settings = {
|
||||
...defaultPdfSettings,
|
||||
...(gist._state.layout?.forPrinting?.page || {})
|
||||
}
|
||||
|
||||
// a specified size should override the gist one
|
||||
if (format !== 'pdf') {
|
||||
settings.size = format
|
||||
}
|
||||
|
||||
try {
|
||||
// add pages to pdf exports
|
||||
if (format !== 'svg') {
|
||||
pattern.use(pagesPlugin(settings, true))
|
||||
}
|
||||
|
||||
pattern.draft();
|
||||
svg = pattern.render()
|
||||
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
|
266
sites/shared/components/workbench/exporting/pdfExporter.js
Normal file
266
sites/shared/components/workbench/exporting/pdfExporter.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
import {sizes} from '../layout/print/plugin'
|
||||
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
|
||||
import SVGtoPDF from 'svg-to-pdfkit'
|
||||
import fileSaver from 'file-saver'
|
||||
|
||||
/**
|
||||
* About these numbers, as they are the hardest part of all this:
|
||||
* 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
|
||||
* */
|
||||
|
||||
// multiply a pixel value by this to get a points value
|
||||
const pxToPoints = (72/96);
|
||||
// multiply a mm value by this to get a pixel value
|
||||
const mmToPx = 3.77953
|
||||
// multiply a mm value by this to get a points value
|
||||
const mmToPoints = mmToPx * pxToPoints
|
||||
|
||||
/**
|
||||
* Freesewing's first explicit class?
|
||||
* handles pdf exporting
|
||||
*/
|
||||
export default class Exporter {
|
||||
/** the name of the design, this is used to title the exported pdf */
|
||||
designName
|
||||
/** the svg element to embed in the pdf */
|
||||
svg
|
||||
/** the document configuration */
|
||||
settings
|
||||
/** the pattern instance that is being exported */
|
||||
pattern
|
||||
/** the pdfKit instance that is writing the document */
|
||||
pdf
|
||||
|
||||
/** 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
|
||||
|
||||
/** the width of the svg element, in points */
|
||||
svgWidth
|
||||
/** the height of the svg element, in points */
|
||||
svgHeight
|
||||
|
||||
/** a dictionary to track which pages actually have anything on them */
|
||||
pagesWithContent = {}
|
||||
|
||||
|
||||
constructor(designName, pattern, svg, settings) {
|
||||
// default just in case
|
||||
this.designName = designName || 'freesewing'
|
||||
this.settings = settings
|
||||
this.pattern = pattern
|
||||
|
||||
this.createPdf()
|
||||
|
||||
this.margin = this.settings.margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points
|
||||
this.pageHeight = this.pdf.page.height - this.margin // this is in points because it comes from pdfKit
|
||||
this.pageWidth = this.pdf.page.width - this.margin// this is in points because it comes from pdfKit
|
||||
|
||||
// we pass the svg as a string, so we need to make it a DOM element so we can manipulate it
|
||||
const divElem = document.createElement('div');
|
||||
divElem.innerHTML = svg;
|
||||
this.svg = divElem.firstElementChild;
|
||||
|
||||
// get the pages data
|
||||
const pages = this.pattern.parts.pages.pages
|
||||
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)
|
||||
this.svgWidth = this.columns * this.pageWidth
|
||||
this.svgHeight = this.rows * this.pageHeight
|
||||
this.svg.setAttribute('height', this.svgWidth + 'pt')
|
||||
this.svg.setAttribute('width', this.svgHeight + 'pt')
|
||||
|
||||
// set the viewbox to include all pages in full as well, this time in mm
|
||||
this.svg.setAttribute('viewBox', `0 0 ${this.columns * this.pageWidthInMm} ${this.rows * this.pageHeightInMm}`)
|
||||
}
|
||||
|
||||
/** pdf page usable (excluding margin) width, in mm */
|
||||
get pageWidthInMm() { return this.pageWidth / mmToPoints }
|
||||
/** pdf page usable (excluding margin) height, in mm */
|
||||
get pageHeightInMm() { return this.pageHeight / mmToPoints }
|
||||
|
||||
|
||||
/** 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
|
||||
const buffers = [];
|
||||
// add new data to our buffer storage
|
||||
this.pdf.on('data', buffers.push.bind(buffers));
|
||||
// when the end event fires, then we save the whole thing
|
||||
this.pdf.on('end', () => {
|
||||
// convert buffers to a blob
|
||||
const blob = new Blob(buffers, {
|
||||
type: 'application/pdf'
|
||||
})
|
||||
// save the blob
|
||||
fileSaver.saveAs(blob, `freesewing-${this.designName}.pdf`)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan all the pages and remove ones that have no content.
|
||||
* Because we are rendering a grid of pages that fits the layout, some might not actually have anything on them.
|
||||
* We do not want to user to have to figure out which pages to print and which to skip, so we have to identify these pages
|
||||
* */
|
||||
scanPages() {
|
||||
// get the layout from the pattern
|
||||
const layout = typeof this.pattern.settings.layout === 'object' ? this.pattern.settings.layout : this.pattern.autoLayout;
|
||||
|
||||
// go through all the rows
|
||||
for (var h = 0; h < this.rows; h++) {
|
||||
// make a storage for this row's pages
|
||||
this.pagesWithContent[h] = {}
|
||||
for (var w = 0; w < this.columns; w++) {
|
||||
// topLeft corner to the current page
|
||||
let x = w * this.pageWidthInMm
|
||||
let y = h * this.pageHeightInMm
|
||||
|
||||
// assume no content
|
||||
let hasContent = false
|
||||
// look through all the parts
|
||||
for (var p in layout.parts) {
|
||||
let part = this.pattern.parts[p]
|
||||
// skip the pages part and any that aren't rendered
|
||||
if (p === 'pages' || part.render === false) continue
|
||||
|
||||
// get the position of the part
|
||||
let partLayout = layout.parts[p]
|
||||
let partX = partLayout.move.x + part.topLeft.x
|
||||
let partY = partLayout.move.y + part.topLeft.y
|
||||
|
||||
// check if the part overlaps the page extents
|
||||
if (
|
||||
// if the left of the part is further left than the right end of the page
|
||||
partX < x + this.pageWidthInMm &&
|
||||
// and the top of the part is above the bottom of the page
|
||||
partY < y + this.pageHeightInMm &&
|
||||
// and the right of the part is further right than the left of the page
|
||||
partX + part.width > x &&
|
||||
// and the bottom of the part is below the top to the page
|
||||
partY + part.height > y
|
||||
) {
|
||||
// the part has content inside the page
|
||||
hasContent = true;
|
||||
// so we stop looking
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// save the outcome because we will need it later
|
||||
this.pagesWithContent[h][w] = hasContent
|
||||
|
||||
// if the page has no content, hide its various markers
|
||||
if (!hasContent) {
|
||||
let pageName = `_pages__row${h}-col${w}`
|
||||
this.hideElem(pageName, 'circle', 'text')
|
||||
this.hideElem(pageName + '-row-marker', 'text')
|
||||
this.hideElem(pageName + '-col-marker', 'text')
|
||||
this.hideElem(pageName + '-x-ruler', 'text')
|
||||
this.hideElem(pageName + '-y-ruler', 'text')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hide an element with the given id
|
||||
* id: the string ID of the element to hide
|
||||
* suffixes: additional strings to add to the end of the id like `${id}-${suffix}`, to hide related elements
|
||||
* */
|
||||
hideElem(id, ...suffixes) {
|
||||
// keep a clean reference
|
||||
const that = this;
|
||||
// find the element
|
||||
const elem = that.svg.getElementById(id)
|
||||
// if it exists, change the class to hidden
|
||||
if (elem) elem.setAttribute('class', 'hidden')
|
||||
// do the same with the suffixes
|
||||
suffixes.forEach((s) => that.hideElem(`${id}-${s}`))
|
||||
}
|
||||
|
||||
/** export to pdf */
|
||||
async export() {
|
||||
this.scanPages()
|
||||
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
|
||||
}
|
||||
|
||||
//abitrary margin for visual space
|
||||
let coverMargin = 50
|
||||
|
||||
let coverHeight = this.pageHeight - coverMargin * 2
|
||||
let coverWidth = this.pageWidth - 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.outerHTML, coverMargin, coverMargin, {
|
||||
width: coverWidth,
|
||||
height: coverHeight,
|
||||
assumePt: true,
|
||||
// use aspect ratio to center it
|
||||
preserveAspectRatio: 'xMidYMid meet'
|
||||
});
|
||||
}
|
||||
|
||||
/** 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,
|
||||
preserveAspectRatio: 'xMinYMin slice'
|
||||
}
|
||||
|
||||
// everything is offset by half a margin so that it's centered on the page
|
||||
const startMargin = 0.5 * this.margin
|
||||
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
|
||||
await SVGtoPDF(this.pdf, this.svg.outerHTML, x, y, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** download the pdf */
|
||||
save() {
|
||||
this.pdf.end();
|
||||
}
|
||||
}
|
|
@ -16,12 +16,12 @@ const PrintLayout = props => {
|
|||
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
||||
const layoutSettings = props.gist?._state?.layout?.forPrinting?.page || defaultPdfSettings
|
||||
|
||||
const draft = props.draft
|
||||
|
||||
// add the pages plugin to the draft
|
||||
const layoutSettings = props.gist?._state?.layout?.forPrinting?.page || defaultPdfSettings
|
||||
draft.use(pagesPlugin(
|
||||
layoutSettings
|
||||
layoutSettings, true
|
||||
))
|
||||
|
||||
let patternProps
|
||||
|
|
|
@ -10,18 +10,37 @@ export const sizes = {
|
|||
tabloid: [ 279.4, 431.8 ],
|
||||
}
|
||||
|
||||
/** get a letter to represent an index */
|
||||
const indexLetter = (i) => String.fromCharCode('A'.charCodeAt(0) + i - 1)
|
||||
|
||||
export const pagesPlugin = ({size='a4', orientation='portrait', margin=10}, outlineStyle = true ) => {
|
||||
let [sheetWidth, sheetHeight] = sizes[size];
|
||||
/**
|
||||
* A plugin to add printer pages
|
||||
* */
|
||||
export const pagesPlugin = ({size='a4', orientation='portrait', margin=10}, printStyle = false /** should the pages be rendered for printing or for screen viewing? */ ) => {
|
||||
const ls = orientation === 'landscape'
|
||||
let sheetHeight = sizes[size][ls ? 1 : 0]
|
||||
let sheetWidth = sizes[size][ls ? 0 : 1]
|
||||
sheetWidth -= margin
|
||||
sheetHeight -= margin
|
||||
return basePlugin({sheetWidth, sheetHeight, orientation, outlineStyle})
|
||||
return basePlugin({sheetWidth, sheetHeight, orientation, printStyle})
|
||||
}
|
||||
|
||||
export const cutFabricPlugin = (sheetWidth, sheetHeight) => basePlugin({sheetWidth, sheetHeight, boundary: true, partName: "cutFabric", responsiveWidth: false})
|
||||
|
||||
const basePlugin = ({sheetWidth, sheetHeight, orientation='portrait', boundary=false, partName="pages", responsiveWidth=true, outlineStyle=false}) => ({
|
||||
/**
|
||||
* The base plugin for adding a layout helper part like pages or fabric
|
||||
* sheetWidth: the width of the helper part
|
||||
* sheetHeight: the height of the helper part
|
||||
* boundary: should the helper part calculate its boundary?
|
||||
* responsiveColumns: should the part make more columns if the pattern exceed its width? (for pages you want this, for fabric you don't)
|
||||
* printStyle: hould the pages be rendered for printing or for screen viewing?
|
||||
* */
|
||||
const basePlugin = ({
|
||||
sheetWidth,
|
||||
sheetHeight,
|
||||
boundary=false,
|
||||
partName="pages",
|
||||
responsiveColumns=true,
|
||||
printStyle=false
|
||||
}) => ({
|
||||
name,
|
||||
version,
|
||||
hooks: {
|
||||
|
@ -37,22 +56,21 @@ const basePlugin = ({sheetWidth, sheetHeight, orientation='portrait', boundary=f
|
|||
// Add pages
|
||||
const { macro } = pattern.parts[partName].shorthand()
|
||||
let { height, width } = pattern
|
||||
if (!responsiveWidth) width = sheetWidth;
|
||||
if (!responsiveColumns) width = sheetWidth;
|
||||
if (pattern.settings.layout?.topLeft) {
|
||||
height += pattern.settings.layout.topLeft.y
|
||||
responsiveWidth && (width += pattern.settings.layout.topLeft.x)
|
||||
responsiveColumns && (width += pattern.settings.layout.topLeft.x)
|
||||
}
|
||||
|
||||
macro('addPages', { size: [sheetWidth, sheetHeight], orientation, height, width })
|
||||
macro('addPages', { size: [sheetWidth, sheetHeight], height, width })
|
||||
|
||||
if (boundary) pattern.parts[partName].boundary();
|
||||
}
|
||||
},
|
||||
macros: {
|
||||
/** draft the pages */
|
||||
addPages: function(so) {
|
||||
const ls = so.orientation === 'landscape'
|
||||
const w = so.size[ls ? 1 : 0]
|
||||
const h = so.size[ls ? 0 : 1]
|
||||
const [h,w] = so.size
|
||||
const cols = Math.ceil(so.width / w)
|
||||
const rows = Math.ceil(so.height / h)
|
||||
const { points, Point, paths, Path, macro } = this.shorthand()
|
||||
|
@ -85,12 +103,13 @@ const basePlugin = ({sheetWidth, sheetHeight, orientation='portrait', boundary=f
|
|||
.line(points[`${pageName}-tr`])
|
||||
.close()
|
||||
|
||||
if (!outlineStyle) {
|
||||
if (!printStyle) {
|
||||
paths[pageName].attr('class', 'fill-fabric')
|
||||
.attr('style', `stroke-opacity: 0; fill-opacity: ${(col+row)%2===0 ? 0.03 : 0.09};`)
|
||||
}
|
||||
else {
|
||||
paths[pageName].attr('class', 'interfacing stroke-xs')
|
||||
// add markers and rulers
|
||||
macro('addPageMarkers', {row, col, pageName})
|
||||
macro('addRuler', {xAxis: true, pageName})
|
||||
macro('addRuler', {xAxis: false, pageName})
|
||||
|
@ -103,48 +122,71 @@ const basePlugin = ({sheetWidth, sheetHeight, orientation='portrait', boundary=f
|
|||
this.pages = { cols, rows, count: cols*rows }
|
||||
|
||||
},
|
||||
/** add a ruler to the top left corner of the page */
|
||||
addRuler({xAxis, pageName}) {
|
||||
const { points, Point, paths, Path } = this.shorthand()
|
||||
// arbitrary number of units for the ruler
|
||||
const rulerLength = 2
|
||||
const isMetric = this.context.settings.units === 'metric'
|
||||
// distance to the end of the ruler
|
||||
const endPointDist = [(isMetric ? 10 : 25.4) * rulerLength, 0]
|
||||
|
||||
const axisName = xAxis ? 'x' : 'y'
|
||||
const rulerName = `${pageName}-${axisName}`
|
||||
// start by making an endpoint for the ruler based on the axis
|
||||
const endPoint = [endPointDist[xAxis ? 0 : 1], endPointDist[xAxis ? 1 : 0]]
|
||||
points[`${pageName}-${axisName}-ruler-start`] = points[`${pageName}-tl`].translate(endPoint[0], endPoint[1])
|
||||
points[`${pageName}-${axisName}-ruler-end`] = points[`${pageName}-${axisName}-ruler-start`].translate(xAxis ? 0 : 3, xAxis ? 3 : 0)
|
||||
points[`${rulerName}-ruler-end`] = points[`${pageName}-tl`].translate(endPoint[0], endPoint[1])
|
||||
// also make a tick for the end of the ruler
|
||||
points[`${rulerName}-ruler-tick`] = points[`${rulerName}-ruler-end`].translate(xAxis ? 0 : 3, xAxis ? 3 : 0)
|
||||
// add a label to it
|
||||
.attr('data-text', rulerLength + (isMetric ? 'cm' : 'in'))
|
||||
// space the text properly from the end of the line
|
||||
.attr('data-text-class', 'fill-interfacing baseline-center' + (xAxis ? ' center' : ''))
|
||||
.attr('data-text-id', `${pageName}-${axisName}-ruler-text`)
|
||||
.attr(`data-text-d${xAxis ? 'y' : 'x'}`, xAxis ? 5 : 3)
|
||||
paths[`${pageName}-${axisName}-ruler`] = new Path()
|
||||
// give the text an explicit id in case we need to hide it later
|
||||
.attr('data-text-id', `${rulerName}-ruler-text`)
|
||||
|
||||
// start the path
|
||||
paths[`${rulerName}-ruler`] = new Path()
|
||||
.move(points[`${pageName}-tl`])
|
||||
.attr('id', `${pageName}-${axisName}-ruler`)
|
||||
// give it an explicit id in case we need to hide it later
|
||||
.attr('id', `${rulerName}-ruler`)
|
||||
.attr('class', 'interfacing stroke-xs')
|
||||
|
||||
// get the distance between the smaller ticks on the rule
|
||||
const division = (isMetric ? 0.1 : 0.125) / rulerLength
|
||||
// we're going to go by fraction, so we want to do this up to 1
|
||||
for (var d = division; d < 1; d+= division) {
|
||||
points[`${pageName}-${axisName}-ruler-${d}-start`] = points[`${pageName}-tl`].shiftFractionTowards(points[`${pageName}-${axisName}-ruler-start`], d)
|
||||
// make a start point
|
||||
points[`${rulerName}-ruler-${d}-end`] = points[`${pageName}-tl`].shiftFractionTowards(points[`${rulerName}-ruler-end`], d)
|
||||
|
||||
// base tick size on whether this is a major interval or a minor one
|
||||
let tick = 1;
|
||||
// if this tick indicates a whole unit, extra long
|
||||
if (d.toFixed(3) % (1/rulerLength) === 0) tick = 3
|
||||
// if this tick indicates half a unit, long
|
||||
else if (d.toFixed(3) % (0.5/rulerLength) === 0) tick = 2
|
||||
|
||||
points[`${pageName}-${axisName}-ruler-${d}-end`] = points[`${pageName}-${axisName}-ruler-${d}-start`].translate(xAxis ? 0 : tick, xAxis ? tick : 0)
|
||||
// make a point for the end of the tick
|
||||
points[`${rulerName}-ruler-${d}-tick`] = points[`${rulerName}-ruler-${d}-end`].translate(xAxis ? 0 : tick, xAxis ? tick : 0)
|
||||
|
||||
paths[`${pageName}-${axisName}-ruler`]
|
||||
.line(points[`${pageName}-${axisName}-ruler-${d}-start`])
|
||||
.line(points[`${pageName}-${axisName}-ruler-${d}-end`])
|
||||
.line(points[`${pageName}-${axisName}-ruler-${d}-start`])
|
||||
// add the whole set to the ruler path
|
||||
paths[`${rulerName}-ruler`]
|
||||
.line(points[`${rulerName}-ruler-${d}-end`])
|
||||
.line(points[`${rulerName}-ruler-${d}-tick`])
|
||||
.line(points[`${rulerName}-ruler-${d}-end`])
|
||||
}
|
||||
|
||||
paths[`${pageName}-${axisName}-ruler`]
|
||||
.line(points[`${pageName}-${axisName}-ruler-start`])
|
||||
.line(points[`${pageName}-${axisName}-ruler-end`])
|
||||
.attr('class', 'interfacing stroke-xs')
|
||||
|
||||
// add the end
|
||||
paths[`${rulerName}-ruler`]
|
||||
.line(points[`${rulerName}-ruler-end`])
|
||||
.line(points[`${rulerName}-ruler-tick`])
|
||||
},
|
||||
/** add page markers to the given page */
|
||||
addPageMarkers({row, col, pageName}) {
|
||||
const {macro, points} = this.shorthand()
|
||||
// these markers are placed on the top and left of the page,
|
||||
// so skip markers for the top row or leftmost column
|
||||
if (row !== 0) macro('addPageMarker', {
|
||||
along: [points[`${pageName}-tl`], points[`${pageName}-tr`]],
|
||||
label: '' + row,
|
||||
|
@ -158,18 +200,26 @@ const basePlugin = ({sheetWidth, sheetHeight, orientation='portrait', boundary=f
|
|||
pageName
|
||||
})
|
||||
},
|
||||
/** add a page marker for either the row of the column */
|
||||
addPageMarker({along, label, isRow, pageName}) {
|
||||
const {points, paths, Point, Path} = this.shorthand()
|
||||
const markerName = `${pageName}-${isRow ? 'row' : 'col'}-marker`
|
||||
|
||||
// get a point on the center of the appropriate side
|
||||
points[`${markerName}-center`] = along[0].shiftFractionTowards(along[1], 0.5)
|
||||
// add the label to it
|
||||
.attr('data-text', label)
|
||||
.attr('data-text-class', 'text-sm center baseline-center bold')
|
||||
// give it an explicit ID in case we need to hide it later
|
||||
.attr('data-text-id', markerName + '-text')
|
||||
|
||||
// get points to make a diamond around the center point
|
||||
points[`${markerName}-r`] = points[`${markerName}-center`].translate(-5, 0)
|
||||
points[`${markerName}-l`] = points[`${markerName}-center`].translate(5, 0)
|
||||
points[`${markerName}-t`] = points[`${markerName}-center`].translate(0, -5)
|
||||
points[`${markerName}-b`] = points[`${markerName}-center`].translate(0, 5)
|
||||
|
||||
// make a path for the diamond
|
||||
paths[markerName] = new Path()
|
||||
.move(points[`${markerName}-r`])
|
||||
.line(points[`${markerName}-t`])
|
||||
|
@ -177,6 +227,7 @@ const basePlugin = ({sheetWidth, sheetHeight, orientation='portrait', boundary=f
|
|||
.line(points[`${markerName}-b`])
|
||||
.close()
|
||||
.attr('class', 'fill-interfacing interfacing')
|
||||
// give it an explicit ID in case we need to hide it later
|
||||
.attr('id', markerName)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue