剩余模块:生产入库,销售出库

This commit is contained in:
2025-05-20 16:07:49 +08:00
parent 933ddab8f3
commit b1d8dec263
299 changed files with 38798 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
"use strict";
// const {asBuffer, asDownload, asZipDownload, atScale, options} = require('./io')
import io from "./io";
const { asBuffer, asDownload, asZipDownload, atScale, options } = io;
//
// Browser equivalents of the skia-canvas convenience initializers and polyfills for
// the Canvas objects newPage & export methods
//
const _toURL_ = Symbol.for("toDataURL");
const loadImage = src =>
new Promise((onload, onerror) =>
Object.assign(new Image(), {
crossOrigin: "Anonymous",
onload,
onerror,
src
})
);
class Canvas {
constructor(width, height) {
// alert(1)
let elt = document.createElement("canvas"),
pages = [];
Object.defineProperty(elt, "async", {
value: true,
writable: false,
enumerable: true
});
for (var [prop, get] of Object.entries({
png: () => asBuffer(elt, "image/png"),
jpg: () => asBuffer(elt, "image/jpeg"),
pages: () => pages.concat(elt).map(c => c.getContext("2d"))
}))
Object.defineProperty(elt, prop, { get });
return Object.assign(elt, {
width,
height,
newPage(...size) {
var { width, height } = elt,
page = Object.assign(document.createElement("canvas"), {
width,
height
});
page.getContext("2d").drawImage(elt, 0, 0);
pages.push(page);
var [width, height] = size.length ? size : [width, height];
return Object.assign(elt, { width, height }).getContext("2d");
},
saveAs(filename, args) {
args = typeof args == "number" ? { quality: args } : args;
let opts = options(this.pages, { filename, ...args }),
{ pattern, padding, mime, quality, matte, density, archive } = opts,
pages = atScale(opts.pages, density);
return padding == undefined
? asDownload(pages[0], mime, quality, matte, filename)
: asZipDownload(
pages,
mime,
quality,
matte,
archive,
pattern,
padding
);
},
toBuffer(extension = "png", args = {}) {
args = typeof args == "number" ? { quality: args } : args;
let opts = options(this.pages, { extension, ...args }),
{ mime, quality, matte, pages, density } = opts,
canvas = atScale(pages, density, matte)[0];
return asBuffer(canvas, mime, quality, matte);
},
[_toURL_]: elt.toDataURL.bind(elt),
toDataURL(extension = "png", args = {}) {
args = typeof args == "number" ? { quality: args } : args;
let opts = options(this.pages, { extension, ...args }),
{ mime, quality, matte, pages, density } = opts,
canvas = atScale(pages, density, matte)[0],
url = canvas[canvas === elt ? _toURL_ : "toDataURL"](mime, quality);
return Promise.resolve(url);
}
});
}
}
const {
CanvasRenderingContext2D,
CanvasGradient,
CanvasPattern,
Image,
ImageData,
Path2D,
DOMMatrix,
DOMRect,
DOMPoint
} = window;
// module.exports = {
// Canvas,
// loadImage,
// CanvasRenderingContext2D,
// CanvasGradient,
// CanvasPattern,
// Image,
// ImageData,
// Path2D,
// DOMMatrix,
// DOMRect,
// DOMPoint
// };
const obj = {
Canvas,
loadImage,
CanvasRenderingContext2D,
CanvasGradient,
CanvasPattern,
Image,
ImageData,
Path2D,
DOMMatrix,
DOMRect,
DOMPoint
};
export default obj;

361
node_modules/vue-qr/packages/skia-canvas-lib/lib/css.js generated vendored Normal file
View File

@@ -0,0 +1,361 @@
"use strict";
//
// Parsers for properties that take CSS-style strings as values
//
// -- Font & Variant --------------------------------------------------------------------
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant
// https://www.w3.org/TR/css-fonts-3/#font-size-prop
import splitBy from "string-split-by";
var m,
cache = { font: {}, variant: {} };
const styleRE = /^(normal|italic|oblique)$/,
smallcapsRE = /^(normal|small-caps)$/,
stretchRE = /^(normal|(semi-|extra-|ultra-)?(condensed|expanded))$/,
namedSizeRE = /(?:xx?-)?small|smaller|medium|larger|(?:xx?-)?large|normal/,
numSizeRE = /^([\d\.]+)(px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q)/,
namedWeightRE = /^(normal|bold(er)?|lighter)$/,
numWeightRE = /^(1000|\d{1,3})$/,
parameterizedRE = /([\w\-]+)\((.*?)\)/,
unquote = s => s.replace(/^(['"])(.*?)\1$/, "$2"),
isSize = s => namedSizeRE.test(s) || numSizeRE.test(s),
isWeight = s => namedWeightRE.test(s) || numWeightRE.test(s);
function parseFont(str) {
if (cache.font[str] === undefined) {
try {
if (typeof str !== "string")
throw new Error("Font specification must be a string");
if (!str) throw new Error("Font specification cannot be an empty string");
let font = {
style: "normal",
variant: "normal",
weight: "normal",
stretch: "normal"
},
value = str.replace(/\s*\/\*s/, "/"),
tokens = splitBy(value, /\s+/),
token;
while ((token = tokens.shift())) {
let match = styleRE.test(token)
? "style"
: smallcapsRE.test(token)
? "variant"
: stretchRE.test(token)
? "stretch"
: isWeight(token)
? "weight"
: isSize(token)
? "size"
: null;
switch (match) {
case "style":
case "variant":
case "stretch":
case "weight":
font[match] = token;
break;
case "size":
// size is the pivot point between the style fields and the family name stack,
// so start processing what's been collected
let [emSize, leading] = splitBy(token, "/"),
size = parseSize(emSize),
lineHeight = parseSize(
(leading || "1.2").replace(/(\d)$/, "$1em"),
size
),
weight = parseWeight(font.weight),
family = splitBy(tokens.join(" "), /\s*,\s*/).map(unquote),
features =
font.variant == "small-caps" ? { on: ["smcp", "onum"] } : {},
{ style, stretch, variant } = font;
// make sure all the numeric fields have legitimate values
let invalid = !isFinite(size)
? `font size "${emSize}"`
: !isFinite(lineHeight)
? `line height "${leading}"`
: !isFinite(weight)
? `font weight "${font.weight}"`
: family.length == 0
? `font family "${tokens.join(", ")}"`
: false;
if (!invalid) {
// include a re-stringified version of the decoded/absified values
return (cache.font[str] = Object.assign(font, {
size,
lineHeight,
weight,
family,
features,
canonical: [
style,
variant !== style && variant,
[variant, style].indexOf(weight) == -1 && weight,
[variant, style, weight].indexOf(stretch) == -1 && stretch,
`${size}px/${lineHeight}px`,
family.map(nm => (nm.match(/\s/) ? `"${nm}"` : nm)).join(", ")
]
.filter(Boolean)
.join(" ")
}));
}
throw new Error(`Invalid ${invalid}`);
default:
throw new Error(`Unrecognized font attribute "${token}"`);
}
}
throw new Error("Could not find a font size value");
} catch (e) {
// console.warn(Object.assign(e, {name:"Warning"}))
cache.font[str] = null;
}
}
return cache.font[str];
}
function parseSize(str, emSize = 16) {
if ((m = numSizeRE.exec(str))) {
let [size, unit] = [parseFloat(m[1]), m[2]];
return (
size *
(unit == "px"
? 1
: unit == "pt"
? 1 / 0.75
: unit == "%"
? emSize / 100
: unit == "pc"
? 16
: unit == "in"
? 96
: unit == "cm"
? 96.0 / 2.54
: unit == "mm"
? 96.0 / 25.4
: unit == "q"
? 96 / 25.4 / 4
: unit.match("r?em")
? emSize
: NaN)
);
}
if ((m = namedSizeRE.exec(str))) {
return emSize * (sizeMap[m[0]] || 1.0);
}
return NaN;
}
function parseWeight(str) {
return (m = numWeightRE.exec(str))
? parseInt(m[0]) || NaN
: (m = namedWeightRE.exec(str))
? weightMap[m[0]]
: NaN;
}
function parseVariant(str) {
if (cache.variant[str] === undefined) {
let variants = [],
features = { on: [], off: [] };
for (let token of splitBy(str, /\s+/)) {
if (token == "normal") {
return { variants: [token], features: { on: [], off: [] } };
} else if (token in featureMap) {
featureMap[token].forEach(feat => {
if (feat[0] == "-") features.off.push(feat.slice(1));
else features.on.push(feat);
});
variants.push(token);
} else if ((m = parameterizedRE.exec(token))) {
let subPattern = alternatesMap[m[1]],
subValue = Math.max(0, Math.min(99, parseInt(m[2], 10))),
[feat, val] = subPattern
.replace(/##/, subValue < 10 ? "0" + subValue : subValue)
.replace(/#/, Math.min(9, subValue))
.split(" ");
if (typeof val == "undefined") features.on.push(feat);
else features[feat] = parseInt(val, 10);
variants.push(`${m[1]}(${subValue})`);
} else {
throw new Error(`Invalid font variant "${token}"`);
}
}
cache.variant[str] = { variant: variants.join(" "), features: features };
}
return cache.variant[str];
}
// -- Image Filters -----------------------------------------------------------------------
// https://developer.mozilla.org/en-US/docs/Web/CSS/filter
var plainFilterRE = /(blur|hue-rotate|brightness|contrast|grayscale|invert|opacity|saturate|sepia)\((.*?)\)/,
shadowFilterRE = /drop-shadow\((.*)\)/,
percentValueRE = /^(\+|-)?\d+%$/,
angleValueRE = /([\d\.]+)(deg|g?rad|turn)/;
function parseFilter(str) {
let filters = {};
let canonical = [];
for (var spec of splitBy(str, /\s+/) || []) {
if ((m = shadowFilterRE.exec(spec))) {
let kind = "drop-shadow",
args = m[1].trim().split(/\s+/),
lengths = args.slice(0, 3),
color = args.slice(3).join(" "),
dims = lengths.map(s => parseSize(s)).filter(isFinite);
if (dims.length == 3 && !!color) {
filters[kind] = [...dims, color];
canonical.push(
`${kind}(${lengths.join(" ")} ${color.replace(/ /g, "")})`
);
}
} else if ((m = plainFilterRE.exec(spec))) {
let [kind, arg] = m.slice(1);
let val =
kind == "blur"
? parseSize(arg)
: kind == "hue-rotate"
? parseAngle(arg)
: parsePercentage(arg);
if (isFinite(val)) {
filters[kind] = val;
canonical.push(`${kind}(${arg.trim()})`);
}
}
}
return str.trim() == "none"
? { canonical: "none", filters }
: canonical.length
? { canonical: canonical.join(" "), filters }
: null;
}
function parsePercentage(str) {
return percentValueRE.test(str.trim()) ? parseInt(str, 10) / 100 : NaN;
}
function parseAngle(str) {
if ((m = angleValueRE.exec(str.trim()))) {
let [amt, unit] = [parseFloat(m[1]), m[2]];
return unit == "deg"
? amt
: unit == "rad"
? (360 * amt) / (2 * Math.PI)
: unit == "grad"
? (360 * amt) / 400
: unit == "turn"
? 360 * amt
: NaN;
}
}
//
// Font attribute keywords & corresponding values
//
const weightMap = {
lighter: 300,
normal: 400,
bold: 700,
bolder: 800
};
const sizeMap = {
"xx-small": 3 / 5,
"x-small": 3 / 4,
small: 8 / 9,
smaller: 8 / 9,
large: 6 / 5,
larger: 6 / 5,
"x-large": 3 / 2,
"xx-large": 2 / 1,
normal: 1.2 // special case for lineHeight
};
const featureMap = {
normal: [],
// font-variant-ligatures
"common-ligatures": ["liga", "clig"],
"no-common-ligatures": ["-liga", "-clig"],
"discretionary-ligatures": ["dlig"],
"no-discretionary-ligatures": ["-dlig"],
"historical-ligatures": ["hlig"],
"no-historical-ligatures": ["-hlig"],
contextual: ["calt"],
"no-contextual": ["-calt"],
// font-variant-position
super: ["sups"],
sub: ["subs"],
// font-variant-caps
"small-caps": ["smcp"],
"all-small-caps": ["c2sc", "smcp"],
"petite-caps": ["pcap"],
"all-petite-caps": ["c2pc", "pcap"],
unicase: ["unic"],
"titling-caps": ["titl"],
// font-variant-numeric
"lining-nums": ["lnum"],
"oldstyle-nums": ["onum"],
"proportional-nums": ["pnum"],
"tabular-nums": ["tnum"],
"diagonal-fractions": ["frac"],
"stacked-fractions": ["afrc"],
ordinal: ["ordn"],
"slashed-zero": ["zero"],
// font-variant-east-asian
jis78: ["jp78"],
jis83: ["jp83"],
jis90: ["jp90"],
jis04: ["jp04"],
simplified: ["smpl"],
traditional: ["trad"],
"full-width": ["fwid"],
"proportional-width": ["pwid"],
ruby: ["ruby"],
// font-variant-alternates (non-parameterized)
"historical-forms": ["hist"]
};
const alternatesMap = {
stylistic: "salt #",
styleset: "ss##",
"character-variant": "cv##",
swash: "swsh #",
ornaments: "ornm #",
annotation: "nalt #"
};
// module.exports = {
// font: parseFont,
// variant: parseVariant,
// size: parseSize,
// filter: parseFilter
// };
export default {
font: parseFont,
variant: parseVariant,
size: parseSize,
filter: parseFilter
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,234 @@
/// <reference lib="dom"/>
/// <reference types="node" />
export function loadImage(src: string | Buffer): Promise<Image>
export class DOMMatrix extends globalThis.DOMMatrix {}
export class DOMPoint extends globalThis.DOMPoint {}
export class DOMRect extends globalThis.DOMRect {}
export class Image extends globalThis.Image {}
export class ImageData extends globalThis.ImageData {}
export class CanvasGradient extends globalThis.CanvasGradient {}
export class CanvasPattern extends globalThis.CanvasPattern {}
export class CanvasTexture {}
//
// Canvas
//
export type ExportFormat = "png" | "jpg" | "jpeg" | "pdf" | "svg";
export interface RenderOptions {
/** Page to export: Defaults to 1 (i.e., first page) */
page?: number
/** Background color to draw beneath transparent parts of the canvas */
matte?: string
/** Number of pixels per grid point (defaults to 1) */
density?: number
/** Quality for lossy encodings like JPEG (0.01.0) */
quality?: number
/** Convert text to paths for SVG exports */
outline?: boolean
}
export interface SaveOptions extends RenderOptions {
/** Image format to use */
format?: ExportFormat
}
export class Canvas {
/** @internal */
constructor(width?: number, height?: number)
static contexts: WeakMap<Canvas, readonly CanvasRenderingContext2D[]>
/**
* @deprecated Use the saveAsSync, toBufferSync, and toDataURLSync methods
* instead of setting the async property to false
*/
async: boolean
width: number
height: number
getContext(type?: "2d"): CanvasRenderingContext2D
newPage(width?: number, height?: number): CanvasRenderingContext2D
readonly pages: CanvasRenderingContext2D[]
saveAs(filename: string, options?: SaveOptions): Promise<void>
toBuffer(format: ExportFormat, options?: RenderOptions): Promise<Buffer>
toDataURL(format: ExportFormat, options?: RenderOptions): Promise<string>
saveAsSync(filename: string, options?: SaveOptions): void
toBufferSync(format: ExportFormat, options?: RenderOptions): Buffer
toDataURLSync(format: ExportFormat, options?: RenderOptions): string
get pdf(): Promise<Buffer>
get svg(): Promise<Buffer>
get jpg(): Promise<Buffer>
get png(): Promise<Buffer>
}
//
// Context
//
type Offset = [x: number, y: number] | number
export interface CreateTextureOptions {
/** The 2D shape to be drawn in a repeating grid with the specified spacing (if omitted, parallel lines will be used) */
path?: Path2D
/** The lineWidth with which to stroke the path (if omitted, the path will be filled instead) */
line?: number
/** The color to use for stroking/filling the path */
color?: string
/** The orientation of the pattern grid in radians */
angle?: number
/** The amount by which to shift the pattern relative to the canvas origin */
offset?: Offset
}
export type CanvasImageSource = Canvas | Image;
interface CanvasDrawImage {
drawImage(image: CanvasImageSource, dx: number, dy: number): void;
drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;
drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
drawCanvas(image: Canvas, dx: number, dy: number): void;
drawCanvas(image: Canvas, dx: number, dy: number, dw: number, dh: number): void;
drawCanvas(image: Canvas, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
}
interface CanvasFillStrokeStyles {
fillStyle: string | CanvasGradient | CanvasPattern | CanvasTexture;
strokeStyle: string | CanvasGradient | CanvasPattern | CanvasTexture;
createConicGradient(startAngle: number, x: number, y: number): CanvasGradient;
createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;
createPattern(image: CanvasImageSource, repetition: string | null): CanvasPattern | null;
createTexture(spacing: Offset, options?: CreateTextureOptions): CanvasTexture
}
type QuadOrRect = [x1:number, y1:number, x2:number, y2:number, x3:number, y3:number, x4:number, y4:number] |
[left:number, top:number, right:number, bottom:number] | [width:number, height:number]
export interface CanvasRenderingContext2D extends CanvasCompositing, CanvasDrawImage, CanvasDrawPath, CanvasFillStrokeStyles, CanvasFilters, CanvasImageData, CanvasImageSmoothing, CanvasPath, CanvasPathDrawingStyles, CanvasRect, CanvasShadowStyles, CanvasState, CanvasText, CanvasTextDrawingStyles, CanvasTransform, CanvasUserInterface {
readonly canvas: Canvas;
fontVariant: string;
textTracking: number;
textWrap: boolean;
lineDashMarker: Path2D | null;
lineDashFit: "move" | "turn" | "follow";
get currentTransform(): DOMMatrix
set currentTransform(matrix: DOMMatrix)
createProjection(quad: QuadOrRect, basis?: QuadOrRect): DOMMatrix
conicCurveTo(cpx: number, cpy: number, x: number, y: number, weight: number): void
// getContextAttributes(): CanvasRenderingContext2DSettings;
fillText(text: string, x: number, y:number, maxWidth?: number): void
strokeText(text: string, x: number, y:number, maxWidth?: number): void
measureText(text: string, maxWidth?: number): TextMetrics
outlineText(text: string): Path2D
}
//
// Bézier Paths
//
export interface Path2DBounds {
readonly top: number
readonly left: number
readonly bottom: number
readonly right: number
readonly width: number
readonly height: number
}
export type Path2DEdge = [verb: string, ...args: number[]]
export class Path2D extends globalThis.Path2D {
d: string
readonly bounds: Path2DBounds
readonly edges: readonly Path2DEdge[]
contains(x: number, y: number): boolean
conicCurveTo(
cpx: number,
cpy: number,
x: number,
y: number,
weight: number
): void
complement(otherPath: Path2D): Path2D
difference(otherPath: Path2D): Path2D
intersect(otherPath: Path2D): Path2D
union(otherPath: Path2D): Path2D
xor(otherPath: Path2D): Path2D
interpolate(otherPath: Path2D, weight: number): Path2D
jitter(segmentLength: number, amount: number, seed?: number): Path2D
offset(dx: number, dy: number): Path2D
points(step?: number): readonly [x: number, y: number][]
round(radius: number): Path2D
simplify(rule?: "nonzero" | "evenodd"): Path2D
transform(...args: [matrix: DOMMatrix] | [a: number, b: number, c: number, d: number, e: number, f: number]): Path2D;
trim(start: number, end: number, inverted?: boolean): Path2D;
trim(start: number, inverted?: boolean): Path2D;
unwind(): Path2D
}
//
// Typography
//
export interface TextMetrics extends globalThis.TextMetrics {
lines: TextMetricsLine[]
}
export interface TextMetricsLine {
readonly x: number
readonly y: number
readonly width: number
readonly height: number
readonly baseline: number
readonly startIndex: number
readonly endIndex: number
}
export interface FontFamily {
family: string
weights: number[]
widths: string[]
styles: string[]
}
export interface Font {
family: string
weight: number
style: string
width: string
file: string
}
export interface FontLibrary {
families: readonly string[]
family(name: string): FontFamily | undefined
has(familyName: string): boolean
use(familyName: string, fontPaths?: string | readonly string[]): Font[]
use(fontPaths: readonly string[]): Font[]
use(
families: Record<string, readonly string[] | string>
): Record<string, Font[] | Font>
}
export const FontLibrary: FontLibrary

1208
node_modules/vue-qr/packages/skia-canvas-lib/lib/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

399
node_modules/vue-qr/packages/skia-canvas-lib/lib/io.js generated vendored Normal file
View File

@@ -0,0 +1,399 @@
"use strict";
// const { basename, extname } = require("path");
import { basename, extname } from "../../path-browserify/index.js";
//
// Mime type <-> File extension mappings
//
class Format {
constructor() {
let isWeb = (() => typeof global == "undefined")(),
png = "image/png",
jpg = "image/jpeg",
jpeg = "image/jpeg",
webp = "image/webp",
pdf = "application/pdf",
svg = "image/svg+xml";
Object.assign(this, {
toMime: this.toMime.bind(this),
fromMime: this.fromMime.bind(this),
expected: isWeb
? `"png", "jpg", or "webp"`
: `"png", "jpg", "pdf", or "svg"`,
formats: isWeb ? { png, jpg, jpeg, webp } : { png, jpg, jpeg, pdf, svg },
mimes: isWeb
? { [png]: "png", [jpg]: "jpg", [webp]: "webp" }
: { [png]: "png", [jpg]: "jpg", [pdf]: "pdf", [svg]: "svg" }
});
}
toMime(ext) {
return this.formats[(ext || "").replace(/^\./, "").toLowerCase()];
}
fromMime(mime) {
return this.mimes[mime];
}
}
//
// Validation of the options dict shared by the Canvas saveAs, toBuffer, and toDataURL methods
//
function options(
pages,
{
filename = "",
extension = "",
format,
page,
quality,
matte,
density,
outline,
archive
} = {}
) {
var { fromMime, toMime, expected } = new Format(),
archive = archive || "canvas",
ext = format || extension.replace(/@\d+x$/i, "") || extname(filename),
format = fromMime(toMime(ext) || ext),
mime = toMime(format),
pp = pages.length;
if (!ext)
throw new Error(
`Cannot determine image format (use a filename extension or 'format' argument)`
);
if (!format)
throw new Error(`Unsupported file format "${ext}" (expected ${expected})`);
if (!pp)
throw new RangeError(
`Canvas has no associated contexts (try calling getContext or newPage first)`
);
let padding,
isSequence,
pattern = filename.replace(/{(\d*)}/g, (_, width) => {
isSequence = true;
width = parseInt(width, 10);
padding = isFinite(width) ? width : isFinite(padding) ? padding : -1;
return "{}";
});
// allow negative indexing if a specific page is specified
let idx = page > 0 ? page - 1 : page < 0 ? pp + page : undefined;
if ((isFinite(idx) && idx < 0) || idx >= pp)
throw new RangeError(
pp == 1
? `Canvas only has a page 1 (${idx} is out of bounds)`
: `Canvas has pages 1${pp} (${idx} is out of bounds)`
);
pages = isFinite(idx)
? [pages[idx]]
: isSequence || format == "pdf"
? pages
: pages.slice(-1); // default to the 'current' context
if (quality === undefined) {
quality = 0.92;
} else {
if (
typeof quality != "number" ||
!isFinite(quality) ||
quality < 0 ||
quality > 1
) {
throw new TypeError(
"The quality option must be an number in the 0.01.0 range"
);
}
}
if (density === undefined) {
let m = (extension || basename(filename, ext)).match(/@(\d+)x$/i);
density = m ? parseInt(m[1], 10) : 1;
} else if (
typeof density != "number" ||
!Number.isInteger(density) ||
density < 1
) {
throw new TypeError("The density option must be a non-negative integer");
}
if (outline === undefined) {
outline = true;
} else if (format == "svg") {
outline = !!outline;
}
return {
filename,
pattern,
format,
mime,
pages,
padding,
quality,
matte,
density,
outline,
archive
};
}
//
// Zip (pace Phil Katz & q.v. https://github.com/jimmywarting/StreamSaver.js)
//
class Crc32 {
static for(data) {
return new Crc32().append(data).get();
}
constructor() {
this.crc = -1;
}
get() {
return ~this.crc;
}
append(data) {
var crc = this.crc | 0,
table = this.table;
for (var offset = 0, len = data.length | 0; offset < len; offset++) {
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xff];
}
this.crc = crc;
return this;
}
}
Crc32.prototype.table = (() => {
var i,
j,
t,
table = [];
for (i = 0; i < 256; i++) {
t = i;
for (j = 0; j < 8; j++) {
t = t & 1 ? (t >>> 1) ^ 0xedb88320 : t >>> 1;
}
table[i] = t;
}
return table;
})();
function calloc(size) {
let array = new Uint8Array(size),
view = new DataView(array.buffer),
buf = {
array,
view,
size,
set8(at, to) {
view.setUint8(at, to);
return buf;
},
set16(at, to) {
view.setUint16(at, to, true);
return buf;
},
set32(at, to) {
view.setUint32(at, to, true);
return buf;
},
bytes(at, to) {
array.set(to, at);
return buf;
}
};
return buf;
}
// const TextEncoder=require('util').TextEncoder
class Zip {
constructor(directory) {
let now = new Date();
Object.assign(this, {
directory,
offset: 0,
files: [],
time:
(((now.getHours() << 6) | now.getMinutes()) << 5) |
(now.getSeconds() / 2),
date:
((((now.getFullYear() - 1980) << 4) | (now.getMonth() + 1)) << 5) |
now.getDate()
});
this.add(directory);
}
async add(filename, blob) {
let folder = !blob,
name = Zip.encoder.encode(`${this.directory}/${folder ? "" : filename}`),
data = new Uint8Array(folder ? 0 : await blob.arrayBuffer()),
preamble = 30 + name.length,
descriptor = preamble + data.length,
postamble = 16,
{ offset } = this;
let header = calloc(26)
.set32(0, 0x08080014) // zip version
.set16(6, this.time) // time
.set16(8, this.date) // date
.set32(10, Crc32.for(data)) // checksum
.set32(14, data.length) // compressed size (w/ zero compression)
.set32(18, data.length) // un-compressed size
.set16(22, name.length); // filename length (utf8 bytes)
offset += preamble;
let payload = calloc(preamble + data.length + postamble)
.set32(0, 0x04034b50) // local header signature
.bytes(4, header.array) // ...header fields...
.bytes(30, name) // filename
.bytes(preamble, data); // blob bytes
offset += data.length;
payload
.set32(descriptor, 0x08074b50) // signature
.bytes(descriptor + 4, header.array.slice(10, 22)); // length & filemame
offset += postamble;
this.files.push({ offset, folder, name, header, payload });
this.offset = offset;
}
toBuffer() {
// central directory record
let length = this.files.reduce(
(len, { name }) => 46 + name.length + len,
0
),
cdr = calloc(length + 22),
index = 0;
for (var { offset, name, header, folder } of this.files) {
cdr
.set32(index, 0x02014b50) // archive file signature
.set16(index + 4, 0x0014) // version
.bytes(index + 6, header.array) // ...header fields...
.set8(index + 38, folder ? 0x10 : 0) // is_dir flag
.set32(index + 42, offset) // file offset
.bytes(index + 46, name); // filename
index += 46 + name.length;
}
cdr
.set32(index, 0x06054b50) // signature
.set16(index + 8, this.files.length) // № files per-segment
.set16(index + 10, this.files.length) // № files this segment
.set32(index + 12, length) // central directory length
.set32(index + 16, this.offset); // file-offset of directory
// concatenated zipfile data
let output = new Uint8Array(this.offset + cdr.size),
cursor = 0;
for (var { payload } of this.files) {
output.set(payload.array, cursor);
cursor += payload.size;
}
output.set(cdr.array, cursor);
return output;
}
get blob() {
return new Blob([this.toBuffer()], { type: "application/zip" });
}
}
Zip.encoder = new TextEncoder();
//
// Browser helpers for converting canvas elements to blobs/buffers/files/zips
//
const asBlob = (canvas, mime, quality, matte) => {
if (matte) {
let { width, height } = canvas,
comp = Object.assign(document.createElement("canvas"), { width, height }),
ctx = comp.getContext("2d");
ctx.fillStyle = matte;
ctx.fillRect(0, 0, width, height);
ctx.drawImage(canvas, 0, 0);
canvas = comp;
}
return new Promise((res, rej) => canvas.toBlob(res, mime, quality));
};
const asBuffer = (...args) => asBlob(...args).then(b => b.arrayBuffer());
const asDownload = async (canvas, mime, quality, matte, filename) => {
_download(filename, await asBlob(canvas, mime, quality, matte));
};
const asZipDownload = async (
pages,
mime,
quality,
matte,
archive,
pattern,
padding
) => {
let filenames = i =>
pattern.replace("{}", String(i + 1).padStart(padding, "0")),
folder = basename(archive, ".zip") || "archive",
zip = new Zip(folder);
await Promise.all(
pages.map(async (page, i) => {
let filename = filenames(i); // serialize filename(s) before awaiting
await zip.add(filename, await asBlob(page, mime, quality, matte));
})
);
_download(`${folder}.zip`, zip.blob);
};
const _download = (filename, blob) => {
const href = window.URL.createObjectURL(blob),
link = document.createElement("a");
link.style.display = "none";
link.href = href;
link.setAttribute("download", filename);
if (typeof link.download === "undefined") {
link.setAttribute("target", "_blank");
}
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => window.URL.revokeObjectURL(href), 100);
};
const atScale = (pages, density, matte) =>
pages.map(page => {
if (density == 1 && !matte) return page.canvas;
let scaled = document.createElement("canvas"),
ctx = scaled.getContext("2d"),
src = page.canvas ? page.canvas : page;
scaled.width = src.width * density;
scaled.height = src.height * density;
if (matte) {
ctx.fillStyle = matte;
ctx.fillRect(0, 0, scaled.width, scaled.height);
}
ctx.scale(density, density);
ctx.drawImage(src, 0, 0);
return scaled;
});
const obj = { asBuffer, asDownload, asZipDownload, atScale, options };
export default obj;
// module.exports = { asBuffer, asDownload, asZipDownload, atScale, options };

Binary file not shown.

View File

@@ -0,0 +1,73 @@
{
"name": "skia-canvas",
"version": "0.9.29",
"description": "A canvas environment for Node",
"author": "Christian Swinehart <drafting@samizdat.co>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/samizdatco/skia-canvas.git"
},
"bugs": {
"url": "https://github.com/samizdatco/skia-canvas/issues"
},
"homepage": "https://github.com/samizdatco/skia-canvas#readme",
"main": "lib",
"browser": {
"lib": "./lib/browser.js",
"path": "path-browserify"
},
"scripts": {
"build": "cargo-cp-artifact -nc lib/v6/index.node -- cargo build --message-format=json-render-diagnostics",
"install": "node-pre-gyp install || npm run build -- --release",
"package": "node-pre-gyp package",
"upload": "node-pre-gyp publish",
"test": "jest"
},
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.8",
"cargo-cp-artifact": "^0.1",
"glob": "^7.2.0",
"path-browserify": "^1.0.1",
"simple-get": "^4.0.1",
"string-split-by": "^1.0.0"
},
"devDependencies": {
"@types/jest": "^27.4.0",
"@types/node": "^17.0.15",
"aws-sdk": "^2.1069.0",
"express": "^4.17.2",
"jest": "^27.5.0",
"lodash": "^4.17.21",
"nodemon": "^2.0.15",
"tmp": "^0.2.1"
},
"files": [
"lib"
],
"binary": {
"module_name": "index",
"module_path": "./lib/v{napi_build_version}",
"remote_path": "./v{version}",
"package_name": "{platform}-{arch}-{node_napi_label}-{libc}.tar.gz",
"host": "https://skia-canvas.s3.us-east-1.amazonaws.com",
"napi_versions": [
6
]
},
"keywords": [
"skia",
"canvas",
"offscreen",
"headless",
"graphic",
"graphics",
"image",
"images",
"compositing",
"render",
"pdf",
"svg",
"rust"
]
}