Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pagedjs/pagedjs-cli
  • math712b/pagedjs-cli
  • valentin.schabschneider/pagedjs-cli
3 results
Show changes
Showing
with 7764 additions and 3183 deletions
File added
File added
File added
File added
File added
1.c52c62a7c50daf7d3f73ec16977cd4b0ea401710807d5dbe3850941dd1b73a70
\ No newline at end of file
{
"manifest_version": 2,
"name": "hyphens-data",
"version": "120.0.6050.0"
}
\ No newline at end of file
const Printer = require('./src/printer');
module.exports = Printer;
This diff is collapsed.
{
"name": "pagedjs-cli",
"version": "0.1.1",
"version": "0.4.3",
"author": "Fred Chasen",
"license": "MIT",
"homepage": "https://pagedmedia.org",
"homepage": "https://pagedjs.org",
"repository": {
"type": "git",
"url": "https://gitlab.pagedmedia.org/polyfills/pagedjs-cli.git"
"url": "https://gitlab.coko.foundation/pagedjs/pagedjs.git"
},
"bin": "./bin/paged",
"type": "module",
"main": "./src/index.js",
"exports": {
"import": "./src/index.js",
"require": "./src/index.js",
"default": "./src/index.js"
},
"bin": "./src/cli.js",
"scripts": {
"start": "./bin/paged",
"build": "pkg .",
"lint": "./node_modules/.bin/eslint src bin/paged"
"start": "./src/cli.js",
"build": "rollup -c",
"lint": "eslint src",
"test": "npm run lint",
"prepare": "npm run build"
},
"main": "index.js",
"dependencies": {
"commander": "^6.1.0",
"express": "^4.17.1",
"hyphenopoly": "^4.7.0",
"katex": "^0.12.0",
"lodash": "^4.17.20",
"mathjax": "^3.1.0",
"node-fetch": "^2.6.0",
"ora": "^5.0.0",
"pagedjs": "0.1.42",
"pdf-lib": "0.6.4",
"puppeteer": "^5.2.1",
"commander": "^11.1.0",
"express": "^4.18.2",
"html-entities": "^2.4.0",
"katex": "^0.16.9",
"lodash": "^4.17.21",
"mathjax": "^3.2.2",
"node-fetch": "^3.3.2",
"ora": "^8.0.1",
"pagedjs": "^0.4.3",
"pdf-lib": "1.17.1",
"puppeteer": "^21.9.0",
"replace-ext": "^2.0.0"
},
"devDependencies": {
"eslint": "^7.7.0"
}
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"eslint": "^8.56.0",
"rollup": "^4.9.6",
"rollup-plugin-license": "^3.2.0",
"rollup-plugin-serve": "^2.0.2"
},
"files": [
"dist",
"src"
]
}
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import license from "rollup-plugin-license";
const plugins = [
nodeResolve({
extensions: ['.cjs','.mjs', '.js']
}),
commonjs({
include: ["node_modules/**", "../../node_modules/**"]
}),
json(),
license({
banner: "@license Paged.js v<%= pkg.version %> | MIT | https://pagedjs.org",
})
];
export default [
{
input: "./src/browser.js",
output: {
name: "PagedPolyfill",
file: "./dist/browser.js",
format: "umd"
},
plugins: plugins
}
];
This diff is collapsed.
import * as Paged from "pagedjs";
window.Paged = Paged;
let ready = new Promise(function(resolve, reject){
if (document.readyState === "interactive" || document.readyState === "complete") {
resolve(document.readyState);
return;
}
document.onreadystatechange = function ($) {
if (document.readyState === "interactive") {
resolve(document.readyState);
}
};
});
let config = window.PagedConfig || {
auto: true,
before: undefined,
after: undefined,
content: undefined,
stylesheets: undefined,
renderTo: undefined,
settings: undefined
};
let previewer = new Paged.Previewer(config.settings);
ready.then(async function () {
let done;
if (config.before) {
await config.before();
}
if(config.auto !== false) {
done = await previewer.preview(config.content, config.stylesheets, config.renderTo);
}
if (config.after) {
await config.after(done);
}
});
export default previewer;
\ No newline at end of file
#!/usr/bin/env node
import { program } from "commander";
import ora from "ora";
import path from "path";
import fs from "fs";
import replaceExt from "replace-ext";
import Printer from "./printer.js";
// import pkg from "../package.json" assert { type: "json" };
function commaSeparatedList(value) {
return value.split(",");
}
program
// .version(pkg.version)
.arguments("[inputPath]")
.option("-i, --inputs [inputs]", "Inputs")
.option("-o, --output [output]", "Output")
.option("-d, --debug", "Debug")
.option("-l, --landscape", "Landscape printing", false)
.option("-s, --page-size [size]", "Print to Page Size [size]")
.option("-w, --width [size]", "Print to Page Width [width] in MM")
.option("-h --height [size]", "Print to Page Height [weight] in MM")
.option("--forceTransparentBackground", "Print with transparent background")
.option("-t, --timeout [ms]", "Set a max timeout of [ms]")
.option("-x, --html", "output html file")
.option("-b, --blockLocal", "Disallow access to filesystem for local files")
.option("-r, --blockRemote", "Disallow requests to remote servers")
.option("--allowedPath [allowedPaths]", "Only allow access to given filesystem paths, repeatable.", collect, [])
.option("--allowedDomain [allowedDomains]", "Only allow access to given remote domains, repeatable", collect, [])
.option("--outline-tags [tags]", "Specifies that an outline should be " +
"generated for the resulting PDF document. [tags] specifies which " +
"HTML tags should be considered for that outline. " +
"\"h1,h2\" will trigger an outline with \"h1\" tags as root elements " +
"and \"h2\" elements as their childs.")
.option("--additional-script <script>", "Additional script tags which are " +
"added to the HTML document before rendering. This is useful for " +
"adding custom pagedjs handlers. The option can be repeated.",
collect, [])
.option("--browserEndpoint <browserEndpoint>", "Use a remote Chrome server with browserWSEndpoint")
.option("--browserArgs <browserArgs>", "Launch Chrome with comma separated args", commaSeparatedList)
.option("--media [media]", "Emulate \"print\" or \"screen\" media, defaults to print.")
.option("--style <style>", "Path to CSS stylesheets to be added before rendering", collect, [])
.option("--warn", "Enable warning logs")
.option("--disable-script-injection", "Disable in injection of the polyphill script.")
.option("--extra-header <header:value>", "Header to be added to the page request.", collect, [])
.parse(process.argv);
function collect(value, previous) {
return previous.concat(value);
}
const options = program.opts();
let input = options.inputs || program.args[0];
let dir = process.cwd();
let relativePath;
let allowLocal;
try {
new URL(input);
allowLocal = false;
} catch (error) {
relativePath = path.resolve(dir, input);
allowLocal = !options.blockLocal;
}
const extraHTTPHeaders = options.extraHeader.reduce((acc, header) => {
const [name, ...value] = header.split(":");
return [ ...acc, { [name]: value.join(":") } ];
}, []);
let output;
if (!input) {
console.error("You must include an input path");
process.exit(1);
}
if (relativePath) {
if ([".html", ".xhtml"].indexOf(path.extname(relativePath)) === -1) {
console.error("Must pass a html or xhtml file as input");
process.exit(1);
}
try {
fs.accessSync(relativePath, fs.F_OK);
} catch (e) {
console.error("Input cannot be found", e);
process.exit(1);
}
}
if (typeof(options.output) === "string") {
output = path.resolve(dir, options.output);
} else if (typeof(options.output) !== "undefined") {
output = "./" + replaceExt(path.basename(input), ".pdf");
}
const spinner = ora({
spinner: "circleQuarters"
});
if (typeof input === "string") {
spinner.start("Loading: " + input);
} else {
spinner.start("Loading");
}
(async () => {
const printerOptions = {
debug: options.debug,
headless: options.headless,
allowLocal: allowLocal,
allowRemote: !options.blockRemote,
allowedPaths: options.allowedPaths,
allowedDomains: options.allowedDomains,
additionalScripts: options.additionalScript,
styles: options.style,
browserEndpoint: options.browserEndpoint,
timeout: options.timeout,
browserArgs: options.browserArgs,
emulateMedia: options.media,
enableWarnings: options.warn,
disableScriptInjection: options.disableScriptInjection,
extraHTTPHeaders: extraHTTPHeaders
};
if (options.forceTransparentBackground) {
printerOptions.overrideDefaultBackgroundColor = { r: 0, g: 0, b: 0, a: 0 }; // Workaround to get a transparent background in the resulting PDF. See https://bugs.chromium.org/p/chromium/issues/detail?id=498892 for more information.
}
let printer = new Printer(printerOptions);
printer.on("page", (page) => {
if (page.position === 0) {
spinner.succeed("Loaded");
spinner.start("Rendering: Page " + (page.position + 1));
} else {
spinner.text = "Rendering: Page " + (page.position + 1);
}
});
printer.on("rendered", (msg) => {
spinner.succeed(msg);
spinner.start("Generating");
});
printer.on("postprocessing", (msg) => {
spinner.succeed("Generated");
spinner.start("Processing");
});
options.outlineTags = !options.outlineTags ? ["h1","h2","h3"] : options.outlineTags.split(",");
let file;
if (options.html) {
file = await printer.html(input, options)
.catch((e) => {
console.error(e);
process.exit(1);
});
output = replaceExt(output, ".html");
} else if (options.debug === true) {
await printer.preview(input);
} else {
file = await printer.pdf(input, options)
.catch((e) => {
console.error(e);
process.exit(1);
});
}
spinner.succeed("Processed");
if (file && output) {
fs.writeFile(output, file, (err) => {
if (err) throw err;
spinner.succeed("Saved to " + output);
process.exit(0);
});
} else if (file) {
process.stdout.write(file);
}
})();
import Printer from "./printer.js";
export default Printer;
// Adapted from asciidoctor-web-pdf for HTML documents
// https://github.com/Mogztter/asciidoctor-web-pdf/blob/0a27de7423f12fe1f8b5ff7bcb720b786fb63e5b/lib/outline.js
import { PDFDict, PDFName, PDFNumber, PDFHexString } from "pdf-lib";
import { decode as htmlEntitiesDecode } from "html-entities";
const SanitizeXMLRx = /<[^>]+>/g;
function sanitize (string) {
if (string.includes("<")) {
string = string.replace(SanitizeXMLRx, "");
}
return htmlEntitiesDecode(string);
}
async function parseOutline(page, tags, enableWarnings) {
return await page.evaluate((tags) => {
const tagsToProcess = [];
for (const node of document.querySelectorAll(tags.join(","))) {
tagsToProcess.push(node);
}
tagsToProcess.reverse();
const root = {children: [], depth: -1};
let currentOutlineNode = root;
const linkHolder = document.createElement("div");
const body = document.querySelector("body");
linkHolder.style.display = "none";
body.insertBefore(linkHolder, body.firstChild);
while (tagsToProcess.length > 0) {
const tag = tagsToProcess.pop();
const orderDepth = tags.indexOf(tag.tagName.toLowerCase());
const dest = encodeURIComponent(tag.id).replace(/%/g, "#25");
// Add to link holder to register a destination
const hiddenLink = document.createElement("a");
hiddenLink.href = "#"+dest;
linkHolder.appendChild(hiddenLink);
if (orderDepth < currentOutlineNode.depth) {
currentOutlineNode = currentOutlineNode.parent;
tagsToProcess.push(tag);
} else {
const newNode = {
title: tag.innerText.trim(),
// encode section ID until https://bugs.chromium.org/p/chromium/issues/detail?id=985254 is fixed
destination: dest,
children: [],
depth: orderDepth,
};
if (orderDepth == currentOutlineNode.depth) {
if (currentOutlineNode.parent) {
newNode.parent = currentOutlineNode.parent;
currentOutlineNode.parent.children.push(newNode);
} else {
newNode.parent = currentOutlineNode;
currentOutlineNode.children.push(newNode);
}
currentOutlineNode = newNode;
} else if (orderDepth > currentOutlineNode.depth) {
newNode.parent = currentOutlineNode;
currentOutlineNode.children.push(newNode);
currentOutlineNode = newNode;
}
}
}
const stripParentProperty = (node) => {
node.parent = undefined;
for (const child of node.children) {
stripParentProperty(child);
}
};
stripParentProperty(root);
return root.children;
}, tags);
}
function setRefsForOutlineItems (layer, context, parentRef) {
for (const item of layer) {
item.ref = context.nextRef();
item.parentRef = parentRef;
setRefsForOutlineItems(item.children, context, item.ref);
}
}
function countChildrenOfOutline (layer) {
let count = 0;
for (const item of layer) {
++count;
count += countChildrenOfOutline(item.children);
}
return count;
}
function buildPdfObjectsForOutline (layer, context) {
for (const [i, item] of layer.entries()) {
const prev = layer[i - 1];
const next = layer[i + 1];
const pdfObject = new Map([
[PDFName.of("Title"), PDFHexString.fromText(sanitize(item.title))],
[PDFName.of("Dest"), PDFName.of(item.destination)],
[PDFName.of("Parent"), item.parentRef]
]);
if (prev) {
pdfObject.set(PDFName.of("Prev"), prev.ref);
}
if (next) {
pdfObject.set(PDFName.of("Next"), next.ref);
}
if (item.children.length > 0) {
pdfObject.set(PDFName.of("First"), item.children[0].ref);
pdfObject.set(PDFName.of("Last"), item.children[item.children.length - 1].ref);
pdfObject.set(PDFName.of("Count"), PDFNumber.of(countChildrenOfOutline(item.children)));
}
context.assign(item.ref, PDFDict.fromMapWithContext(pdfObject, context));
buildPdfObjectsForOutline(item.children, context);
}
}
function generateWarningsAboutMissingDestinations (layer, pdfDoc) {
const dests = pdfDoc.context.lookup(pdfDoc.catalog.get(PDFName.of("Dests")));
// Dests can be undefined if the PDF wasn't successfully generated (for instance if Paged.js threw an exception)
if (dests) {
const validDestinationTargets = dests.entries().map(([key, _]) => key.value());
for (const item of layer) {
if (item.destination && !validDestinationTargets.includes("/" + item.destination)) {
console.warn(`Unable to find destination "${item.destination}" while generating PDF outline.`);
}
generateWarningsAboutMissingDestinations(item.children, pdfDoc);
}
}
}
async function setOutline (pdfDoc, outline, enableWarnings=false) {
const context = pdfDoc.context;
const outlineRef = context.nextRef();
if (outline.length === 0) {
return pdfDoc;
}
if (enableWarnings) {
generateWarningsAboutMissingDestinations(outline, pdfDoc);
}
setRefsForOutlineItems(outline, context, outlineRef);
buildPdfObjectsForOutline(outline, context);
const outlineObject = PDFDict.fromMapWithContext(new Map([
[PDFName.of("First"), outline[0].ref],
[PDFName.of("Last"), outline[outline.length - 1].ref],
[PDFName.of("Count"), PDFNumber.of(countChildrenOfOutline(outline))]
]), context);
context.assign(outlineRef, outlineObject);
pdfDoc.catalog.set(PDFName.of("Outlines"), outlineRef);
return pdfDoc;
}
export {
parseOutline,
setOutline
};
\ No newline at end of file
const PDFLib = require("pdf-lib");
const EventEmitter = require("events");
export function setTrimBoxes(pdfDoc, pages) {
const pdfPages = pdfDoc.getPages();
const PDFDocumentWriter = require("./writer");
pdfPages.forEach((pdfPage, index) => {
const page = pages[index];
class PostProcesser extends EventEmitter {
constructor(pdf) {
super();
if (!page) {
return; // page was not rendered
}
if (!pdf) {
throw "Must pass a PDF Buffer to PostProcesser";
}
this.pdf = pdf;
this.pdfDoc = PDFLib.PDFDocumentFactory.load(pdf);
}
let { boxes } = page;
metadata(meta) {
if (meta.keywords && typeof meta.keywords === "string") {
meta.keywords = meta.keywords.split(",");
}
if (Object.is(boxes.media, boxes.crop)) {
return; // No bleed set
}
if (!meta.keywords) {
meta.keywords = [];
}
// Overwrite Dates
if (!(meta.creationDate instanceof Date)) {
meta.creationDate = new Date();
}
meta.modDate = new Date();
meta.metadataDate = new Date();
// Get the existing Info
let info = this.getInfoDict();
if (!meta.creator) {
meta.creator = info.creator + " + Paged.js";
}
if (!meta.producer) {
meta.producer = info.producer;
}
// Add meta
this.addXmpMetadata(meta);
this.updateInfoDict(meta);
}
getInfoDict(){
// Info Reference in Skia pdfs is always 1st
let ref = PDFLib.PDFIndirectReference.forNumbers(1, 0);
let info = this.pdfDoc.index.lookup(ref);
return {
title: info.getMaybe("Title") && info.getMaybe("Title").string,
subject: info.getMaybe("Subject") && info.getMaybe("Subject").string,
keywords: info.getMaybe("Keywords") && info.getMaybe("Keywords").string,
author: info.getMaybe("Author") && info.getMaybe("Author").string,
creationDate: info.getMaybe("CreationDate") && info.getMaybe("CreationDate").string,
modDate: info.getMaybe("ModDate") && info.getMaybe("ModDate").string,
creator: info.getMaybe("Creator") && info.getMaybe("Creator").string,
producer: info.getMaybe("Producer") && info.getMaybe("Producer").string
};
}
updateInfoDict(meta) {
// Info Reference in Skia pdfs is always 1st
let ref = PDFLib.PDFIndirectReference.forNumbers(1, 0);
let info = this.pdfDoc.index.lookup(ref);
if (meta.title) {
info.set("Title", PDFLib.PDFString.fromString(meta.title));
}
if (meta.subject) {
info.set("Subject", PDFLib.PDFString.fromString(meta.subject));
}
if (meta.keywords && meta.keywords.length) {
info.set("Keywords", PDFLib.PDFString.fromString(meta.keywords.join(", ")));
}
if (meta.author) {
info.set("Author", PDFLib.PDFString.fromString(meta.author));
}
if (meta.creationDate) {
info.set("CreationDate", PDFLib.PDFString.fromString(meta.creationDate.toISOString()));
}
if (meta.modDate) {
info.set("ModDate", PDFLib.PDFString.fromString(meta.modDate.toISOString()));
}
if (meta.creator) {
info.set("Creator", PDFLib.PDFString.fromString(meta.creator));
}
if (meta.producer) {
info.set("Producer", PDFLib.PDFString.fromString(meta.producer));
}
}
addXmpMetadata(meta) {
const charCodes = (str) => str.split("").map((c) => c.charCodeAt(0));
const typedArrayFor = (str) => new Uint8Array(charCodes(str));
const whitespacePadding = new Array(20).fill(" ".repeat(100)).join("\n");
const metadataXML = `
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.2-c001 63.139439, 2010/09/27-13:37:26">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:format>application/pdf</dc:format>
<dc:creator>
<rdf:Seq>
<rdf:li>${meta.author}</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">${meta.title}</rdf:li>
</rdf:Alt>
</dc:title>
<dc:subject>
<rdf:Bag>
${meta.keywords
.map((keyword) => `<rdf:li>${keyword}</rdf:li>`)
.join("\n")}
</rdf:Bag>
</dc:subject>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<xmp:CreatorTool>${meta.creatorTool}</xmp:CreatorTool>
<xmp:CreateDate>${meta.creationDate.toISOString()}</xmp:CreateDate>
<xmp:ModifyDate>${meta.modDate.toISOString()}</xmp:ModifyDate>
<xmp:MetadataDate>${meta.metadataDate.toISOString()}</xmp:MetadataDate>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<pdf:Subject>${meta.subject}</pdf:Subject>
<pdf:Producer>${meta.producer}</pdf:Producer>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
${whitespacePadding}
<?xpacket end="w"?>
`.trim();
const metadataStreamDict = PDFLib.PDFDictionary.from(
{
Type: PDFLib.PDFName.from("Metadata"),
Subtype: PDFLib.PDFName.from("XML"),
Length: PDFLib.PDFNumber.fromNumber(metadataXML.length),
},
this.pdfDoc.index,
);
const metadataStream = PDFLib.PDFRawStream.from(
metadataStreamDict,
typedArrayFor(metadataXML),
);
const metadataStreamRef = this.pdfDoc.register(metadataStream);
this.pdfDoc.catalog.set("Metadata", metadataStreamRef);
}
boxes(pages) {
const pdfPages = this.pdfDoc.getPages();
pdfPages.forEach((pdfPage, index) => {
const page = pages[index];
if (!page) {
return; // page was not rendered
}
let { boxes } = page;
if (Object.is(boxes.media, boxes.crop)) {
return; // No bleed set
}
const rectangle = PDFLib.PDFArray.fromArray(
[
PDFLib.PDFNumber.fromNumber(boxes.crop.x),
PDFLib.PDFNumber.fromNumber(boxes.crop.y),
PDFLib.PDFNumber.fromNumber(boxes.crop.width + boxes.crop.x),
PDFLib.PDFNumber.fromNumber(boxes.crop.height + boxes.crop.y),
],
pdfPage.index,
);
pdfPage.set("TrimBox", rectangle);
});
}
updatePageBoxes(page) {
console.log(page);
}
/**
* Adds a table of content to the generated PDF
*
* Ideally this would not be required if Chromium would add this directly.
* So if these bugs are closed this can probably be removed again:
* - https://bugs.chromium.org/p/chromium/issues/detail?id=840455
* - https://github.com/GoogleChrome/puppeteer/issues/1778
*
* This code is heavily based on @Hopding's comment at:
* https://github.com/Hopding/pdf-lib/issues/127#issuecomment-502450179
*/
addOutline(outlineSpec) {
const outline = JSON.parse(JSON.stringify(outlineSpec));
const pageRefs = [];
this.pdfDoc.catalog.Pages.traverse((kid, ref) => {
if (kid instanceof PDFLib.PDFPage)
pageRefs.push(ref);
});
const index = this.pdfDoc.index;
const outlineReference = index.nextObjectNumber();
const countOutlineLayer = (layer) => {
let count = 0;
for (const outlineEntry of layer) {
++count;
count += countOutlineLayer(outlineEntry.children);
}
return count;
};
const createItemsForOutlineLayer = (layer, parent) => {
layer.forEach((outlineItem, i) => {
let prev = i > 0 ? layer[i - 1].ref : null;
let next = i < layer.length - 1 ? layer[i + 1].ref : null;
const pdfItem = createOutlineItem(outlineItem, prev, next, parent);
index.assign(outlineItem.ref, pdfItem);
});
};
const createOutlineItem = (outlineItem, prev, next, parent) => {
if (!outlineItem.id) {
throw new Error(`Cannot generate outline item with title '${outlineItem.title} ` +
"without any target anchor. Please specify an 'id' attribute for " +
"the relevant HTML element");
}
const item = {
Title: PDFLib.PDFString.fromString(outlineItem.title),
Parent: parent,
Dest: PDFLib.PDFName.from(outlineItem.id),
};
if (prev) {
item.Prev = prev;
}
if (next) {
item.Next = next;
}
if (outlineItem.children.length > 0) {
item.First = outlineItem.children[0].ref;
item.Last = outlineItem.children[outlineItem.children.length - 1].ref;
item.Count = PDFLib.PDFNumber.fromNumber(countOutlineLayer(outlineItem.children));
createItemsForOutlineLayer(outlineItem.children, outlineItem.ref);
}
return PDFLib.PDFDictionary.from(item, index);
};
const createOutlineReferences = (outlineEntry) => {
outlineEntry.ref = index.nextObjectNumber();
for (const child of outlineEntry.children) {
createOutlineReferences(child);
}
};
for (const outlineItem of outline) {
createOutlineReferences(outlineItem);
}
createItemsForOutlineLayer(outline, outlineReference);
const pdfOutline = PDFLib.PDFDictionary.from(
{
First: outline[0].ref,
Last: outline[outline.length - 1].ref,
Count: PDFLib.PDFNumber.fromNumber(countOutlineLayer(outline)),
},
index,
);
index.assign(outlineReference, pdfOutline);
this.pdfDoc.catalog.set("Outlines", outlineReference);
}
save() {
let writer = new PDFDocumentWriter();
const pdfBytes = writer.saveToBytesWithXRefTable(this.pdfDoc);
this.pdf = pdfBytes;
return this.pdf;
}
pdfPage.setTrimBox(boxes.crop.x,
boxes.crop.y,
boxes.crop.width + boxes.crop.x,
boxes.crop.height + boxes.crop.y);
});
}
module.exports = PostProcesser;
export function setMetadata(pdfDoc, meta) {
if (meta.keywords && typeof meta.keywords === "string") {
meta.keywords = meta.keywords.split(",");
}
if (!meta.keywords) {
meta.keywords = [];
}
// Overwrite Dates
if (!(meta.creationDate instanceof Date)) {
meta.creationDate = new Date();
}
meta.modDate = new Date();
meta.metadataDate = new Date();
// Get the existing Info
if (!meta.creator) {
let creator = pdfDoc.getCreator();
meta.creator = creator + " + Paged.js";
}
if (!meta.producer) {
let producer = pdfDoc.getProducer();
meta.producer = producer;
}
if (meta.title) {
pdfDoc.setTitle(meta.title);
}
if (meta.subject) {
pdfDoc.setSubject(meta.subject);
}
if (meta.keywords && meta.keywords.length) {
pdfDoc.setKeywords(meta.keywords);
}
if (meta.author) {
pdfDoc.setAuthor(meta.author);
}
if (meta.creationDate) {
pdfDoc.setCreationDate(meta.creationDate);
}
if (meta.modDate) {
pdfDoc.setModificationDate(meta.modDate);
}
if (meta.creator) {
pdfDoc.setCreator(meta.creator);
}
if (meta.producer) {
pdfDoc.setProducer(meta.producer);
}
}
\ No newline at end of file
This diff is collapsed.
const PDFLib = require("pdf-lib");
const isFunction = require( "lodash/isFunction" );
const last = require( "lodash/last" );
const sortBy = require( "lodash/sortBy" );
const PDFXRefTableFactory = require( "pdf-lib/lib/core/pdf-structures/factories/PDFXRefTableFactory" ).default;
const createIndirectObjectsFromIndex = ({ index }) => {
let catalogRef;
const streamObjects = [];
const nonStreamObjects = [];
index.forEach((object, ref) => {
if (object instanceof PDFLib.PDFCatalog) catalogRef = ref;
const array =
object instanceof PDFLib.PDFStream ? streamObjects : nonStreamObjects;
array.push(PDFLib.PDFIndirectObject.of(object).setReference(ref));
});
return { catalogRef, streamObjects, nonStreamObjects };
};
const computeOffsets = (
startingOffset,
indirectObjects,
) =>
indirectObjects.map((object) => ({
objectNumber: object.reference.objectNumber,
generationNumber: object.reference.generationNumber,
startOffset: startingOffset,
endOffset: startingOffset += object.bytesSize(),
}));
class PDFDocumentWriter extends PDFLib.PDFDocumentWriter {
constructor() {
super();
}
saveToBytesWithXRefTable(pdfDoc) {
const {
catalogRef,
streamObjects,
nonStreamObjects,
} = createIndirectObjectsFromIndex(pdfDoc.index);
if (!catalogRef) console.error("Missing PDFCatalog");
streamObjects.forEach((streamObj) => {
if (isFunction(streamObj.pdfObject.encode)) streamObj.pdfObject.encode();
});
const merged = [...streamObjects, ...nonStreamObjects];
const offsets = computeOffsets(pdfDoc.header.bytesSize(), merged);
const sortedOffsets = sortBy(offsets, "objectNumber");
/* ===== (2) Create XRefTable and Trailer ===== */
const table = PDFXRefTableFactory.forOffsets(sortedOffsets);
const tableOffset = last(offsets).endOffset;
const trailer = PDFLib.PDFTrailer.from(
tableOffset,
PDFLib.PDFDictionary.from(
{
Size: PDFLib.PDFNumber.fromNumber(last(sortedOffsets).objectNumber + 1),
Root: catalogRef,
Info: PDFLib.PDFIndirectReference.forNumbers(1, 0), // TODO: this is specific to Skia
},
pdfDoc.index,
),
);
/* ===== (3) Create buffer and copy objects into it ===== */
const bufferSize = tableOffset + table.bytesSize() + trailer.bytesSize();
const buffer = new Uint8Array(bufferSize);
/* eslint-disable no-unused-vars */
// TODO: how is remaining used?
let remaining = pdfDoc.header.copyBytesInto(buffer);
remaining = merged.reduce(
(remBytes, indirectObj) => indirectObj.copyBytesInto(remBytes),
remaining,
);
remaining = table.copyBytesInto(remaining);
remaining = trailer.copyBytesInto(remaining);
/* eslint-enable no-unused-vars */
return buffer;
}
}
module.exports = PDFDocumentWriter;
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>Hyphens Test</title>
<meta name="author" content="Wikipedia">
<style media="print">
@page {
size: 8.5in 11in;
margin: 20mm 25mm;
@top-center {
vertical-align: bottom;
padding-bottom: 10mm;
content: string(booktitle);
}
}
@page :left {
margin: 20mm 60mm 20mm 60mm;
@top-left {
vertical-align: bottom;
padding-bottom: 10mm;
content: string(page-number, first-except);
letter-spacing: 0.1em;
margin-left: -1em;
font-size: 0.9em;
}
@bottom-left-corner {
content: counter(page);
}
}
@page :right {
margin: 20mm 60mm 20mm 60mm;
@top-right {
vertical-align: bottom;
padding-bottom: 10mm;
content: string(page-number, first-except);
letter-spacing: 0.08em;
margin-right: -1em;
font-size: 0.9em;
}
@bottom-right-corner {
content: counter(page);
}
@top-center{
content: string(booktitle);
}
}
@page cover {
@top-center{
content: none;
}
}
section:nth-child(1) h1 {
string-set: booktitle content(text);
}
section {
break-before: right;
break-after: always;
}
body {
font-size: 1em;
line-height: 1.33em;
font-family: serif;
font-variant-numeric: oldstyle-nums;
text-align: justify;
hyphens:auto
}
</style>
</head>
<body>
<section class="body-rw Chapter-rw">
The CO2 fertilization effect or carbon fertilization effect causes an increased rate of photosynthesis while limiting leaf transpiration in plants. Both processes result from increased levels of atmospheric carbon dioxide (CO2).[2][3] The carbon fertilization effect varies depending on plant species, air and soil temperature, and availability of water and nutrients.[4][5] Net primary productivity (NPP) might positively respond to the carbon fertilization effect.[6] Although, evidence shows that enhanced rates of photosynthesis in plants due to CO2 fertilization do not directly enhance all plant growth, and thus carbon storage.[4] The carbon fertilization effect has been reported to be the cause of 44% of gross primary productivity (GPP) increase since the 2000s.[1] Earth System Models, Land System Models and Dynamic Global Vegetation Models are used to investigate and interpret vegetation trends related to increasing levels of atmospheric CO2.[4][7] However, the ecosystem processes associated with the CO2 fertilization effect remain uncertain and therefore are challenging to model.[8][9]
Terrestrial ecosystems have reduced atmospheric CO2 concentrations and have partially mitigated climate change effects.[10] The response by plants to the carbon fertilization effect is unlikely to significantly reduce atmospheric CO2 concentration over the next century due to the increasing anthropogenic influences on atmospheric CO2.[3][4][11][12] Earth's vegetated lands have shown significant greening since the early 1980s[13] largely due to rising levels of atmospheric CO2.[14][15][16][17]
Theory predicts the tropics to have the largest uptake due to the carbon fertilization effect, but this has not been observed. The amount of CO2 uptake from CO2 fertilization also depends on how forests respond to climate change, and if they are protected from deforestation.[18]
Changes in atmospheric carbon dioxide may reduce the nutritional quality of some crops, with for instance wheat having less protein and less of some minerals.[19]: 439 [20] Food crops could see a reduction of protein, iron and zinc content in common food crops of 3 to 17%.[21]
Mechanism
Main article: Photosynthesis
Through photosynthesis, plants use CO2 from the atmosphere, water from the ground, and energy from the sun to create sugars used for growth and fuel.[22] While using these sugars as fuel releases carbon back into the atmosphere (photorespiration), growth stores carbon in the physical structures of the plant (i.e. leaves, wood, or non-woody stems).[23] With about 19 percent of Earth's carbon stored in plants,[24] plant growth plays an important role in storing carbon on the ground rather than in the atmosphere. In the context of carbon storage, growth of plants is often referred to as biomass productivity.[23][25][26] This term is used because researchers compare the growth of different plant communities by their biomass, amount of carbon they contain.
Increased biomass productivity directly increases the amount of carbon stored in plants.[23] And because researchers are interested in carbon storage, they are interested in where most of the biomass is found in individual plants or in an ecosystem. Plants will first use their available resources for survival and support the growth and maintenance of the most important tissues like leaves and fine roots which have short lives.[27] With more resources available plants can grow more permanent, but less necessary tissues like wood.[27]
If the air surrounding plants has a higher concentration of carbon dioxide, they may be able to grow better and store more carbon[28] and also store carbon in more permanent structures like wood.[23] Evidence has shown this occurring for a few different reasons. First, plants that were otherwise limited by carbon or light availability benefit from a higher concentration of carbon.[29] Another reason is that plants are able use water more efficiently because of reduced stomatal conductance.[30] Plants experiencing higher CO2 concentrations may benefit from a greater ability to gain nutrients from mycorrhizal fungi in the sugar-for-nutrients transaction.[31] The same interaction can may also increase the amount of carbon stored in the soil by mycorrhizal fungi.[32]
Observations and trends
From 2002 to 2014, plants appear to have gone into overdrive, starting to pull more CO2 out of the air than they have done before.[33] The result was that the rate at which CO2 accumulates in the atmosphere did not increase during this time period, although previously, it had grown considerably in concert with growing greenhouse gas emissions.[33]
A 1993 review of scientific greenhouse studies found that a doubling of CO2 concentration would stimulate the growth of 156 different plant species by an average of 37%. Response varied significantly by species, with some showing much greater gains and a few showing a loss. For example, a 1979 greenhouse study found that with doubled CO2 concentration the dry weight of 40-day-old cotton plants doubled, but the dry weight of 30-day-old maize plants increased by only 20%.[34][35]
In addition to greenhouse studies, field and satellite measurements attempt to understand the effect of increased CO2 in more natural environments. In free-air carbon dioxide enrichment (FACE) experiments plants are grown in field plots and the CO2 concentration of the surrounding air is artificially elevated. These experiments generally use lower CO2 levels than the greenhouse studies. They show lower gains in growth than greenhouse studies, with the gains depending heavily on the species under study. A 2005 review of 12 experiments at 475–600 ppm showed an average gain of 17% in crop yield, with legumes typically showing a greater response than other species and C4 plants generally showing less. The review also stated that the experiments have their own limitations. The studied CO2 levels were lower, and most of the experiments were carried out in temperate regions.[36] Satellite measurements found increasing leaf area index for 25% to 50% of Earth's vegetated area over the past 35 years (i.e., a greening of the planet), providing evidence for a positive CO2 fertilization effect.[37][38]
Depending on environment, there are differential responses to elevated atmospheric CO2 between major 'functional types' of plant, such as C3 and C4 plants, or more or less woody species; which has the potential among other things to alter competition between these groups.[39][40] Increased CO2 can also lead to increased Carbon : Nitrogen ratios in the leaves of plants or in other aspects of leaf chemistry, possibly changing herbivore nutrition.[41] Studies show that doubled concentrations of CO2 will show an increase in photosynthesis in C3 plants but not in C4 plants.[42] However, it is also shown that C4 plants are able to persist in drought better than the C3 plants.[43]
Experimentation by enrichment
The effects of CO2 enrichment can be most simply attained in a greenhouse (see Greenhouse § Carbon dioxide enrichment for its agricultural use). However, for experimentation, the results obtained in a greenhouse would be doubted due to it introducing too many confounding variables. Open-air chambers have been similarly doubted, with some critiques attributing, e.g., a decline in mineral concentrations found in these CO2-enrichment experiments to constraints put on the root system. The current state-of-the art is the FACE methodology, where CO2 is put out directly in the open field.[44] Even then, there are doubts over whether the results of FACE in one part of the world applies to another.[45]
Free-Air CO2 Enrichment (FACE) experiments
Main article: Free-air concentration enrichment
The ORNL conducted FACE experiments where CO2 levels were increased above ambient levels in forest stands.[46] These experiments showed:[47]
Increased root production stimulated by increased CO2, resulting in more soil carbon.
An initial increase of net primary productivity, which was not sustained.
Faster decline in nitrogen availability in increased CO2 forest plots.
Change in plant community structure, with minimal change in microbial community structure.[48]
Enhanced CO2 cannot significantly increase the leaf carrying capacity or leaf area index of an area.[48]
FACE experiments have been criticized as not being representative of the entire globe. These experiments were not meant to be extrapolated globally. Similar experiments are being conducted in other regions such as in the Amazon rainforest in Brazil.[45]
</section>
</body>
</html>