Comparing version 0.0.0 to 0.0.1
@@ -25,3 +25,3 @@ 'use strict' | ||
// cast to string | ||
replacement = replacement ? replacement + '' : '' | ||
replacement = replacement !== undefined && replacement !== null ? replacement + '' : '' | ||
@@ -57,3 +57,5 @@ replacement = replacement.replace(/[\r|\n]+/g, "\n") | ||
apply() { | ||
document.querySelector(this.selector).outerHTML = this.svg | ||
if (this.svg !== document.querySelector(this.selector).outerHTML) { | ||
document.querySelector(this.selector).outerHTML = this.svg | ||
} | ||
@@ -68,4 +70,5 @@ this.adjustTextQueries.forEach(query => { | ||
// initialize | ||
this.svg = document.querySelector(this.selector).outerHTML | ||
this.adjustTextQueries = [] | ||
} | ||
} |
@@ -7,3 +7,3 @@ 'use strict' | ||
test('textarea with single-byte characters', () => { | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/textarea.svg', 'utf-8') | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/fixtures-textarea.svg', 'utf-8') | ||
@@ -27,3 +27,3 @@ const paper = new SvgPaper() | ||
test('textarea with multi-byte characters', () => { | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/textarea.svg', 'utf-8') | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/fixtures-textarea.svg', 'utf-8') | ||
@@ -47,3 +47,3 @@ const paper = new SvgPaper() | ||
test('textarea with single-byte and multi-byte characters also with line-breaks', () => { | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/textarea.svg', 'utf-8') | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/fixtures-textarea.svg', 'utf-8') | ||
@@ -66,1 +66,72 @@ const paper = new SvgPaper() | ||
}) | ||
test('real world paper created with Adobe XD', () => { | ||
document.body.innerHTML = fs.readFileSync('js/tests/resources/real-world-paper-xd.svg', 'utf-8') | ||
const paper = new SvgPaper() | ||
paper | ||
.replace('%customerName%', 'Mr. John Smith') | ||
.replace('%caseName%', 'Case 1234') | ||
.replace('%deliverTo%', 'California headquotes') | ||
.replace('%deliveredBy%', '2021-12-31') | ||
.replace('%expiredAt%', '2021-06-30') | ||
.replace('%date%', '2021-06-01') | ||
.replace('%number%', 'EST-20210601-001') | ||
.replace('%address%', "1600 Amphitheatre Parkway\nMountain View, CA 94043\nUnited States") | ||
.adjustText('#_customerName_', 1230, 'middle') | ||
.adjustText(`#_caseName_`, 960) | ||
.adjustText(`#_deliverTo_`, 960) | ||
.adjustText(`#_deliveredBy_`, 960) | ||
.adjustText(`#_expiredAt_`, 960) | ||
.adjustText(`#_date_`, 960) | ||
.adjustText(`#_number_`, 960) | ||
.adjustTextarea(`#_address_`, 513, 136) | ||
let subtotal = 0 | ||
for (const i of [...Array(26)].keys()) { | ||
subtotal += i * i * 10 | ||
paper | ||
.replace(`%itemName[${i}]%`, 'Sample item') | ||
.replace(`%vendorName[${i}]%`, 'Sample vendor') | ||
.replace(`%modelCode[${i}]%`, `SAMPLE-ITEM-${i}`) | ||
.replace(`%quantity[${i}]%`, i) | ||
.replace(`%unitPrice[${i}]%`, (i * 10).toLocaleString()) | ||
.replace(`%total[${i}]%`, (i * i * 10).toLocaleString()) | ||
.adjustText(`#_itemName_${i}_`, 665) | ||
.adjustText(`#_vendorName_${i}_`, 200) | ||
.adjustText(`#_modelCode_${i}_`, 260) | ||
.adjustText(`#_quantity_${i}_`, 140, 'middle') | ||
.adjustText(`#_unitPrice_${i}_`, 185, 'end') | ||
.adjustText(`#_total_${i}_`, 215, 'end') | ||
} | ||
paper | ||
.replace('%subtotal%', subtotal.toLocaleString()) | ||
.replace('%discount%', 100) | ||
.replace('%discountedSubtotal%', (subtotal - 100).toLocaleString()) | ||
.replace('%tax%', parseInt((subtotal - 100) * 0.1).toLocaleString()) | ||
.replace('%quoteTotal%', (subtotal - 100 + parseInt((subtotal - 100) * 0.1)).toLocaleString()) | ||
.adjustText(`#_subtotal_`, 215, 'end') | ||
.adjustText(`#_discount_`, 215, 'end') | ||
.adjustText(`#_discountedSubtotal_`, 215, 'end') | ||
.adjustText(`#_tax_`, 215, 'end') | ||
.adjustText(`#_quoteTotal_`, 215, 'end') | ||
.replace('%totalAmount%', '$ ' + (subtotal - 100 + parseInt((subtotal - 100) * 0.1)).toLocaleString()) | ||
.replace('%remark%', 'This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark. This is a remark.') | ||
.adjustTextarea('#_remark_', 1343, 235) | ||
.replace('%additionalComment%', 'This is an additional comment. This is an additional comment. This is an additional comment. This is an additional comment. This is an additional comment. This is an additional comment. This is an additional comment. This is an additional comment. This is an additional comment.') | ||
.adjustTextarea('#_additionalComment_', 1830, 287) | ||
.replace('font-family="IPAexGothic"', 'font-family="monospace"') | ||
paper.apply() | ||
// actually, Element.clientWidth returns always 0 in test. | ||
// so mock Element.clientWidth only for vendorName elements, which have overflowing contents, and re-apply adjustText() to document | ||
for (const i of [...Array(26)].keys()) { | ||
jest.spyOn(document.querySelector(`#_vendorName_${i}_`), 'clientWidth', 'get').mockImplementation(() => 424) | ||
paper.adjustText(`#_vendorName_${i}_`, 200) | ||
} | ||
paper.apply() | ||
fs.writeFileSync('js/tests/output/real-world-paper-xd.svg', document.body.innerHTML) | ||
fs.writeFileSync('js/tests/output/real-world-paper-xd.html', '<!DOCTYPE html><html><head><link rel="stylesheet" href="../../../dist/svg-paper.min.css"></head><body>' + document.body.innerHTML + '</body></html>') | ||
}) |
{ | ||
"name": "svg-paper", | ||
"version": "0.0.0", | ||
"description": "", | ||
"version": "0.0.1", | ||
"description": "The world's most maintainable way to create paper-printable documents \uD83D\uDDA8\uD83D\uDC98", | ||
"homepage": "https://github.com/ttskch/svg-paper", | ||
@@ -6,0 +6,0 @@ "bugs": { |
218
README.md
@@ -1,1 +0,217 @@ | ||
# svg-paper | ||
# svg-paper | ||
[](https://travis-ci.com/ttskch/svg-paper) | ||
[](https://www.npmjs.com/package/svg-paper) | ||
[](https://www.npmjs.com/package/svg-paper) | ||
[](https://www.jsdelivr.com/package/npm/svg-paper) | ||
The world's most maintainable way to create paper-printable documents ๐จ๐ | ||
 | ||
## Concepts | ||
You can print beautiful and maintainable paper documents by following steps: | ||
1. Design the document with [Adobe XD](https://www.adobe.com/products/xd.html), [Figma](https://www.figma.com/), or something | ||
1. Export it as SVG | ||
1. Embed SVG into your html and fix it with **svg-paper** | ||
1. Write a little CSS for previewing | ||
1. That's it ๐ | ||
## Installation | ||
### CDN | ||
```html | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/svg-paper@0.0.0/dist/svg-paper.min.css"> | ||
<script src="https://cdn.jsdelivr.net/npm/svg-paper@0.0.0/dist/svg-paper.min.js"></script> | ||
``` | ||
### npm | ||
```bash | ||
$ npm install svg-paper | ||
``` | ||
## Basic usage | ||
```js | ||
import SvgPaper from 'svg-paper' | ||
// or | ||
// const SvgPaper = require('svg-paper') | ||
const paper = new SvgPaper() | ||
paper | ||
// replace placeholder to actual value | ||
.replace('%placeholder1%', 'Actual value 1') | ||
// ... and more | ||
// set max width to 1000 | ||
// in the other words, if actual width of the content is bigger than 1000 it shrinks automatically | ||
.adjustText('#selector1', 1000) | ||
// set max width to 800 and brings the element #_caseName to the center of 800 width area | ||
.adjustText('#selector2', 800, 'middle') | ||
// of course you can bring it to the end | ||
.adjustText('#selector3', 800, 'end') | ||
// automatically wrap or shrink actual content so that it fits within the specified area (600 x 300) | ||
.adjustTetxarea('#selector4', 600, 300) | ||
// you can pass some additional args | ||
.adjustTextarea('#selector5', | ||
600, // width | ||
300, // height | ||
1.2, // lineHeight : default is 1.2 times font-size | ||
0.5, // paddingX : default is 0.5 times font-size | ||
0.5, // paddingY : default is 0.5 times font-size | ||
false // nowrap : default is false. if true, content will not be wrapped | ||
) | ||
// finally, apply all replacing and adjusting to DOM | ||
.apply() | ||
``` | ||
Please see [test code](js/tests/functional.test.js) to learn more ๐ | ||
You also can see the live demo on your local by following steps: | ||
1. `git clone` this repo | ||
1. `npm install` | ||
1. `npm run test js/tests/functional.test.js` | ||
1. open `js/tests/output/real-world-paper-xd.html` in your browser | ||
### Tips: Hiding content before placeholders are replaced | ||
svg-paper replaces placeholders and adjust text/textarea after DOM loaded, so the content before replaced and adjusted will be shown on the screen for a moment ๐ค | ||
This problem is very easy to solve just by adding some "blinder" layer on the content and hide it after `.apply()` ๐ | ||
```html | ||
<body> | ||
<div id="blinder" style="width:100vw; height:100vh; background-color:#ccc"></div> | ||
<svg>...</svg> | ||
</body> | ||
``` | ||
```js | ||
paper.apply() | ||
document.querySelector('#blinder').style.display = 'none' | ||
``` | ||
## With non Node.js back-end | ||
If your back-end isn't Node.js, you can use svg-paper by passing replacements and text/textarea adjustment parameters to front-end as JSON string. | ||
### e.g. PHP and Twig | ||
```php | ||
// Controller | ||
public function paper(YourModel $model, YourPaperDefinition $paper) | ||
{ | ||
return $this->render('paper.html.twig', [ | ||
'svg' => $paper->getSvg(), | ||
'replacements' => $paper->getReplacements($model), | ||
'textAdjustments' => $paper->getTextAdjustments(), | ||
'textAreaAdjustments' => $paper->getTextAreaAdjustments(), | ||
]); | ||
} | ||
``` | ||
```php | ||
// YourPaperDefinition | ||
class YourPaperDefinition | ||
{ | ||
public function getSvg() | ||
{ | ||
return file_get_contents('/path/to/paper.svg'); | ||
} | ||
public function getReplacements(YourModel $model): array | ||
{ | ||
return [ | ||
'%placeholder1%' => 'Actual value 1', | ||
// ... and more | ||
]; | ||
} | ||
public function getTextAdjustments(): array | ||
{ | ||
return [ | ||
// selector => [args for SvgPaper.adjustText()] | ||
'#selector1' => [1000], | ||
'#selector2' => [800, 'middle'], | ||
'#selector3' => [800, 'end'], | ||
]; | ||
} | ||
public function getTextareaAdjustments(): array | ||
{ | ||
// selector => [args for SvgPaper.adjustTextarea()] | ||
return [ | ||
'#selector2' => [600, 300], | ||
// ... and more | ||
]; | ||
} | ||
} | ||
``` | ||
```twig | ||
{# paper.html.twig #} | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1"> | ||
<title>Paper</title> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/svg-paper@0.0.0/dist/svg-paper.min.css"> | ||
</head> | ||
<body class="{% block body_class %}{{ block('title') }}{% endblock %}"> | ||
{{ svg|raw }} | ||
<div data-replacements="{{ replacements|json_encode }}"></div> | ||
<div data-text-adjustments="{{ textAdjustments|json_encode }}"></div> | ||
<div data-textarea-adjustments="{{ textAdjustments|json_encode }}"></div> | ||
<script src="https://cdn.jsdelivr.net/npm/svg-paper@0.0.0/dist/svg-paper.min.js"></script> | ||
<script src="your-script.js"></script> | ||
</body> | ||
</html> | ||
``` | ||
```js | ||
// your-script.js | ||
const replacements = $('[data-replacements]').data('replacements') | ||
const textAdjustments = $('[data-adjustments]').data('text-adjustments') | ||
const textareaAdjustments = $('[data-adjustments]').data('textarea-adjustments') | ||
const paper = new SvgPaper() | ||
for (let [search, replacement] of Object.entries(replacements)) { | ||
paper.replace(search, replacement) | ||
} | ||
for (let [selector, args] of Object.entries(textAdjustments)) { | ||
paper.adjustText(selector, ...args) | ||
} | ||
for (let [selector, args] of Object.entries(textareaAdjustments)) { | ||
paper.adjustTextarea(selector, ...args) | ||
} | ||
paper.apply() | ||
``` | ||
## PDF Generation | ||
You can easily print to PDF by using [electron-pdf](https://github.com/fraserxu/electron-pdf). | ||
```bash | ||
$ npm install --global electron-pdf | ||
$ electron-pdf your-document.html your-document.pdf | ||
``` |
Sorry, the diff of this file is not supported yet
1525259
32
1087
218