UNPKG

@awayjs/graphics

Version:
472 lines (432 loc) 16.7 kB
import { DataBuffer } from './DataBuffer'; import { assert, ensureTypedArrayCapacity } from './utilities'; /** * Copyright 2014 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Serialization format for shape data: * (canonical, update this instead of anything else!) * * Shape data is serialized into a set of three buffers: * - `commands`: a Uint8Array for commands * - valid values: [1-11] (i.e. one of the PathCommand enum values) * - `coordinates`: an Int32Array for path coordinates* * OR uint8 thickness iff the current command is PathCommand.LineStyleSolid * - valid values: the full range of 32bit numbers, representing x,y coordinates in twips * - `styles`: a DataBuffer for style definitions * - valid values: structs for the various style definitions as described below * * (*: with one exception: to make various things faster, stroke widths are stored in the * coordinates buffer, too.) * * All entries always contain all fields, default values aren't omitted. * * the various commands write the following sets of values into the various buffers: * * moveTo: * commands: PathCommand.MoveTo * coordinates: target x coordinate, in twips * target y coordinate, in twips * styles: n/a * * lineTo: * commands: PathCommand.LineTo * coordinates: target x coordinate, in twips * target y coordinate, in twips * styles: n/a * * curveTo: * commands: PathCommand.CurveTo * coordinates: control point x coordinate, in twips * control point y coordinate, in twips * target x coordinate, in twips * target y coordinate, in twips * styles: n/a * * cubicCurveTo: * commands: PathCommand.CubicCurveTo * coordinates: control point 1 x coordinate, in twips * control point 1 y coordinate, in twips * control point 2 x coordinate, in twips * control point 2 y coordinate, in twips * target x coordinate, in twips * target y coordinate, in twips * styles: n/a * * beginFill: * commands: PathCommand.BeginSolidFill * coordinates: n/a * styles: uint32 - RGBA color * * beginGradientFill: * commands: PathCommand.BeginGradientFill * coordinates: n/a * Note: the style fields are ordered this way to optimize performance in the rendering backend * Note: the style record has a variable length depending on the number of color stops * styles: uint8 - GradientType.{LINEAR,RADIAL} * fix8 - focalPoint [-128.0xff,127.0xff] * matrix - transform (see Matrix#writeExternal for details) * uint8 - colorStops (Number of color stop records that follow) * list of uint8,uint32 pairs: * uint8 - ratio [0-0xff] * uint32 - RGBA color * uint8 - SpreadMethod.{PAD,REFLECT,REPEAT} * uint8 - InterpolationMethod.{RGB,LINEAR_RGB} * * beginBitmapFill: * commands: PathCommand.BeginBitmapFill * coordinates: n/a * styles: uint32 - Index of the bitmapData object in the Graphics object's `textures` * array * matrix - transform (see Matrix#writeExternal for details) * bool - repeat * bool - smooth * * lineStyle: * commands: PathCommand.LineStyleSolid * coordinates: uint32 - thickness (!) * style: uint32 - RGBA color * bool - pixelHinting * uint8 - LineScaleMode, [0-3] see LineScaleMode.fromNumber for meaning * uint8 - CapsStyle, [0-2] see CapsStyle.fromNumber for meaning * uint8 - JointStyle, [0-2] see JointStyle.fromNumber for meaning * uint8 - miterLimit * * lineGradientStyle: * commands: PathCommand.LineStyleGradient * coordinates: n/a * Note: the style fields are ordered this way to optimize performance in the rendering backend * Note: the style record has a variable length depending on the number of color stops * styles: uint8 - GradientType.{LINEAR,RADIAL} * int8 - focalPoint [-128,127] * matrix - transform (see Matrix#writeExternal for details) * uint8 - colorStops (Number of color stop records that follow) * list of uint8,uint32 pairs: * uint8 - ratio [0-0xff] * uint32 - RGBA color * uint8 - SpreadMethod.{PAD,REFLECT,REPEAT} * uint8 - InterpolationMethod.{RGB,LINEAR_RGB} * * lineBitmapStyle: * commands: PathCommand.LineBitmapStyle * coordinates: n/a * styles: uint32 - Index of the bitmapData object in the Graphics object's `textures` * array * matrix - transform (see Matrix#writeExternal for details) * bool - repeat * bool - smooth * * lineEnd: * Note: emitted for invalid `lineStyle` calls * commands: PathCommand.LineEnd * coordinates: n/a * styles: n/a * */ /** * Used for (de-)serializing Graphics path data in defineShape, flash.display.Graphics * and the renderer. */ export const enum PathCommand { BeginSolidFill = 1, BeginGradientFill = 2, BeginBitmapFill = 3, EndFill = 4, LineStyleSolid = 5, LineStyleGradient = 6, LineStyleBitmap = 7, LineEnd = 8, MoveTo = 9, LineTo = 10, CurveTo = 11, CubicCurveTo = 12, } export const enum GradientType { Linear = 0x10, Radial = 0x12 } export const enum GradientSpreadMethod { Pad = 0, Reflect = 1, Repeat = 2 } export const enum GradientInterpolationMethod { RGB = 0, LinearRGB = 1 } export const enum LineScaleMode { None = 0, Normal = 1, Vertical = 2, Horizontal = 3 } export interface ShapeMatrix { a: number; b: number; c: number; d: number; tx: number; ty: number; } export class PlainObjectShapeData { constructor(public commands: Uint8Array, public commandsPosition: number, public coordinates: Int32Array, public morphCoordinates: Int32Array, public coordinatesPosition: number, public styles: ArrayBuffer, public stylesLength: number, public morphStyles: ArrayBuffer, public morphStylesLength: number, public hasFills: boolean, public hasLines: boolean) {} } enum DefaultSize { Commands = 32, Coordinates = 128, Styles = 16 } export class ShapeData { commands: Uint8Array; commandsPosition: number; coordinates: Int32Array; // Note: creation and capacity-ensurance have to happen from the outside for this field. morphCoordinates: Int32Array; coordinatesPosition: number; styles: DataBuffer; morphStyles: DataBuffer; hasFills: boolean; hasLines: boolean; constructor(initialize: boolean = true) { if (initialize) { this.clear(); } } static FromPlainObject(source: PlainObjectShapeData): ShapeData { const data = new ShapeData(false); data.commands = source.commands; data.coordinates = source.coordinates; data.morphCoordinates = source.morphCoordinates; data.commandsPosition = source.commandsPosition; data.coordinatesPosition = source.coordinatesPosition; data.styles = DataBuffer.FromArrayBuffer(source.styles, source.stylesLength); data.styles.endian = 'auto'; if (source.morphStyles) { data.morphStyles = DataBuffer.FromArrayBuffer( source.morphStyles, source.morphStylesLength); data.morphStyles.endian = 'auto'; } data.hasFills = source.hasFills; data.hasLines = source.hasLines; return data; } moveTo(x: number, y: number): void { this.ensurePathCapacities(1, 2); this.commands[this.commandsPosition++] = PathCommand.MoveTo; this.coordinates[this.coordinatesPosition++] = x; this.coordinates[this.coordinatesPosition++] = y; } lineTo(x: number, y: number): void { this.ensurePathCapacities(1, 2); this.commands[this.commandsPosition++] = PathCommand.LineTo; this.coordinates[this.coordinatesPosition++] = x; this.coordinates[this.coordinatesPosition++] = y; } curveTo(controlX: number, controlY: number, anchorX: number, anchorY: number): void { this.ensurePathCapacities(1, 4); this.commands[this.commandsPosition++] = PathCommand.CurveTo; this.coordinates[this.coordinatesPosition++] = controlX; this.coordinates[this.coordinatesPosition++] = controlY; this.coordinates[this.coordinatesPosition++] = anchorX; this.coordinates[this.coordinatesPosition++] = anchorY; } cubicCurveTo(controlX1: number, controlY1: number, controlX2: number, controlY2: number, anchorX: number, anchorY: number): void { this.ensurePathCapacities(1, 6); this.commands[this.commandsPosition++] = PathCommand.CubicCurveTo; this.coordinates[this.coordinatesPosition++] = controlX1; this.coordinates[this.coordinatesPosition++] = controlY1; this.coordinates[this.coordinatesPosition++] = controlX2; this.coordinates[this.coordinatesPosition++] = controlY2; this.coordinates[this.coordinatesPosition++] = anchorX; this.coordinates[this.coordinatesPosition++] = anchorY; } beginFill(color: number): void { this.ensurePathCapacities(1, 0); this.commands[this.commandsPosition++] = PathCommand.BeginSolidFill; this.styles.writeUnsignedInt(color); this.hasFills = true; } writeMorphFill(color: number) { this.morphStyles.writeUnsignedInt(color); } endFill() { this.ensurePathCapacities(1, 0); this.commands[this.commandsPosition++] = PathCommand.EndFill; } endLine() { this.ensurePathCapacities(1, 0); this.commands[this.commandsPosition++] = PathCommand.LineEnd; } lineStyle(thickness: number, color: number, pixelHinting: boolean, scaleMode: LineScaleMode, caps: number, joints: number, miterLimit: number): void { assert(thickness === (thickness|0), thickness >= 0 && thickness <= 0xff * 20); this.ensurePathCapacities(2, 0); this.commands[this.commandsPosition++] = PathCommand.LineStyleSolid; this.coordinates[this.coordinatesPosition++] = thickness; const styles: DataBuffer = this.styles; styles.writeUnsignedInt(color); styles.writeBoolean(pixelHinting); styles.writeUnsignedByte(scaleMode); styles.writeUnsignedByte(caps); styles.writeUnsignedByte(joints); styles.writeUnsignedByte(miterLimit); this.hasLines = true; } writeMorphLineStyle(thickness: number, color: number) { this.morphCoordinates[this.coordinatesPosition - 1] = thickness; this.morphStyles.writeUnsignedInt(color); } /** * Bitmaps are specified the same for fills and strokes, so we only need to serialize them * once. The Parameter `pathCommand` is treated as the actual command to serialize, and must * be one of BeginBitmapFill and LineStyleBitmap. */ beginBitmap(pathCommand: PathCommand, bitmapId: number, matrix: ShapeMatrix, repeat: boolean, smooth: boolean): void { assert(pathCommand === PathCommand.BeginBitmapFill || pathCommand === PathCommand.LineStyleBitmap); this.ensurePathCapacities(1, 0); this.commands[this.commandsPosition++] = pathCommand; const styles: DataBuffer = this.styles; styles.writeUnsignedInt(bitmapId); this._writeStyleMatrix(matrix, false); styles.writeBoolean(repeat); styles.writeBoolean(smooth); this.hasFills = true; } writeMorphBitmap(matrix: ShapeMatrix) { this._writeStyleMatrix(matrix, true); } /** * Gradients are specified the same for fills and strokes, so we only need to serialize them * once. The Parameter `pathCommand` is treated as the actual command to serialize, and must * be one of BeginGradientFill and LineStyleGradient. */ beginGradient(pathCommand: PathCommand, colors: number[], ratios: number[], gradientType: number, matrix: ShapeMatrix, spread: number, interpolation: number, focalPointRatio: number) { assert(pathCommand === PathCommand.BeginGradientFill || pathCommand === PathCommand.LineStyleGradient); this.ensurePathCapacities(1, 0); this.commands[this.commandsPosition++] = pathCommand; const styles: DataBuffer = this.styles; styles.writeUnsignedByte(gradientType); assert(focalPointRatio === (focalPointRatio|0)); styles.writeShort(focalPointRatio); this._writeStyleMatrix(matrix, false); const colorStops = colors.length; styles.writeByte(colorStops); for (let i = 0; i < colorStops; i++) { // Ratio must be valid, otherwise we'd have bailed above. styles.writeUnsignedByte(ratios[i]); // Colors are coerced to uint32, with the highest byte stripped. styles.writeUnsignedInt(colors[i]); } styles.writeUnsignedByte(spread); styles.writeUnsignedByte(interpolation); this.hasFills = true; } writeMorphGradient(colors: number[], ratios: number[], matrix: ShapeMatrix) { this._writeStyleMatrix(matrix, true); const styles: DataBuffer = this.morphStyles; for (let i = 0; i < colors.length; i++) { // Ratio must be valid, otherwise we'd have bailed above. styles.writeUnsignedByte(ratios[i]); // Colors are coerced to uint32, with the highest byte stripped. styles.writeUnsignedInt(colors[i]); } } writeCommandAndCoordinates(command: PathCommand, x: number, y: number) { this.ensurePathCapacities(1, 2); this.commands[this.commandsPosition++] = command; this.coordinates[this.coordinatesPosition++] = x; this.coordinates[this.coordinatesPosition++] = y; } writeCoordinates(x: number, y: number) { this.ensurePathCapacities(0, 2); this.coordinates[this.coordinatesPosition++] = x; this.coordinates[this.coordinatesPosition++] = y; } writeMorphCoordinates(x: number, y: number) { this.morphCoordinates = ensureTypedArrayCapacity(this.morphCoordinates, this.coordinatesPosition); this.morphCoordinates[this.coordinatesPosition - 2] = x; this.morphCoordinates[this.coordinatesPosition - 1] = y; } clear() { this.commandsPosition = this.coordinatesPosition = 0; this.commands = new Uint8Array(DefaultSize.Commands); this.coordinates = new Int32Array(DefaultSize.Coordinates); this.styles = new DataBuffer(DefaultSize.Styles); this.styles.endian = 'auto'; this.hasFills = this.hasLines = false; } isEmpty() { return this.commandsPosition === 0; } clone(): ShapeData { const copy = new ShapeData(false); copy.commands = new Uint8Array(this.commands); copy.commandsPosition = this.commandsPosition; copy.coordinates = new Int32Array(this.coordinates); copy.coordinatesPosition = this.coordinatesPosition; copy.styles = new DataBuffer(this.styles.length); copy.styles.writeRawBytes(this.styles.bytes.subarray(0, this.styles.length)); if (this.morphStyles) { copy.morphStyles = new DataBuffer(this.morphStyles.length); copy.morphStyles.writeRawBytes( this.morphStyles.bytes.subarray(0, this.morphStyles.length)); } copy.hasFills = this.hasFills; copy.hasLines = this.hasLines; return copy; } toPlainObject(): PlainObjectShapeData { return new PlainObjectShapeData(this.commands, this.commandsPosition, this.coordinates, this.morphCoordinates, this.coordinatesPosition, this.styles.buffer, this.styles.length, this.morphStyles && this.morphStyles.buffer, this.morphStyles ? this.morphStyles.length : 0, this.hasFills, this.hasLines); } public get buffers(): ArrayBuffer[] { const buffers = [this.commands.buffer, this.coordinates.buffer, this.styles.buffer]; if (this.morphCoordinates) { buffers.push(this.morphCoordinates.buffer); } if (this.morphStyles) { buffers.push(this.morphStyles.buffer); } return buffers; } private _writeStyleMatrix(matrix: ShapeMatrix, isMorph: boolean) { const styles: DataBuffer = isMorph ? this.morphStyles : this.styles; styles.write6Floats(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); } private ensurePathCapacities(numCommands: number, numCoordinates: number) { // ensureTypedArrayCapacity will hopefully be inlined, in which case the field writes // will be optimized out. this.commands = ensureTypedArrayCapacity(this.commands, this.commandsPosition + numCommands); this.coordinates = ensureTypedArrayCapacity(this.coordinates, this.coordinatesPosition + numCoordinates); } }