-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathimages.ts
130 lines (116 loc) · 4.2 KB
/
images.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import {AlignmentNumber, Fitting, PartialOriginAlignment, RequiredOriginAlignment, alignmentToNumber, requiredOriginAlignmentFromPartial} from './alignment.ts';
import * as assets from './assets.ts';
import * as dataURIConv from './data_uri_conv.ts';
import {cloneElement, createElement, getElementsBoundingBox, setAttributes} from './elements.ts';
import {getGlobalOptions} from './global_options.ts';
import {loadEvent} from './internal_util.ts';
import {DefaultPiece} from './pieces.ts';
export type ImageType = "png" | "jpeg" | "gif";
export const DEFAULT_IMAGE_TYPE: ImageType = "png";
interface PartialImageScalingInterface {
width?: number;
height?: number;
align?: PartialOriginAlignment;
fitting?: Fitting;
}
export type PartialImageScaling = PartialImageScalingInterface | "auto";
interface ImageScalingInterface {
readonly width: number;
readonly height: number;
readonly align: RequiredOriginAlignment;
readonly fitting: Fitting;
}
type ImageScaling = ImageScalingInterface | "auto";
function imageScalingFromPartial(partialScaling: PartialImageScaling = "auto"): ImageScaling {
if (partialScaling === "auto")
return "auto";
const {
width = 1,
height = 1,
align,
fitting = "fit",
} = partialScaling;
return {width, height, align: requiredOriginAlignmentFromPartial(align), fitting};
}
/** A class representing an image, usually referenced by URL or data URI. */
export class Image extends DefaultPiece {
protected constructor(
private readonly image: SVGImageElement,
readonly scaling: ImageScaling,
) {
super(image);
}
static async fromBlob(blob: Blob) {
return await Image.fromURL(await dataURIConv.fromBlob(blob));
}
/**
* Loads an image from a URL, with the specified scaling.
* If it's an external URL, the image is fetched and encoded as a data URI instead.
*/
static async fromURL(url: string, {scaling}: {scaling?: PartialImageScaling} = {}) {
const image = createElement({tagName: "image"});
const loaded = loadEvent(image);
setAttributes(image, {href: await dataURIConv.urlToDataURI(url)});
await loaded;
return Image.fromImage(image, {canModifyImage: true, scaling});
}
static async fromAsset(urlAsset: assets.ModuleImport<string>): Promise<Image>;
static async fromAsset(args: {
urlAsset: assets.ModuleImport<string>,
scaling?: PartialImageScaling,
}): Promise<Image>;
static async fromAsset(arg: assets.ModuleImport<string> | {
urlAsset: assets.ModuleImport<string>,
scaling?: PartialImageScaling,
}) {
const {urlAsset, scaling = undefined} = arg instanceof Promise ? {urlAsset: arg} : arg;
return await Image.fromURL(await assets.url(urlAsset), {scaling});
}
static fromImage(image: SVGImageElement, {canModifyImage = false, scaling}: {
canModifyImage?: boolean,
scaling?: PartialImageScaling,
} = {}) {
const imageClone = canModifyImage ? image : cloneElement(image);
const fullScaling = imageScalingFromPartial(scaling);
applyImageScalingAttributes(imageClone, fullScaling);
return new Image(imageClone, fullScaling);
}
setScaling(scaling: PartialImageScaling) {
return Image.fromImage(this.image, {scaling});
}
}
function applyImageScalingAttributes(image: SVGImageElement, scaling: ImageScaling) {
if (scaling === "auto") {
const {imageAutoSizeLogic} = getGlobalOptions();
if (imageAutoSizeLogic.widthAndHeight === "auto")
setAttributes(image, {width: "auto", height: "auto"});
if (imageAutoSizeLogic.measure) {
const box = getElementsBoundingBox([image]);
setAttributes(image, {width: box.width, height: box.height});
}
} else {
const {
width,
height,
align: {x, y},
fitting,
} = scaling;
setAttributes(image, {
width,
height,
x: -width * (alignmentToNumber(x) + 1) / 2,
y: -height * (alignmentToNumber(y) + 1) / 2,
preserveAspectRatio: fitting === "stretch" ? "none" : [
"x", MIN_MID_MAX.get(alignmentToNumber(x)),
"Y", MIN_MID_MAX.get(alignmentToNumber(y)),
" ",
fitting === "fit" ? "meet" : "slice",
].join(""),
});
}
}
const MIN_MID_MAX = new Map<AlignmentNumber, string>([
[-1, "Min"],
[0, "Mid"],
[1, "Max"],
]);