Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import pdfs #125

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion nwb.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = {
webpack: {
publicPath: "",
config(config) {
config.entry = "./src/index.js";
config.entry = ["./src/index.js", "pdfjs-dist/build/pdf.worker.entry"];
config.resolve.extensions = [".ts", ".tsx", ".js", ".jsx"];
config.module.rules.push({
test: /\.tsx?$/,
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@mehra/ts": "^5.2.0",
"fabric": "^4.2.0",
"keyboardjs": "^2.6.4",
"pdfjs-dist": "^2.6.347",
"pdfmake": "^0.1.70",
"react": "^16.13.1",
"react-dom": "^16.13.1",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Toolbar from "./Toolbar";
import Stylebar from "./Stylebar";
import HelpModal from "./HelpModal";
import ContextMenu from "./ContextMenu";
import VirtualFileInput from "./VirtualFileInput";
import VirtualFileInput from "./virtual/VirtualFileInput";

export const enum Visibility {
None,
Expand Down
12 changes: 12 additions & 0 deletions src/components/virtual/VirtualElement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

export interface VirtualElementProps<T extends HTMLElement> {
/**
* Your own callback so that you can record the reference {@param ref} to the input.
* That way, you can
* invoke `click()` (and therefore open a file dialog) or
* render to a canvas
* programmatically.
*/
captureRef?: (ref: React.RefObject<T>) => void;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useRef } from "react";
import { VirtualElementProps } from "./VirtualElement";

/**
* Passed to [[`VirtualFileInput`]]
*/
interface VirtualFileInputProps {
interface VirtualFileInputProps extends VirtualElementProps<HTMLInputElement> {
/**
* An `onChange` callback that is given the extracted `FileList` as {@param files} if and only if
* * it exists (is non-null), and
Expand All @@ -13,12 +14,6 @@ interface VirtualFileInputProps {
* Use the `onChange` attribute if you instead want the raw handler
*/
acceptFiles?: (files: FileList) => void;

/**
* Your own callback so that you can record the reference {@param ref} to the input.
* That way, you can invoke `click()`, and therefore open the file dialog, programmatically.
*/
captureRef?: (ref: React.RefObject<HTMLInputElement>) => void;
}

const VirtualFileInput = ({
Expand Down
88 changes: 77 additions & 11 deletions src/lib/files.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fabric } from "fabric";
import { MalformedExpressionException, RequireSubType } from "@mehra/ts";
import { fabric } from "fabric";
import { getDocument } from "pdfjs-dist";

import HistoryHandler from "./history";
import Pages, { PageJSON } from "./pages";
Expand All @@ -9,6 +10,21 @@ const defaults = <T>(value: T | undefined, getDefaultValue: () => T) =>
value === undefined ? getDefaultValue() : value;

export class AsyncReader {
static readAsText = (file: File): Promise<string> =>
new Promise((resolve, reject) => {
AsyncReader.setup<"Text">(resolve, reject).readAsText(file);
});

static readAsDataURL = (file: File): Promise<string> =>
new Promise((resolve, reject) => {
AsyncReader.setup<"DataURL">(resolve, reject).readAsDataURL(file);
});

static readAsArrayBuffer = (file: File): Promise<ArrayBuffer> =>
new Promise((resolve, reject) => {
AsyncReader.setup<"ArrayBuffer">(resolve, reject).readAsArrayBuffer(file);
});

private static setup = <
ReadType extends "Text" | "DataURL" | "ArrayBuffer" | "BinaryString"
>(
Expand Down Expand Up @@ -39,27 +55,21 @@ export class AsyncReader {
? { readAsArrayBuffer: readFn }
: never;
};

static readAsText = (file: File): Promise<string> =>
new Promise((resolve, reject) => {
AsyncReader.setup<"Text">(resolve, reject).readAsText(file);
});

static readAsDataURL = (file: File): Promise<string> =>
new Promise((resolve, reject) => {
AsyncReader.setup<"DataURL">(resolve, reject).readAsDataURL(file);
});
}

type JSONFile = File & { type: "application/json" };
type ImageFile = File & { type: `image/${string}` };
type PDFFile = File & { type: "application/pdf" };

const isJSONFile = (file: File): file is JSONFile =>
file.type === "application/json";

const isImageFile = (file: File): file is ImageFile =>
file.type.startsWith("image/");

const isPDFFile = (file: File): file is PDFFile =>
file.type === "application/pdf";

/**
* Common to _all_ versions of exports
*/
Expand Down Expand Up @@ -200,6 +210,11 @@ export class JSONWriter {
};
}

const loadSVGFromString = (str: string) =>
new Promise<fabric.Object[]>((resolve) => {
fabric.loadSVGFromString(str, (results) => resolve(results));
});

export default class FileHandler {
constructor(public pages: Pages, private history: HistoryHandler) {}

Expand Down Expand Up @@ -227,6 +242,11 @@ export default class FileHandler {
// eslint-disable-next-line no-await-in-loop
await this.handleJSON(file);
}

if (isPDFFile(file)) {
// eslint-disable-next-line no-await-in-loop
await this.handlePDF(file);
}
}

this.history.add(additions);
Expand Down Expand Up @@ -301,4 +321,50 @@ export default class FileHandler {
const pages = JSONReader.read(await AsyncReader.readAsText(file));
return this.pages.insertPagesAfter(pages);
};

private handlePDF = async (file: PDFFile): Promise<number> => {
console.log("Handling pdf", file);
const doc = await getDocument({
data: new Uint8Array(await AsyncReader.readAsArrayBuffer(file)),
fontExtraProperties: true,
}).promise;

// Don't call get() accessor multiple times
const { numPages } = doc;
console.log({ numPages });
for (let i = 0; i < 1 /*numPages*/; i++) {
this.pages.savePage();
// eslint-disable-next-line no-await-in-loop
await this.pages.insertPagesAfter();

// eslint-disable-next-line no-await-in-loop
const page = await doc.getPage(i + 1);

const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d")!;
const viewport = page.getViewport({ scale: 1 });
console.log({ viewport });
canvas.height = viewport.height;
canvas.width = viewport.width;

console.log(canvas);

// eslint-disable-next-line no-await-in-loop
await page.render({ canvasContext, viewport }).promise;
// console.log(canvas);
const fabricCanvas = new fabric.Canvas(canvas);
console.log(fabricCanvas._objects);
const svgStr = fabricCanvas.toSVG();

console.log({ svgStr });

// eslint-disable-next-line no-await-in-loop
const objects = await loadSVGFromString(svgStr);

this.pages.canvas.add(...objects).requestRenderAll();
this.pages.updateState();
}

return 0;
};
}
24 changes: 13 additions & 11 deletions src/lib/qboard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { fabric } from "fabric";
import { Network } from "@mehra/ts";
import { fabric } from "fabric";
import { RefObject } from "react";

import AssertType from "../types/assert";
import {
FabricIEvent,
GuaranteedIObjectOptions,
isFabricCollection,
ObjectId,
PathEvent,
} from "../types/fabric";
import { HTMLChildElement } from "../types/html";

import instantiateTools, { Tool, Tools } from "./tools";
import Page from "./page";
Expand All @@ -10,19 +21,10 @@ import ClipboardHandler from "./clipboard";
import StyleHandler, { Dash, Fill, Stroke, Style } from "./styles";
import ActionHandler from "./action";
import KeyboardHandler, { KeyMap } from "./keyboard";
import { HTMLChildElement } from "../types/html";
import {
FabricIEvent,
GuaranteedIObjectOptions,
isFabricCollection,
ObjectId,
PathEvent,
} from "../types/fabric";

type Async<T = void> = T | Promise<T>;

type FabricHandler<T extends fabric.IEvent = fabric.IEvent> = (e: T) => Async;
import AssertType from "../types/assert";

export interface QBoardState {
dragActive: boolean;
Expand Down Expand Up @@ -75,7 +77,7 @@ export default class QBoard {
/**
* A ref to the global input element used for file input
*/
fileInputRef?: React.RefObject<HTMLInputElement>;
fileInputRef?: RefObject<HTMLInputElement>;
toggleHelpModal?: () => void;
} = {};

Expand Down