mirror of
https://github.com/cubexy/obsidian-pdf-printer.git
synced 2026-07-22 05:48:03 +00:00
Initial working version
This commit is contained in:
parent
71c6b8e7ba
commit
f1a36cde99
6 changed files with 2267 additions and 35 deletions
50
canvasFactory.ts
Normal file
50
canvasFactory.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { Canvas, createCanvas, CanvasRenderingContext2D } from "canvas";
|
||||
|
||||
type CanvasWithContext = {
|
||||
canvas: Canvas | null;
|
||||
context: CanvasRenderingContext2D | null;
|
||||
};
|
||||
|
||||
export class NodeCanvasFactory {
|
||||
create(
|
||||
width: number,
|
||||
height: number,
|
||||
transparent: boolean
|
||||
): CanvasWithContext {
|
||||
const canvas = createCanvas(width, height);
|
||||
const context = canvas.getContext("2d", { alpha: transparent });
|
||||
if (transparent) context.clearRect(0, 0, width, height);
|
||||
return {
|
||||
canvas,
|
||||
context,
|
||||
};
|
||||
}
|
||||
|
||||
reset(
|
||||
canvasAndContext: CanvasWithContext,
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
if (!canvasAndContext.canvas) {
|
||||
throw new Error("Canvas is not specified");
|
||||
}
|
||||
if (!canvasAndContext.context) {
|
||||
throw new Error("Context is not specified");
|
||||
}
|
||||
canvasAndContext.canvas.width = width;
|
||||
canvasAndContext.canvas.height = height;
|
||||
}
|
||||
|
||||
destroy(canvasAndContext: CanvasWithContext) {
|
||||
if (!canvasAndContext.canvas) {
|
||||
throw new Error("Canvas is not specified");
|
||||
}
|
||||
if (!canvasAndContext.context) {
|
||||
throw new Error("Context is not specified");
|
||||
}
|
||||
canvasAndContext.canvas.width = 0;
|
||||
canvasAndContext.canvas.height = 0;
|
||||
canvasAndContext.canvas = null;
|
||||
canvasAndContext.context = null;
|
||||
}
|
||||
}
|
||||
120
main.ts
120
main.ts
|
|
@ -1,3 +1,4 @@
|
|||
import { NodeCanvasFactory } from "canvasFactory";
|
||||
import {
|
||||
App,
|
||||
Editor,
|
||||
|
|
@ -8,9 +9,8 @@ import {
|
|||
PluginSettingTab,
|
||||
Setting,
|
||||
TFile,
|
||||
moment,
|
||||
loadPdfJs,
|
||||
} from "obsidian";
|
||||
import { Options, pdf } from "pdf-to-img";
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
|
|
@ -22,6 +22,16 @@ const DEFAULT_SETTINGS: MyPluginSettings = {
|
|||
mySetting: "default",
|
||||
};
|
||||
|
||||
type PDFDocumentBuffer = {
|
||||
fileName: string;
|
||||
pages: PDFPage[];
|
||||
};
|
||||
|
||||
type PDFPage = {
|
||||
pageNumber: number;
|
||||
buffer: ArrayBuffer;
|
||||
};
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
|
||||
|
|
@ -63,9 +73,14 @@ export default class MyPlugin extends Plugin {
|
|||
if (!pdfFile) {
|
||||
return;
|
||||
}
|
||||
const pngFiles = await this.parsePDF(pdfFile, { scale: 2 });
|
||||
const fileBufferArray = await this.parsePDF(pdfFile);
|
||||
const imagePathList = await this.convertPDFBufferToImages({
|
||||
fileName: pdfFile.name,
|
||||
pages: fileBufferArray,
|
||||
});
|
||||
|
||||
editor.replaceSelection(
|
||||
pngFiles.map((f) => `![[${f}]]`).join("\n")
|
||||
imagePathList.map((f) => `![[${f}]]`).join("\n")
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -121,6 +136,12 @@ export default class MyPlugin extends Plugin {
|
|||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input string adheres to the format ![[something]].
|
||||
* @param input the string to parse
|
||||
* @description checks if the input is a valid file path
|
||||
* @returns found file path (something) or null
|
||||
*/
|
||||
private checkPathInput(input: string): string | null {
|
||||
const matches = input.match(/!\[\[([^\]]+)\]\]/); // match ![[something]], -> something (capture group 1)
|
||||
if (matches && matches[1]) {
|
||||
|
|
@ -129,16 +150,14 @@ export default class MyPlugin extends Plugin {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the path of a file and returns the TFile object if it exists in the vault.
|
||||
* If the file is not found, it returns null.
|
||||
* @param path path to the file (either just the name or the full path)
|
||||
* @returns File object if it exists, null otherwise
|
||||
*/
|
||||
private fetchVaultFile(path: string): TFile | null {
|
||||
const vaultFiles = this.app.vault.getFiles();
|
||||
const file =
|
||||
vaultFiles.find((f) => f.path === path) ??
|
||||
vaultFiles.find((f) => f.path.contains(path)); // we attempt to find the file by path, if that fails we try to find it by substring
|
||||
if (!file) {
|
||||
new Notice(`File not found: ${path}.`);
|
||||
return null;
|
||||
}
|
||||
return file;
|
||||
return this.app.metadataCache.getFirstLinkpathDest(path, "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -171,19 +190,72 @@ export default class MyPlugin extends Plugin {
|
|||
return file;
|
||||
}
|
||||
|
||||
async parsePDF(file: TFile, options: Options): Promise<string[]> {
|
||||
const document = await pdf(file.path, options);
|
||||
let i = 1;
|
||||
let fileNames: string[] = [];
|
||||
for await (const image of document) {
|
||||
const newFile = await this.app.vault.createBinary(
|
||||
file.basename + `2img-${i}.png`,
|
||||
image
|
||||
/**
|
||||
* Parses a given TFile object (PDF) and converts it to PNG images.
|
||||
* The images are saved in the vault and their paths are returned as an array of strings.
|
||||
* @param file The TFile object representing the PDF file to be parsed.
|
||||
* @description The function uses the pdfjs library to convert the PDF to images.
|
||||
* @throws Error if the canvas or context cannot be created.
|
||||
* @returns An array of strings representing the paths of the saved PNG images.
|
||||
*/
|
||||
async parsePDF(file: TFile): Promise<PDFPage[]> {
|
||||
const pdfjs = await loadPdfJs();
|
||||
const arrayBuffer = await this.app.vault.adapter.readBinary(file.path);
|
||||
const buffer = new Uint8Array(arrayBuffer);
|
||||
const document = await pdfjs.getDocument(buffer).promise;
|
||||
|
||||
const pages: number = document.numPages;
|
||||
let pdfBuffer: PDFPage[] = [];
|
||||
|
||||
for (let i = 1; i <= pages; i++) {
|
||||
const page = await document.getPage(i);
|
||||
const viewport = page.getViewport({ scale: 2 });
|
||||
const canvasFactory = new NodeCanvasFactory();
|
||||
const { canvas, context } = canvasFactory.create(
|
||||
viewport.width,
|
||||
viewport.height,
|
||||
false
|
||||
);
|
||||
i++;
|
||||
fileNames.push(newFile.path);
|
||||
if (!canvas || !context) {
|
||||
console.error("could not generate canvas or context");
|
||||
return [];
|
||||
}
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
const blob = await new Promise<Blob | null>((resolve) => {
|
||||
/** @ts-ignore because toBlob exists, but is not found */
|
||||
canvas.toBlob(resolve, "image/png");
|
||||
});
|
||||
|
||||
if (blob) {
|
||||
pdfBuffer.push({
|
||||
pageNumber: i,
|
||||
buffer: await blob.arrayBuffer(), // Store base64 string (binary data)
|
||||
});
|
||||
} else {
|
||||
console.error(`could not generate blob for page ${i}`);
|
||||
}
|
||||
}
|
||||
return fileNames;
|
||||
|
||||
return pdfBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PDF page buffer to a PNG file and saves it in the vault.
|
||||
* @param page The PDF page buffer to convert.
|
||||
* @param pageNumber The page number of the PDF.
|
||||
* @returns The path of the saved PNG file.
|
||||
*/
|
||||
private async convertPDFBufferToImages(
|
||||
pdfBuffer: PDFDocumentBuffer
|
||||
): Promise<string[]> {
|
||||
const pngFilePaths: string[] = [];
|
||||
for (const page of pdfBuffer.pages) {
|
||||
const pngFileName = `${pdfBuffer.fileName}-${page.pageNumber}.png`;
|
||||
const pngFilePath = `${pngFileName}`;
|
||||
await this.app.vault.createBinary(pngFilePath, page.buffer);
|
||||
pngFilePaths.push(pngFilePath);
|
||||
}
|
||||
return pngFilePaths;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"id": "pdf-annotator",
|
||||
"name": "PDF Annotator",
|
||||
"version": "0.0.1",
|
||||
"id": "obsidian-pdf-printer",
|
||||
"name": "PDF Printer",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "A tool that allows greater annotation of PDFs.",
|
||||
"author": "Obsidian",
|
||||
"description": "Convert your PDF notes/files to images without external dependencies.",
|
||||
"author": "cubexy",
|
||||
"authorUrl": "https://github.com/cubexy",
|
||||
"fundingUrl": "https://www.tierschutzbund.de/helfen/spenden/jetzt-spenden/",
|
||||
"isDesktopOnly": true
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
2111
package-lock.json
generated
Normal file
2111
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"name": "obsidian-pdf-printer",
|
||||
"version": "0.1.0",
|
||||
"description": "Convert your PDF notes/files to images without external dependencies.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
"0.1.0": "0.15.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue