node-geogebra
Version:
Generate graphics with geogebra from nodejs
159 lines (152 loc) • 6.26 kB
text/typescript
import * as puppeteer from 'puppeteer';
import { EventEmitter } from 'events';
import { GGBOptions } from './GGBOptions';
import * as path from 'path';
let window: any;
const DEBUG = false;
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
export class GGBPlotter {
releasedEmitter: EventEmitter;
id: string | number;
poolOpts: GGBOptions;
pagePromise: Promise<puppeteer.Page>;
browser: puppeteer.Browser;
constructor(id?: number | GGBOptions, page?: puppeteer.Page, releasedEmitter?: EventEmitter) {
if (id) {
if (typeof(id) == "number") {
this.id = id;
} else {
this.poolOpts = {plotters: 3 , ggb: "local", ...id};
this.id = Math.random().toString(32).substring(2);
}
} else {
this.poolOpts = {plotters: 3 , ggb: "local"};
this.id = Math.random().toString(32).substring(2);
}
this.pagePromise = this.createPage(page);
this.releasedEmitter = releasedEmitter;
}
private async createPage(page: puppeteer.Page): Promise<puppeteer.Page> {
if (page) {
return page;
} else {
const opts: puppeteer.LaunchOptions = {
devtools: false,
args: ["--allow-file-access-from-files", "--non-secure",
"--allow-running-insecure-content", "--no-sandbox",
"--no-startup-window"]
};
this.browser = await puppeteer.launch(opts);
const newPage = await this.browser.newPage();
let url;
if( this.poolOpts.ggb==="local") {
const dir = path.resolve(__dirname, "../geogebra-math-apps-bundle/Geogebra/HTML5/5.0/GeoGebra.html");
url = "file://" + dir;
} else {
url = "https://www.geogebra.org/classic";
}
await newPage.goto(url, {waitUntil: 'networkidle2'});
DEBUG && console.log( url + " has been loaded");
await newPage.waitForFunction("window.ggbApplet!=null");
DEBUG && console.log( "ggbApplet is ready");
await newPage.evaluate('window.ggbApplet.evalCommand(\'SetPerspective("G")\\nShowGrid(true)\')');
DEBUG && console.log( "SetPerspective->G, showGrid->true");
return newPage;
}
}
async ready() {
return this.pagePromise;
}
async evalGGBScript(ggbScript: string[], width?: number, height?: number) {
const page = await this.pagePromise;
// 53 px accounts for the toolbar which cannot be removed in geogebra app mode
await page.setViewport({ width: width || 600, height: (height || 400) + 53 });
if (ggbScript && ggbScript.length) {
await page.evaluate((x) => window.ggbApplet.evalCommand(x), ggbScript.join("\n"));
}
}
async exportPNG(alpha?: boolean, dpi?: number): Promise<Buffer> {
const pdf64 = await this.exportPNG64(alpha, dpi);
const raw = pdf64.replace("data:image/png;base64,", "");
return Buffer.from(raw, 'base64');
}
async exportPNG64(alpha?: boolean, dpi?: number): Promise<string> {
const page = await this.pagePromise;
const out = await page.evaluate((alpha, dpi) => window.ggbApplet.getPNGBase64(1, alpha, dpi || 300), alpha, dpi);
return "data:image/png;base64," + out;
}
async exportSVG(): Promise<string> {
const page = await this.pagePromise;
return page.evaluate(() => window.ggbApplet.exportSVG());
}
async exportSVG64(): Promise<string> {
const svg = await this.exportSVG();
return "data:image/svg+xml;base64," + Buffer.from(svg).toString('base64');
}
async exportPDF(): Promise<Buffer> {
const pdf64 = await this.exportPDF64();
const raw = pdf64.replace("data:application/pdf;base64,", "");
return Buffer.from(raw, 'base64');
}
async exportPDF64(): Promise<string> {
const page = await this.pagePromise;
return page.evaluate(() => window.ggbApplet.exportPDF());
}
async exportGGB(): Promise<Buffer> {
const raw = await this.exportGGB64();
return Buffer.from(raw, 'base64');
}
async exportGGB64(): Promise<string> {
const page = await this.pagePromise;
return page.evaluate(() => window.ggbApplet.getBase64());
}
async export(format: string): Promise<string | Buffer> {
switch (format) {
case("pngalpha"): return this.exportPNG(true);
case("pdf"): return this.exportPDF();
case("svg"): return this.exportSVG();
case("ggb"): return this.exportGGB();
default: return this.exportPNG();
}
}
async export64(format: string): Promise<string> {
switch (format) {
case("pngalpha"): return this.exportPNG64(true);
case("pdf"): return this.exportPDF64();
case("svg"): return this.exportSVG64();
case("ggb"): return this.exportGGB64();
default: return this.exportPNG64();
}
}
async reset() {
const page = await this.pagePromise;
await page.evaluate(() => window.ggbApplet.reset());
}
async exec(ggbAppletProperty: string, args?: any[]) {
const page = await this.pagePromise;
await page.evaluate((prop, argz) => {
const property = window.ggbApplet[prop];
if (typeof(property) === "function") {
return property.apply(window.ggbApplet, argz);
} else {
return property;
}
}, ggbAppletProperty, args);
}
async release() {
const page = await this.pagePromise;
await page.evaluate(() => window.ggbApplet.reset());
if (this.releasedEmitter) {
// notify to the cue that a worker has been released and must be returned to the pool
this.releasedEmitter.emit("released", this);
}
if (this.browser) {
await page.close();
await this.browser.close();
}
}
}