@pixi-essentials/svg
Version:
Native SVG Renderer on top of PixiJS
1,077 lines (1,016 loc) • 35.5 kB
TypeScript
import { Bounds } from 'pixi.js';
import { CanvasSource } from 'pixi.js';
import { Container } from 'pixi.js';
import { Graphics } from 'pixi.js';
import type { LineCap } from 'pixi.js';
import type { LineJoin } from 'pixi.js';
import type { Matrix } from 'pixi.js';
import type { PointData } from 'pixi.js';
import { Rectangle } from 'pixi.js';
import { Renderer } from 'pixi.js';
import type { RenderTexture } from 'pixi.js';
import { Sprite } from 'pixi.js';
import { TextStyle } from 'pixi.js';
import { Texture } from 'pixi.js';
import { TextureSource } from 'pixi.js';
/**
* @public
* @typeParam N - The internal property for marking rectangles.
*/
declare interface AreaAllocator<N>
{
readonly width: number;
readonly height: number;
allocate(width: number, height: number): Rectangle & N;
free(area: N): void;
}
/**
* This allocator issues texture backed by a canvas. You can draw on to that canvas to source
* each texture.
*
* @public
*/
declare class CanvasTextureAllocator extends TextureAllocator<CanvasSource>
{
/**
* Creates a texture slab backed by a canvas.
*/
protected override createSlabSource(): CanvasSource
{
return new CanvasSource({
height: this.slabHeight,
width: this.slabWidth,
});
}
}
/**
* @ignore
* @public
*/
export declare type Contour = Array<number>;
/**
* The fill rules supported by {@link Path}.
*
* @public
*/
export declare enum FILL_RULE {
NONZERO = "nonzero",
EVENODD = "evenodd"
}
/**
* Get information on the internal cache of the SVG loading mechanism.
*
* @public
* @returns A view on the cache - clear() method and a size property.
*/
export declare function getLoaderCache(): {
clear(): void;
size: number;
};
/**
* Inherited paint, used for <use /> elements. The properties used on the inherited paint do not
* override those on the parent.
*
* @public
*/
export declare class InheritedPaintProvider implements Paint {
parent: Paint;
provider: Paint;
/**
* Composes a `Paint` that will inherit properties from the `parent` if the `provider` does not
* define them.
*
* @param parent
* @param provider
*/
constructor(parent: Paint, provider: Paint);
get dirtyId(): number;
get fill(): number | string;
get opacity(): number;
get stroke(): number | string;
get strokeDashArray(): number[];
get strokeDashOffset(): number;
get strokeLineCap(): LineCap;
get strokeLineJoin(): LineJoin;
get strokeMiterLimit(): number;
get strokeWidth(): number;
}
/**
* A `MaskServer` will lazily render its content's luminance into its render-texture's alpha
* channel using the luminance-alpha filter. The `dirtyId` flag can be used to make it re-render its
* contents. It is intended to be used as a sprite-mask, where black pixels are invisible and white
* pixels are visible (i.e. black pixels are filtered to alpha = 0, while white pixels are filtered
* to alpha = 1. The rest are filtered to an alpha such that 0 < alpha < 1.). This is in compliance
* with [CSS Masking Module Level 1](https://www.w3.org/TR/css-masking-1/#MaskElement).
*
* **Note: This functionality is disabled in PixiJS 8's renderer**
*
* @public
* @ignore
*/
export declare class MaskServer extends Sprite {
/**
* Flags when re-renders are required due to content updates.
*/
dirtyId: number;
/**
* Flags when the content is re-rendered and should be equal to `this.dirtyId` when the texture
* is update-to-date.
*/
updateId: number;
/**
* @param texture - The render-texture that will cache the contents.
*/
constructor(texture: RenderTexture);
/**
* @override
*/
/**
* Create a mask that will overlay on top of the given display-object using the texture of this
* mask server.
*
* @param displayObject - The mask target.
*/
createMask(_: Container): MaskSprite;
}
/**
* A sprite that does not render anything. It can be used as a mask whose bounds can be updated by adding it
* as a child of the mask-target.
*
* @public
* @see MaskServer.createMask
* @ignore
*/
export declare class MaskSprite extends Sprite {
render(_: Renderer): void;
}
/**
* Internal, parsed form of painting attributes. If a paint attribute was not defined, it **must** be
* `null` (not `undefined`).
*
* @public
* @see https://www.w3.org/TR/SVG2/painting.html#Introduction
*/
export declare interface Paint {
/**
* The interior paint for the shape.
*/
readonly fill: number | string;
/**
* The opacity of the fill.
*/
readonly opacity: number;
/**
* The color of the stroke outline applied on the shape.
*/
readonly stroke: number | string;
/**
* The dash pattern for stroking the shape.
*/
readonly strokeDashArray: number[];
/**
* The distance into the dash pattern at which the stroking is started.
*/
readonly strokeDashOffset: number;
/**
* The line caps applied at the end of the stroke. This is not applied for closed shapes.
*/
readonly strokeLineCap: LineCap;
/**
* The line join applied at the joint to line segments.
*/
readonly strokeLineJoin: LineJoin;
/**
* The maximum miter distance.
*/
readonly strokeMiterLimit: number;
/**
* The width of the stroke outline applied on the shape.
*/
readonly strokeWidth: number;
/**
* Flags when the paint is updated.
*/
readonly dirtyId: number;
}
/**
* Provides the `Paint` for an `SVGElement`. It will also respond to changes in the attributes of the element
* (not implemented).
*
* @public
*/
export declare class PaintProvider implements Paint {
element: SVGElement;
fill: number | string;
opacity: number;
stroke: number | string;
strokeDashArray: number[];
strokeDashOffset: number;
strokeLineCap: LineCap;
strokeLineJoin: LineJoin;
strokeMiterLimit: number;
strokeWidth: number;
dirtyId: number;
/**
* @param element - The element whose paint is to be provided.
*/
constructor(element: SVGElement);
/**
* Parses the color attribute into an RGBA hexadecimal equivalent, if encoded. If the `colorString` is `none` or
* is a `url(#id)` reference, it is returned as is.
*
* @param colorString
* @see https://github.com/bigtimebuddy/pixi-svg/blob/89e4ab834fa4ef05b64741596516c732eae34daa/src/SVG.js#L106
*/
static parseColor(colorString: string): number | string;
}
/**
* [Paint Servers]{@link https://svgwg.org/svg-next/pservers.html} are implemented as textures. This class is a lazy
* wrapper around paint textures, which can only be generated using the `renderer` drawing to the screen.
*
* @public
*/
export declare class PaintServer {
paintServer: SVGGradientElement | SVGPatternElement;
paintTexture: RenderTexture;
paintContexts: Map<Renderer, number>;
dirtyId: number;
/**
* Creates a `PaintServer` wrapper.
*
* @param paintServer
* @param paintTexture
*/
constructor(paintServer: SVGGradientElement | SVGPatternElement, paintTexture: RenderTexture);
/**
* Ensures the paint texture is updated for the renderer's WebGL context. This should be called before using the
* paint texture to render anything.
*
* @param renderer - The renderer that will use the paint texture.
*/
resolvePaint(renderer: Renderer): void;
/**
* Calculates the optimal texture dimensions for the paint texture, given the bounding box of the
* object applying it. The paint texture is resized accordingly.
*
* If the paint texture is sized smaller than the bounding box, then it is expected that it will
* be scaled up to fit it.
*
* @param bbox - The bounding box of the object applying the paint texture.
*/
resolvePaintDimensions(bbox: Rectangle): void;
/**
* Renders the paint texture using the renderer immediately.
*
* @param renderer - The renderer to use for rendering to the paint texture.
*/
updatePaint(renderer: Renderer): void;
/**
* Renders `this.paintServer` as a `SVGLinearGradientElement`.
*
* @param renderer - The renderer being used to render the paint texture.
*/
private linearGradient;
/**
* Renders `this.paintServer` as a `SVGRadialGradientElement`.
*
* @param renderer - The renderer being used to render the paint texture.
*/
private radialGradient;
/**
* Extracts the color-stops from the children of a `SVGGradientElement`.
*
* @param stopElements - The children of a `SVGGradientElement`. You can get it via `element.children`.
* @return The color stops that can be fed into {@link GradientFactory}.
*/
private createColorStops;
}
/** @internal */
export declare const PATH = 100;
/**
* Shape extension for Graphics
*
* @public
*/
export declare class Path {
/**
* The list of contours of this path, where a contour is a list of points.
*
* @member {Array.Array.<number>>}
*/
contours: Contour[];
/** The fill rule of this path. */
fillRule: FILL_RULE;
/** The type of shape. This is always equal to 100 for now. */
type: number;
/** Whether the calculated bounds are dirty. */
protected dirty: boolean;
/** The calculated bounds of this path. */
protected bounds: Bounds;
/**
* Initializes the path with zero contours and a non-zero fill rule.
*/
constructor();
/**
* Gets the points of the last contour in this path. If there are no contours, one is created.
*/
get points(): number[];
/**
* Calculates whether the point (x, y) is inside this path or not.
*
* @param x - The x-coordinate of the point.
* @param y - The y-coordinate of the point.
* @return Whether (x, y) is inside this path.
*/
contains(x: number, y: number): boolean;
/**
* Clone this path.
*/
clone(): Path;
/**
* Closes the last contour of this path and pushes a new one.
*/
closeContour(): void;
/**
* This should be called when the path is updated so that the hit-testing bounds are recalculated.
*/
invalidate(): void;
toString(): string;
/**
* Recalculates the bounds of this path and sets {@link Path.bounds this.bounds}.
*/
private calculateBounds;
/**
* Hit-tests the point (x, y) based on the even-odd fill rule.
*
* @see http://geomalgorithms.com/a03-_inclusion.html
*/
private hitEvenOdd;
/**
* Hit-tests the point (x, y) based on non-zero fill rule.
*
* @see http://geomalgorithms.com/a03-_inclusion.html
*/
private hitNonZero;
}
/**
* This node can be used to directly embed the following elements:
*
* | Interface | Element |
* | ------------------- | ------------------ |
* | SVGGElement | <g /> |
* | SVGCircleElement | <circle /> |
* | SVGLineElement | <line /> |
* | SVGPolylineElement | <polyline /> |
* | SVGPolygonElement | <polygon /> |
* | SVGRectElement | <rect /> |
*
* It also provides an implementation for dashed stroking, by adding the `dashArray` and `dashOffset` properties
* to `LineStyle`.
*
* @public
*/
export declare class SVGGraphicsNode extends Graphics {
paintServers: PaintServer[];
protected _sceneContext: SVGSceneContext;
constructor(context: SVGSceneContext);
/**
* Draws an elliptical arc.
*
* @param cx - The x-coordinate of the center of the ellipse.
* @param cy - The y-coordinate of the center of the ellipse.
* @param rx - The radius along the x-axis.
* @param ry - The radius along the y-axis.
* @param startAngle - The starting eccentric angle, in radians (0 is at the 3 o'clock position of the arc's circle).
* @param endAngle - The ending eccentric angle, in radians.
* @param xAxisRotation - The angle of the whole ellipse w.r.t. x-axis.
* @param anticlockwise - Specifies whether the drawing should be counterclockwise or clockwise.
* @return This Graphics object. Good for chaining method calls.
*/
ellipticArc(cx: number, cy: number, rx: number, ry: number, startAngle: number, endAngle: number, xAxisRotation?: number, anticlockwise?: boolean): this;
/**
* Draws an elliptical arc to the specified point.
*
* If rx = 0 or ry = 0, then a line is drawn. If the radii provided are too small to draw the arc, then
* they are scaled up appropriately.
*
* @param endX - the x-coordinate of the ending point.
* @param endY - the y-coordinate of the ending point.
* @param rx - The radius along the x-axis.
* @param ry - The radius along the y-axis.
* @param xAxisRotation - The angle of the ellipse as a whole w.r.t/ x-axis.
* @param anticlockwise - Specifies whether the arc should be drawn counterclockwise or clockwise.
* @param largeArc - Specifies whether the larger arc of two possible should be choosen.
* @return This Graphics object. Good for chaining method calls.
* @see https://svgwg.org/svg2-draft/paths.html#PathDataEllipticalArcCommands
* @see https://www.w3.org/TR/SVG2/implnote.html#ArcImplementationNotes
*/
ellipticArcTo(endX: number, endY: number, rx: number, ry: number, xAxisRotation?: number, anticlockwise?: boolean, largeArc?: boolean): this;
/**
* Embeds the `SVGCircleElement` into this node.
*
* @param element - The circle element to draw.
*/
embedCircle(element: SVGCircleElement): void;
/**
* Embeds the `SVGEllipseElement` into this node.
*
* @param element - The ellipse element to draw.
*/
embedEllipse(element: SVGEllipseElement): void;
/**
* Embeds the `SVGLineElement` into this node.
*
* @param element - The line element to draw.
*/
embedLine(element: SVGLineElement): void;
/**
* Embeds the `SVGRectElement` into this node.
*
* @param element - The rectangle element to draw.
*/
embedRect(element: SVGRectElement): void;
/**
* Embeds the `SVGPolygonElement` element into this node.
*
* @param element - The polygon element to draw.
*/
embedPolygon(element: SVGPolygonElement): void;
/**
* Embeds the `SVGPolylineElement` element into this node.
*
* @param element - The polyline element to draw.
*/
embedPolyline(element: SVGPolylineElement): void;
/**
* @override
*/
render(renderer: Renderer): void;
}
/**
* Draws SVG <image /> elements.
*
* @public
*/
export declare class SVGImageNode extends SVGGraphicsNode {
/**
* The canvas used into which the `SVGImageElement` is drawn. This is because WebGL does not support
* using `SVGImageElement` as an `ImageSource` for textures.
*/
protected _canvas: HTMLCanvasElement;
/**
* The Canvas 2D context for `this._canvas`.
*/
protected _canvasContext: CanvasRenderingContext2D;
/**
* A texture backed by `this._canvas`.
*/
protected _texture: Texture;
/**
* Embeds the given SVG image element into this node.
*
* @param element - The SVG image element to embed.
*/
embedImage(element: SVGImageElement): void;
/**
* Initializes {@code this._texture} by allocating it from the atlas. It is expected the texture size requested
* is less than the atlas's slab dimensions.
*
* @param width
* @param height
*/
private initTexture;
/**
* Draws the image into this node's texture.
*
* @param image - The image element holding the image.
*/
private drawTexture;
}
/**
* Draws SVG <path /> elements.
*
* @public
*/
export declare class SVGPathNode extends SVGGraphicsNode {
/**
* Embeds the `SVGPathElement` into this node.
*
* @param element - the path to draw
*/
embedPath(element: SVGPathElement): this;
}
/**
* {@link SVGScene} can be used to build an interactive viewer for scalable vector graphics images. You must specify the size
* of the svg viewer.
*
* ## SVG Scene Graph
*
* SVGScene has an internal, disconnected scene graph that is optimized for lazy updates. It will listen to the following
* events fired by a node:
*
* * `nodetransformdirty`: This will invalidate the transform calculations.
*
* @public
*/
export declare class SVGScene extends Container {
/**
* The SVG image content being rendered by the scene.
*/
content: SVGSVGElement;
/**
* Display objects that don't render to the screen, but are required to update before the rendering
* nodes, e.g. mask sprites.
*/
renderServers: Container;
/**
* The scene context
*/
protected _context: SVGSceneContext;
/**
* The width of the rendered scene in local space.
*/
protected _width: number;
/**
* The height of the rendered scene in local space.
*/
protected _height: number;
/**
* Maps content elements to their paint. These paints do not inherit from their parent element. You must
* compose an {@link InheritedPaintProvider} manually.
*/
private _elementToPaint;
/**
* Maps `SVGMaskElement` elements to their nodes. These are not added to the scene graph directly and are
* only referenced as a `mask`.
*/
private _elementToMask;
private _elementToRenderNode;
/**
* Flags whether any transform is dirty in the SVG scene graph.
*/
protected _transformDirty: boolean;
sortDirty: boolean;
/**
* @param content - The SVG node to render
* @param context - This can be used to configure the scene
*/
constructor(content: SVGSVGElement, context?: Partial<SVGSceneContext>);
initContext(context?: Partial<SVGSceneContext>): void;
/**
* Draw the paints required to render the elements in this SVG scene. If not called, gradients
* and special effects may not render.
*
* @param renderer
*/
drawPaints(renderer: Renderer): void;
/**
* Creates a display object that implements the corresponding `embed*` method for the given node.
*
* @param element - The element to be embedded in a display object.
*/
protected createNode(element: SVGElement): Container;
/**
* Creates a `Paint` object for the given element. This should only be used when sharing the `Paint`
* is not desired; otherwise, use {@link SVGScene.queryPaint}.
*
* This will return `null` if the passed element is not an instance of `SVGElement`.
*
* @alpha
* @param element
*/
protected createPaint(element: SVGElement): Paint;
/**
* Creates a lazy paint texture for the paint server.
*
* @alpha
* @param paintServer - The paint server to be rendered.
*/
protected createPaintServer(paintServer: SVGGradientElement): PaintServer;
/**
* Creates a lazy luminance mask for the `SVGMaskElement` or its rendering node.
*
* @param ref - The `SVGMaskElement` or its rendering node, if already generated.
*/
protected createMask(ref: SVGMaskElement | Container): MaskServer;
/**
* Returns the rendering node for a mask.
*
* @alpha
* @param ref - The mask element whose rendering node is needed.
*/
protected queryMask(ref: SVGMaskElement): MaskServer;
/**
* Returns the cached paint of a content element. The returned paint will not account for any paint
* attributes inherited from ancestor elements.
*
* @alpha
* @param ref - A reference to the content element.
*/
protected queryPaint(ref: SVGElement): Paint;
/**
* Returns an (uncached) inherited paint of a content element.
*
* @alpha
* @param ref
*/
protected queryInheritedPaint(ref: SVGElement): Paint;
/**
* Parses the internal URL reference into a selector (that can be used to find the
* referenced element using `this.content.querySelector`).
*
* @param url - The reference string, e.g. "url(#id)", "url('#id')", "#id"
*/
protected parseReference(url: string): string;
/**
* Embeds a content `element` into the rendering `node`.
*
* This is **not** a stable API.
*
* @alpha
* @param node - The node in this scene that will render the `element`.
* @param element - The content `element` to be rendered. This must be an element of the SVG document
* fragment under `this.content`.
* @param options - Additional options
* @param {Paint} [options.basePaint] - The base paint that the element's paint should inherit from
* @return The base attributes of the element, like paint.
*/
protected embedIntoNode(node: Container, element: SVGGraphicsElement | SVGMaskElement, options?: {
basePaint?: Paint;
}): {
paint: Paint;
};
/**
* Recursively populates a subscene graph that embeds {@code element}. The root of the subscene is returned.
*
* @param element - The SVGElement to be embedded.
* @param options - Inherited attributes from the element's parent, if any.
* @return The display object that embeds the element for rendering.
*/
protected populateSceneRecursive(element: SVGElement, options?: {
basePaint?: Paint;
}): Container;
/**
* Populates the entire SVG scene. This should only be called once after the {@link SVGScene.content} has been set.
*/
protected populateScene(): void;
/**
* Handles `nodetransformdirty` events fired by nodes. It will set {@link SVGScene._transformDirty} to true.
*
* This will also emit `transformdirty`.
*/
private onNodeTransformDirty;
/**
* The width at which the SVG scene is being rendered. By default, this is the viewbox width specified by
* the root element.
*/
get width(): number;
set width(value: number);
/**
* The height at which the SVG scene is being rendered. By default, this is the viewbox height specified by
* the root element.
*/
get height(): number;
set height(value: number);
/**
* Load the SVG document and create a {@link SVGScene} asynchronously.
*
* A cache is used for loaded SVG documents.
*
* @param url
* @param context
* @returns
*/
static from(url: string, context?: SVGSceneContext): Promise<SVGScene>;
}
/**
* Options to manage the SVG scene
*
* @public
*/
export declare interface SVGSceneContext {
/** The texture allocator for loading images. */
atlas: CanvasTextureAllocator;
/** Disable loading SVGs referenced from "href", "xlink:href" attributes of <use /> elements. */
disableHrefSVGLoading: boolean;
/** @ignore */
disableRootPopulation: boolean;
}
/**
* The `SVGTextEngine` interface is used to layout text content authored in SVG files. The @pixi-essentials/svg
* package provides {@link SVGTextEngineImpl} as a default implementation for users.
*
* Text engines are allowed to have async behaviour so that fonts can be loaded before text metrics are measured.
*
* It is expected an implementation inherits from {@link PIXI.DisplayObject}.
*
* @public
* @see SVGTextEngineImpl
*/
export declare interface SVGTextEngine {
/**
* Clears the text content laid out already. This should reset the state of the engine to before any calls
* to {@link SVGTextEngine.put} were made.
*/
clear(): Promise<void>;
/**
* Puts the text {@code content} into the local space of the engine at {@code position}. {@code matrix} can
* be used to transform the glyphs, although it is as optional feature for implementations.
*
* @param id - A locally unique ID that can be used to modify the added block of text later.
* @param position - The position of the text in the engine's local space.
* @param content - The text to add.
* @param style - The text styling applied.
* @param matrix
*/
put(id: any, position: PointData, content: string, style: Partial<TextStyle>, matrix?: Matrix): Promise<PointData>;
}
/**
* `SVGTextEngineImpl` is the default implementation for {@link SVGTextEngine}. It is inspired by {@link PIXI.Text} that
* is provided by @pixi/text. It uses a <canvas /> to draw and cache the text. This may cause blurring issues when
* the SVG is viewed at highly zoomed-in scales because it is rasterized.
*
* @public
*/
export declare class SVGTextEngineImpl extends Sprite implements SVGTextEngine {
protected canvas: HTMLCanvasElement;
protected context: CanvasRenderingContext2D;
protected contentList: Map<any, {
position: PointData;
content: string;
style: Partial<TextStyle>;
matrix?: Matrix;
}>;
protected dirtyId: number;
protected updateId: number;
constructor();
clear(): Promise<void>;
put(id: any, position: PointData, content: string, style: Partial<TextStyle>, matrix?: Matrix): Promise<PointData>;
updateText(): void;
onRender: () => void;
}
/**
* Draws SVG <text /> elements.
*
* @public
*/
export declare class SVGTextNode extends Container {
/**
* The SVG text rendering engine to be used by default in `SVGTextNode`. This API is not stable and
* can change anytime.
*
* @alpha
*/
static defaultEngine: {
new (): SVGTextEngine & Container;
};
/**
* An instance of a SVG text engine used to layout and render text.
*/
protected engine: SVGTextEngine & Container;
/**
* The current text position, where the next glyph will be placed.
*/
protected currentTextPosition: PointData;
constructor();
/**
* Embeds a `SVGTextElement` in this node.
*
* @param {SVGTextElement} element - The `SVGTextElement` to embed.
*/
embedText(element: SVGTextElement | SVGTSpanElement, style?: Partial<TextStyle>): Promise<void>;
}
/**
* Container for rendering SVG <use /> elements.
*
* @public
*/
export declare class SVGUseNode extends Container {
isRefExternal: boolean;
private _ref;
/**
* Embeds the `SVGUseElement` into this node.
*
* @param element - The <use /> element to draw.
*/
embedUse(element: SVGUseElement): void;
/**
* The node that renders the element referenced by a <element /> element.
*/
get ref(): SVGGraphicsNode;
set ref(value: SVGGraphicsNode);
}
/**
* The texture allocator dynamically manages space on base-texture slabs. It can be used to generate
* atlases on demand, which improve batching efficiency.
*
* @public
*/
declare abstract class TextureAllocator<S extends TextureSource, T extends Texture = Texture>
{
/**
* The width of texture slabs.
*/
public readonly slabWidth: number;
/**
* The height of texture slabs.
*/
public readonly slabHeight: number;
/**
* The list of base-textures that are used to allocate texture space.
*/
protected textureSlabs: TextureSlab<S>[];
/**
* @param slabWidth - The width of base-texture slabs. This should be at most 2048.
* @param slabHeight - The height of base-texture slabs. This should be at most 2048.
*/
constructor(slabWidth = 2048, slabHeight = 2048)
{
this.slabWidth = slabWidth;
this.slabHeight = slabHeight;
this.textureSlabs = [];
}
get maxWidth(): number
{
return this.slabWidth - (2 * this.calculatePadding(this.slabWidth, this.slabHeight));
}
get maxHeight(): number
{
return this.slabHeight - (2 * this.calculatePadding(this.slabWidth, this.slabHeight));
}
/**
* Allocates a texture from this allocator.
*
* If its existing slab pool has enough space, the texture is issued from one. Otherwise,
* a new slab is created and the texture is issued from it. However, if the requested
* dimensions are larger than slabs themselves, then `null` is always returned.
*
* To upload a texture source, you will have to create an atlas-managing {@link TextureSource}
* yourself on the base-texture. The {@link AtlasAllocator} does this for you, while the
* {@link CanvasTextureAllocator} can be used to draw on a canvas-based atlas.
*
* @param width - The width of the requested texture.
* @param height - The height of the requested texture.
* @param padding - The padding requested around the texture, to prevent bleeding.
* @return The allocated texture, if successful; otherwise, `null`.
*/
allocate(width: number, height: number, padding = this.calculatePadding(width, height)): T
{
// Cannot allocate a texture larger than a texture-slab.
if (padded(width, padding) > this.slabWidth
|| padded(height, padding) > this.slabHeight)
{
return null;
}
const slabs = this.textureSlabs;
// Loop through the slabs and find one with enough space, if any.
for (let i = 0, j = slabs.length; i < j; i++)
{
const slab = slabs[i];
const texture = this.issueTexture(slab, width, height, padding);
if (texture)
{
return texture;
}
}
// Issue a new slab.
const slab = this.createSlab();
// Append this slab to the head of the list.
this.textureSlabs.unshift(slab);
// Issue the texture from this blank slab.
return this.issueTexture(slab, width, height, padding);
}
/**
* Frees the texture and reclaims its space. It is assumed you will not use it again, and have
* destroyed any resource uploading its data.
*
* @param texture
* @throws When the texture was not located in this allocator.
*/
free(texture: T): void
{
const slab = this.textureSlabs.find((sl) => sl.slab === texture.source);
if (!slab)
{
throw new Error('The texture cannot be freed because '
+ 'its base-texture is not pooled by this allocator. '
+ 'This is either a bug in TextureAllocator or you tried to free a '
+ 'texture that was never allocated by one.');
}
const textureEntry = slab.managedTextures.find((entry) => entry.texture === texture);
if (!textureEntry)
{
throw new Error('The texture cannot be freed because it was not found '
+ 'in the managed list of issued textures on its slab. This may be because you '
+ 'duplicated this texture or a bug in TextureAllocator');
}
slab.managedArea.free(textureEntry.area);
slab.managedTextures.splice(slab.managedTextures.indexOf(textureEntry), 1);
}
protected calculatePadding(width: number, height: number): number
{
const dimen = Math.max(width, height);
if (dimen < 64)
{
return 2;
}
else if (dimen < 128)
{
return 4;
}
else if (dimen < 1024)
{
return 8;
}
return 16;
}
/**
* Creates a texture slab. Uses {@link this.createSlabSource} to initialize the texture data.
*/
protected createSlab(): TextureSlab<S>
{
return {
managedArea: new GuilloteneAllocator(this.slabWidth, this.slabHeight),
managedTextures: [],
slab: this.createSlabSource(),
};
}
/**
* Creates a new texture source to initialize a texture slab.
*/
protected abstract createSlabSource(): S;
/**
* Creates a texture on the given base-texture at {@code frame}.
*
* @param source - The atlas source that will hold the texture's space.
* @param frame - The frame in which the texture will be stored.
*/
protected createTexture(source: S, frame: Rectangle): T
{
// Override this method to return correct texture type T.
return new Texture({ source, frame }) as unknown as T;
}
/**
* Issues a texture from the given texture slab, if possible.
*
* @param slab - The texture slab to allocate frame.
* @param width - The width of the requested texture.
* @param height - The height of the requested texture.
* @param padding - Padding required around the texture.
* @return The issued texture, if successful; otherwise, `null`.
*/
protected issueTexture(slab: TextureSlab<S>, width: number, height: number, padding = 0): T
{
const area = slab.managedArea.allocate(width + (2 * padding), height + (2 * padding));
if (!area)
{
return null;
}
tempRect.copyFrom(area);
tempRect.pad(-padding);
const issuedTexture = this.createTexture(slab.slab, tempRect.clone());
slab.managedTextures.push({
area,
texture: issuedTexture,
});
return issuedTexture;
}
}
/**
* An entry of an issued texture from a {@link TextureSlab}.
*
* @public
*/
declare type TextureEntry =
{
/**
* The area returned by the area allocator, with the `__mem_area` key.
*/
area: Rectangle;
/**
* The issued texture.
*/
texture: Texture;
};
/**
* A texture slab holds a managed base-texture that is used to issue allocated texture space. The
* texture allocator maintains a pool of these texture slabs.
*
* @public
*/
declare type TextureSlab<T extends TextureSource> =
{
/**
* The area allocator that issues texture space.
*/
managedArea: AreaAllocator<any>;
/**
* The list of allocated textures and their area.
*/
managedTextures: TextureEntry[];
/**
* The base-texture that holds all the issued textures.
*/
slab: T;
};
export { }