From 0255e15dd1d46b5f00e51398b5052d88720eba9a Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Fri, 6 Oct 2023 21:42:19 -0500 Subject: [PATCH 01/45] Addons: initial pass for tree-shaking --- examples/jsm/animation/CCDIKSolver.js | 22 +- examples/jsm/animation/MMDAnimationHelper.js | 2 +- examples/jsm/animation/MMDPhysics.js | 8 +- examples/jsm/controls/ArcballControls.js | 14 +- examples/jsm/controls/DragControls.js | 16 +- examples/jsm/controls/FirstPersonControls.js | 6 +- examples/jsm/controls/OrbitControls.js | 4 +- examples/jsm/controls/PointerLockControls.js | 4 +- examples/jsm/controls/TransformControls.js | 46 +- examples/jsm/csm/CSM.js | 14 +- examples/jsm/csm/CSMFrustum.js | 2 +- examples/jsm/exporters/DRACOExporter.js | 37 +- examples/jsm/exporters/GLTFExporter.js | 394 ++++----- examples/jsm/geometries/RoundedBoxGeometry.js | 2 +- examples/jsm/helpers/VertexNormalsHelper.js | 6 +- examples/jsm/helpers/VertexTangentsHelper.js | 4 +- examples/jsm/interactive/InteractiveGroup.js | 2 +- examples/jsm/interactive/SelectionBox.js | 36 +- examples/jsm/lines/LineMaterial.js | 795 +++++++++--------- examples/jsm/lines/LineSegments2.js | 24 +- examples/jsm/lines/LineSegmentsGeometry.js | 4 +- examples/jsm/lines/Wireframe.js | 4 +- examples/jsm/loaders/FBXLoader.js | 4 +- examples/jsm/loaders/GLTFLoader.js | 4 +- examples/jsm/loaders/KTX2Loader.js | 742 ++++++++-------- examples/jsm/loaders/LDrawLoader.js | 6 +- examples/jsm/loaders/OBJLoader.js | 12 +- examples/jsm/loaders/PLYLoader.js | 2 +- examples/jsm/loaders/lwo/IFFParser.js | 213 +++-- examples/jsm/materials/MeshGouraudMaterial.js | 4 +- examples/jsm/math/Capsule.js | 6 +- examples/jsm/math/ConvexHull.js | 10 +- examples/jsm/math/MeshSurfaceSampler.js | 8 +- examples/jsm/math/OBB.js | 28 +- examples/jsm/math/Octree.js | 14 +- examples/jsm/misc/ConvexObjectBreaker.js | 2 +- examples/jsm/misc/Gyroscope.js | 12 +- examples/jsm/modifiers/EdgeSplitModifier.js | 8 +- examples/jsm/modifiers/SimplifyModifier.js | 3 +- examples/jsm/objects/Lensflare.js | 150 ++-- examples/jsm/objects/Reflector.js | 138 +-- examples/jsm/objects/ReflectorForSSRPass.js | 194 ++--- examples/jsm/objects/Refractor.js | 124 +-- examples/jsm/objects/ShadowMesh.js | 2 +- examples/jsm/objects/Sky.js | 302 +++---- examples/jsm/objects/Water2.js | 296 ++++--- examples/jsm/offscreen/jank.js | 1 + examples/jsm/offscreen/offscreen.js | 14 +- examples/jsm/offscreen/scene.js | 1 + examples/jsm/physics/RapierPhysics.js | 4 +- examples/jsm/postprocessing/BloomPass.js | 6 +- examples/jsm/postprocessing/OutlinePass.js | 6 +- examples/jsm/postprocessing/Pass.js | 8 +- examples/jsm/postprocessing/SAOPass.js | 12 +- examples/jsm/postprocessing/SSAOPass.js | 16 +- examples/jsm/postprocessing/SSRPass.js | 18 +- .../jsm/postprocessing/UnrealBloomPass.js | 6 +- examples/jsm/renderers/CSS2DRenderer.js | 10 +- examples/jsm/renderers/CSS3DRenderer.js | 10 +- examples/jsm/shaders/BokehShader2.js | 2 +- examples/jsm/shaders/ColorCorrectionShader.js | 6 +- examples/jsm/shaders/ColorifyShader.js | 2 +- examples/jsm/shaders/ConvolutionShader.js | 2 +- .../jsm/shaders/DepthLimitedBlurShader.js | 4 +- examples/jsm/shaders/DotScreenShader.js | 4 +- examples/jsm/shaders/FXAAShader.js | 2 +- examples/jsm/shaders/FreiChenShader.js | 2 +- examples/jsm/shaders/GodRaysShader.js | 8 +- .../jsm/shaders/LuminosityHighPassShader.js | 2 +- examples/jsm/shaders/MMDToonShader.js | 2 +- examples/jsm/shaders/NormalMapShader.js | 4 +- examples/jsm/shaders/SAOShader.js | 6 +- examples/jsm/shaders/SMAAShader.js | 6 +- examples/jsm/shaders/SSAOShader.js | 6 +- examples/jsm/shaders/SSRShader.js | 8 +- examples/jsm/shaders/SobelOperatorShader.js | 2 +- .../jsm/shaders/SubsurfaceScatteringShader.js | 2 +- examples/jsm/shaders/ToonShader.js | 50 +- examples/jsm/shaders/TriangleBlurShader.js | 2 +- examples/jsm/shaders/VelocityShader.js | 8 +- examples/jsm/shaders/VolumeShader.js | 4 +- examples/jsm/webxr/VRButton.js | 5 +- examples/jsm/webxr/XRHandPrimitiveModel.js | 4 +- 83 files changed, 1987 insertions(+), 1998 deletions(-) diff --git a/examples/jsm/animation/CCDIKSolver.js b/examples/jsm/animation/CCDIKSolver.js index 7066c99b7fc240..577653b856ddee 100644 --- a/examples/jsm/animation/CCDIKSolver.js +++ b/examples/jsm/animation/CCDIKSolver.js @@ -13,17 +13,17 @@ import { Vector3 } from 'three'; -const _q = new Quaternion(); -const _targetPos = new Vector3(); -const _targetVec = new Vector3(); -const _effectorPos = new Vector3(); -const _effectorVec = new Vector3(); -const _linkPos = new Vector3(); -const _invLinkQ = new Quaternion(); -const _linkScale = new Vector3(); -const _axis = new Vector3(); -const _vector = new Vector3(); -const _matrix = new Matrix4(); +const _q = /* @__PURE__ */ new Quaternion(); +const _targetPos = /* @__PURE__ */ new Vector3(); +const _targetVec = /* @__PURE__ */ new Vector3(); +const _effectorPos = /* @__PURE__ */ new Vector3(); +const _effectorVec = /* @__PURE__ */ new Vector3(); +const _linkPos = /* @__PURE__ */ new Vector3(); +const _invLinkQ = /* @__PURE__ */ new Quaternion(); +const _linkScale = /* @__PURE__ */ new Vector3(); +const _axis = /* @__PURE__ */ new Vector3(); +const _vector = /* @__PURE__ */ new Vector3(); +const _matrix = /* @__PURE__ */ new Matrix4(); /** diff --git a/examples/jsm/animation/MMDAnimationHelper.js b/examples/jsm/animation/MMDAnimationHelper.js index b24dea116a84fe..1cc2bdb06bc724 100644 --- a/examples/jsm/animation/MMDAnimationHelper.js +++ b/examples/jsm/animation/MMDAnimationHelper.js @@ -1113,7 +1113,7 @@ class AudioManager { } -const _q = new Quaternion(); +const _q = /* @__PURE__ */ new Quaternion(); /** * Solver for Grant (Fuyo in Japanese. I just google translated because diff --git a/examples/jsm/animation/MMDPhysics.js b/examples/jsm/animation/MMDPhysics.js index 057090112e818b..003917d235bb02 100644 --- a/examples/jsm/animation/MMDPhysics.js +++ b/examples/jsm/animation/MMDPhysics.js @@ -1230,10 +1230,10 @@ class Constraint { // -const _position = new Vector3(); -const _quaternion = new Quaternion(); -const _scale = new Vector3(); -const _matrixWorldInv = new Matrix4(); +const _position = /* @__PURE__ */ new Vector3(); +const _quaternion = /* @__PURE__ */ new Quaternion(); +const _scale = /* @__PURE__ */ new Vector3(); +const _matrixWorldInv = /* @__PURE__ */ new Matrix4(); class MMDPhysicsHelper extends Object3D { diff --git a/examples/jsm/controls/ArcballControls.js b/examples/jsm/controls/ArcballControls.js index b6b36962e35a27..c12c2c9d2c13cf 100644 --- a/examples/jsm/controls/ArcballControls.js +++ b/examples/jsm/controls/ArcballControls.js @@ -54,8 +54,8 @@ const _center = { //transformation matrices for gizmos and camera const _transformation = { - camera: new Matrix4(), - gizmos: new Matrix4() + camera: /* @__PURE__ */ new Matrix4(), + gizmos: /* @__PURE__ */ new Matrix4() }; @@ -64,12 +64,12 @@ const _changeEvent = { type: 'change' }; const _startEvent = { type: 'start' }; const _endEvent = { type: 'end' }; -const _raycaster = new Raycaster(); -const _offset = new Vector3(); +const _raycaster = /* @__PURE__ */ new Raycaster(); +const _offset = /* @__PURE__ */ new Vector3(); -const _gizmoMatrixStateTemp = new Matrix4(); -const _cameraMatrixStateTemp = new Matrix4(); -const _scalePointTemp = new Vector3(); +const _gizmoMatrixStateTemp = /* @__PURE__ */ new Matrix4(); +const _cameraMatrixStateTemp = /* @__PURE__ */ new Matrix4(); +const _scalePointTemp = /* @__PURE__ */ new Vector3(); /** * * @param {Camera} camera Virtual camera used in the scene diff --git a/examples/jsm/controls/DragControls.js b/examples/jsm/controls/DragControls.js index f93f7745075561..c05729fab73c31 100644 --- a/examples/jsm/controls/DragControls.js +++ b/examples/jsm/controls/DragControls.js @@ -7,14 +7,14 @@ import { Vector3 } from 'three'; -const _plane = new Plane(); -const _raycaster = new Raycaster(); - -const _pointer = new Vector2(); -const _offset = new Vector3(); -const _intersection = new Vector3(); -const _worldPosition = new Vector3(); -const _inverseMatrix = new Matrix4(); +const _plane = /* @__PURE__ */ new Plane(); +const _raycaster = /* @__PURE__ */ new Raycaster(); + +const _pointer = /* @__PURE__ */ new Vector2(); +const _offset = /* @__PURE__ */ new Vector3(); +const _intersection = /* @__PURE__ */ new Vector3(); +const _worldPosition = /* @__PURE__ */ new Vector3(); +const _inverseMatrix = /* @__PURE__ */ new Matrix4(); class DragControls extends EventDispatcher { diff --git a/examples/jsm/controls/FirstPersonControls.js b/examples/jsm/controls/FirstPersonControls.js index 0d03421c8b8c54..f61f091fbc027c 100644 --- a/examples/jsm/controls/FirstPersonControls.js +++ b/examples/jsm/controls/FirstPersonControls.js @@ -4,9 +4,9 @@ import { Vector3 } from 'three'; -const _lookDirection = new Vector3(); -const _spherical = new Spherical(); -const _target = new Vector3(); +const _lookDirection = /* @__PURE__ */ new Vector3(); +const _spherical = /* @__PURE__ */ new Spherical(); +const _target = /* @__PURE__ */ new Vector3(); class FirstPersonControls { diff --git a/examples/jsm/controls/OrbitControls.js b/examples/jsm/controls/OrbitControls.js index 6b8bea9104919c..c007569dc05df7 100644 --- a/examples/jsm/controls/OrbitControls.js +++ b/examples/jsm/controls/OrbitControls.js @@ -21,8 +21,8 @@ import { const _changeEvent = { type: 'change' }; const _startEvent = { type: 'start' }; const _endEvent = { type: 'end' }; -const _ray = new Ray(); -const _plane = new Plane(); +const _ray = /* @__PURE__ */ new Ray(); +const _plane = /* @__PURE__ */ new Plane(); const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); class OrbitControls extends EventDispatcher { diff --git a/examples/jsm/controls/PointerLockControls.js b/examples/jsm/controls/PointerLockControls.js index 1d2856d3f6131b..d1dc4949e1b8d8 100644 --- a/examples/jsm/controls/PointerLockControls.js +++ b/examples/jsm/controls/PointerLockControls.js @@ -4,8 +4,8 @@ import { Vector3 } from 'three'; -const _euler = new Euler( 0, 0, 0, 'YXZ' ); -const _vector = new Vector3(); +const _euler = /* @__PURE__ */ new Euler( 0, 0, 0, 'YXZ' ); +const _vector = /* @__PURE__ */ new Vector3(); const _changeEvent = { type: 'change' }; const _lockEvent = { type: 'lock' }; diff --git a/examples/jsm/controls/TransformControls.js b/examples/jsm/controls/TransformControls.js index 4beff97a5e0eba..8347945a61426c 100644 --- a/examples/jsm/controls/TransformControls.js +++ b/examples/jsm/controls/TransformControls.js @@ -20,15 +20,15 @@ import { Vector3 } from 'three'; -const _raycaster = new Raycaster(); +const _raycaster = /* @__PURE__ */ new Raycaster(); -const _tempVector = new Vector3(); -const _tempVector2 = new Vector3(); -const _tempQuaternion = new Quaternion(); +const _tempVector = /* @__PURE__ */ new Vector3(); +const _tempVector2 = /* @__PURE__ */ new Vector3(); +const _tempQuaternion = /* @__PURE__ */ new Quaternion(); const _unit = { - X: new Vector3( 1, 0, 0 ), - Y: new Vector3( 0, 1, 0 ), - Z: new Vector3( 0, 0, 1 ) + X: /* @__PURE__ */ new Vector3( 1, 0, 0 ), + Y: /* @__PURE__ */ new Vector3( 0, 1, 0 ), + Z: /* @__PURE__ */ new Vector3( 0, 0, 1 ) }; const _changeEvent = { type: 'change' }; @@ -745,22 +745,22 @@ function intersectObjectWithRay( object, raycaster, includeInvisible ) { // Reusable utility variables -const _tempEuler = new Euler(); -const _alignVector = new Vector3( 0, 1, 0 ); -const _zeroVector = new Vector3( 0, 0, 0 ); -const _lookAtMatrix = new Matrix4(); -const _tempQuaternion2 = new Quaternion(); -const _identityQuaternion = new Quaternion(); -const _dirVector = new Vector3(); -const _tempMatrix = new Matrix4(); - -const _unitX = new Vector3( 1, 0, 0 ); -const _unitY = new Vector3( 0, 1, 0 ); -const _unitZ = new Vector3( 0, 0, 1 ); - -const _v1 = new Vector3(); -const _v2 = new Vector3(); -const _v3 = new Vector3(); +const _tempEuler = /* @__PURE__ */ new Euler(); +const _alignVector = /* @__PURE__ */ new Vector3( 0, 1, 0 ); +const _zeroVector = /* @__PURE__ */ new Vector3( 0, 0, 0 ); +const _lookAtMatrix = /* @__PURE__ */ new Matrix4(); +const _tempQuaternion2 = /* @__PURE__ */ new Quaternion(); +const _identityQuaternion = /* @__PURE__ */ new Quaternion(); +const _dirVector = /* @__PURE__ */ new Vector3(); +const _tempMatrix = /* @__PURE__ */ new Matrix4(); + +const _unitX = /* @__PURE__ */ new Vector3( 1, 0, 0 ); +const _unitY = /* @__PURE__ */ new Vector3( 0, 1, 0 ); +const _unitZ = /* @__PURE__ */ new Vector3( 0, 0, 1 ); + +const _v1 = /* @__PURE__ */ new Vector3(); +const _v2 = /* @__PURE__ */ new Vector3(); +const _v3 = /* @__PURE__ */ new Vector3(); class TransformControlsGizmo extends Object3D { diff --git a/examples/jsm/csm/CSM.js b/examples/jsm/csm/CSM.js index 649a0e5e9fb450..43a3b8df94873f 100644 --- a/examples/jsm/csm/CSM.js +++ b/examples/jsm/csm/CSM.js @@ -10,15 +10,15 @@ import { import { CSMFrustum } from './CSMFrustum.js'; import { CSMShader } from './CSMShader.js'; -const _cameraToLightMatrix = new Matrix4(); -const _lightSpaceFrustum = new CSMFrustum(); -const _center = new Vector3(); -const _bbox = new Box3(); +const _cameraToLightMatrix = /* @__PURE__ */ new Matrix4(); +const _lightSpaceFrustum = /* @__PURE__ */ new CSMFrustum(); +const _center = /* @__PURE__ */ new Vector3(); +const _bbox = /* @__PURE__ */ new Box3(); const _uniformArray = []; const _logArray = []; -const _lightOrientationMatrix = new Matrix4(); -const _lightOrientationMatrixInverse = new Matrix4(); -const _up = new Vector3( 0, 1, 0 ); +const _lightOrientationMatrix = /* @__PURE__ */ new Matrix4(); +const _lightOrientationMatrixInverse = /* @__PURE__ */ new Matrix4(); +const _up = /* @__PURE__ */ new Vector3( 0, 1, 0 ); export class CSM { diff --git a/examples/jsm/csm/CSMFrustum.js b/examples/jsm/csm/CSMFrustum.js index 2d968bebdb929f..9bd8d0def6c1cc 100644 --- a/examples/jsm/csm/CSMFrustum.js +++ b/examples/jsm/csm/CSMFrustum.js @@ -1,6 +1,6 @@ import { Vector3, Matrix4 } from 'three'; -const inverseProjectionMatrix = new Matrix4(); +const inverseProjectionMatrix = /* @__PURE__ */ new Matrix4(); class CSMFrustum { diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 85a2e15adc4d6d..7ef4a209f211b9 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -18,6 +18,24 @@ import { Color } from 'three'; /* global DracoEncoderModule */ class DRACOExporter { + // Encoder methods + + static MESH_EDGEBREAKER_ENCODING = 1; + static MESH_SEQUENTIAL_ENCODING = 0; + + // Geometry type + + static POINT_CLOUD = 0; + static TRIANGULAR_MESH = 1; + + // Attribute type + + static INVALID = - 1; + static POSITION = 0; + static NORMAL = 1; + static COLOR = 2; + static TEX_COORD = 3; + static GENERIC = 4; parse( object, options = {} ) { @@ -245,23 +263,4 @@ function createVertexColorSRGBArray( attribute ) { } -// Encoder methods - -DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; -DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; - -// Geometry type - -DRACOExporter.POINT_CLOUD = 0; -DRACOExporter.TRIANGULAR_MESH = 1; - -// Attribute type - -DRACOExporter.INVALID = - 1; -DRACOExporter.POSITION = 0; -DRACOExporter.NORMAL = 1; -DRACOExporter.COLOR = 2; -DRACOExporter.TEX_COORD = 3; -DRACOExporter.GENERIC = 4; - export { DRACOExporter }; diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index f1a9831e72cb18..8e5fda7dff7cb0 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -65,6 +65,202 @@ const KHR_mesh_quantization_ExtraAttrTypes = { class GLTFExporter { + /** + * Static utility functions + */ + static Utils = { + + insertKeyframe: function ( track, time ) { + + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); + + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + + let index; + + if ( track.times.length === 0 ) { + + times[ 0 ] = time; + + for ( let i = 0; i < valueSize; i ++ ) { + + values[ i ] = 0; + + } + + index = 0; + + } else if ( time < track.times[ 0 ] ) { + + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + + times[ 0 ] = time; + times.set( track.times, 1 ); + + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); + + index = 0; + + } else if ( time > track.times[ track.times.length - 1 ] ) { + + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + + return track.times.length - 1; + + } + + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); + + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); + + index = times.length - 1; + + } else { + + for ( let i = 0; i < track.times.length; i ++ ) { + + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); + + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + + index = i + 1; + + break; + + } + + } + + } + + track.times = times; + track.values = values; + + return index; + + }, + + mergeMorphTargetTracks: function ( clip, root ) { + + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; + + for ( let i = 0; i < sourceTracks.length; ++ i ) { + + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; + + } + + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + + } + + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); + + } + + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + + if ( targetIndex === undefined ) { + + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + + } + + let mergedTrack; + + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + + mergedTrack = sourceTrack.clone(); + + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + + } + + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; + + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); + + continue; + + } + + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + + } + + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + + } + + } + + clip.tracks = tracks; + + return clip; + + } + + }; + constructor() { this.pluginCallbacks = []; @@ -262,7 +458,7 @@ const PATH_PROPERTIES = { morphTargetInfluences: 'weights' }; -const DEFAULT_SPECULAR_COLOR = new Color(); +const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color(); // GLB constants // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification @@ -3032,200 +3228,4 @@ class GLTFMeshGpuInstancing { } -/** - * Static utility functions - */ -GLTFExporter.Utils = { - - insertKeyframe: function ( track, time ) { - - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); - - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - - let index; - - if ( track.times.length === 0 ) { - - times[ 0 ] = time; - - for ( let i = 0; i < valueSize; i ++ ) { - - values[ i ] = 0; - - } - - index = 0; - - } else if ( time < track.times[ 0 ] ) { - - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - - times[ 0 ] = time; - times.set( track.times, 1 ); - - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); - - index = 0; - - } else if ( time > track.times[ track.times.length - 1 ] ) { - - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - - return track.times.length - 1; - - } - - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); - - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); - - index = times.length - 1; - - } else { - - for ( let i = 0; i < track.times.length; i ++ ) { - - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); - - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - - index = i + 1; - - break; - - } - - } - - } - - track.times = times; - track.values = values; - - return index; - - }, - - mergeMorphTargetTracks: function ( clip, root ) { - - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; - - for ( let i = 0; i < sourceTracks.length; ++ i ) { - - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; - - } - - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); - - } - - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); - - } - - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - - if ( targetIndex === undefined ) { - - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - - } - - let mergedTrack; - - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - - mergedTrack = sourceTrack.clone(); - - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; - - } - - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; - - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); - - continue; - - } - - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - - } - - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - - } - - } - - clip.tracks = tracks; - - return clip; - - } - -}; - export { GLTFExporter }; diff --git a/examples/jsm/geometries/RoundedBoxGeometry.js b/examples/jsm/geometries/RoundedBoxGeometry.js index 8baa16822365c4..9399b031619a68 100644 --- a/examples/jsm/geometries/RoundedBoxGeometry.js +++ b/examples/jsm/geometries/RoundedBoxGeometry.js @@ -3,7 +3,7 @@ import { Vector3 } from 'three'; -const _tempNormal = new Vector3(); +const _tempNormal = /* @__PURE__ */ new Vector3(); function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength ) { diff --git a/examples/jsm/helpers/VertexNormalsHelper.js b/examples/jsm/helpers/VertexNormalsHelper.js index bfe41aba8644d1..6cf45ff656f791 100644 --- a/examples/jsm/helpers/VertexNormalsHelper.js +++ b/examples/jsm/helpers/VertexNormalsHelper.js @@ -7,9 +7,9 @@ import { Vector3 } from 'three'; -const _v1 = new Vector3(); -const _v2 = new Vector3(); -const _normalMatrix = new Matrix3(); +const _v1 = /* @__PURE__ */ new Vector3(); +const _v2 = /* @__PURE__ */ new Vector3(); +const _normalMatrix = /* @__PURE__ */ new Matrix3(); class VertexNormalsHelper extends LineSegments { diff --git a/examples/jsm/helpers/VertexTangentsHelper.js b/examples/jsm/helpers/VertexTangentsHelper.js index 1ad413f65e95c7..82afd8bd7ccb5d 100644 --- a/examples/jsm/helpers/VertexTangentsHelper.js +++ b/examples/jsm/helpers/VertexTangentsHelper.js @@ -6,8 +6,8 @@ import { Vector3 } from 'three'; -const _v1 = new Vector3(); -const _v2 = new Vector3(); +const _v1 = /* @__PURE__ */ new Vector3(); +const _v2 = /* @__PURE__ */ new Vector3(); class VertexTangentsHelper extends LineSegments { diff --git a/examples/jsm/interactive/InteractiveGroup.js b/examples/jsm/interactive/InteractiveGroup.js index a8d7b30f59e279..eac31865c038fa 100644 --- a/examples/jsm/interactive/InteractiveGroup.js +++ b/examples/jsm/interactive/InteractiveGroup.js @@ -5,7 +5,7 @@ import { Vector2 } from 'three'; -const _pointer = new Vector2(); +const _pointer = /* @__PURE__ */ new Vector2(); const _event = { type: '', data: _pointer }; class InteractiveGroup extends Group { diff --git a/examples/jsm/interactive/SelectionBox.js b/examples/jsm/interactive/SelectionBox.js index 597b9b2f101f66..d1b3203552d269 100644 --- a/examples/jsm/interactive/SelectionBox.js +++ b/examples/jsm/interactive/SelectionBox.js @@ -9,29 +9,29 @@ import { * This is a class to check whether objects are in a selection area in 3D space */ -const _frustum = new Frustum(); -const _center = new Vector3(); +const _frustum = /* @__PURE__ */ new Frustum(); +const _center = /* @__PURE__ */ new Vector3(); -const _tmpPoint = new Vector3(); +const _tmpPoint = /* @__PURE__ */ new Vector3(); -const _vecNear = new Vector3(); -const _vecTopLeft = new Vector3(); -const _vecTopRight = new Vector3(); -const _vecDownRight = new Vector3(); -const _vecDownLeft = new Vector3(); +const _vecNear = /* @__PURE__ */ new Vector3(); +const _vecTopLeft = /* @__PURE__ */ new Vector3(); +const _vecTopRight = /* @__PURE__ */ new Vector3(); +const _vecDownRight = /* @__PURE__ */ new Vector3(); +const _vecDownLeft = /* @__PURE__ */ new Vector3(); -const _vecFarTopLeft = new Vector3(); -const _vecFarTopRight = new Vector3(); -const _vecFarDownRight = new Vector3(); -const _vecFarDownLeft = new Vector3(); +const _vecFarTopLeft = /* @__PURE__ */ new Vector3(); +const _vecFarTopRight = /* @__PURE__ */ new Vector3(); +const _vecFarDownRight = /* @__PURE__ */ new Vector3(); +const _vecFarDownLeft = /* @__PURE__ */ new Vector3(); -const _vectemp1 = new Vector3(); -const _vectemp2 = new Vector3(); -const _vectemp3 = new Vector3(); +const _vectemp1 = /* @__PURE__ */ new Vector3(); +const _vectemp2 = /* @__PURE__ */ new Vector3(); +const _vectemp3 = /* @__PURE__ */ new Vector3(); -const _matrix = new Matrix4(); -const _quaternion = new Quaternion(); -const _scale = new Vector3(); +const _matrix = /* @__PURE__ */ new Matrix4(); +const _quaternion = /* @__PURE__ */ new Quaternion(); +const _scale = /* @__PURE__ */ new Vector3(); class SelectionBox { diff --git a/examples/jsm/lines/LineMaterial.js b/examples/jsm/lines/LineMaterial.js index 4e30e7614a30bd..2985648eb32160 100644 --- a/examples/jsm/lines/LineMaterial.js +++ b/examples/jsm/lines/LineMaterial.js @@ -19,420 +19,423 @@ import { Vector2 } from 'three'; - -UniformsLib.line = { - - worldUnits: { value: 1 }, - linewidth: { value: 1 }, - resolution: { value: new Vector2( 1, 1 ) }, - dashOffset: { value: 0 }, - dashScale: { value: 1 }, - dashSize: { value: 1 }, - gapSize: { value: 1 } // todo FIX - maybe change to totalSize - -}; - -ShaderLib[ 'line' ] = { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.fog, - UniformsLib.line - ] ), - - vertexShader: - /* glsl */` - #include - #include - #include - #include - #include - - uniform float linewidth; - uniform vec2 resolution; - - attribute vec3 instanceStart; - attribute vec3 instanceEnd; - - attribute vec3 instanceColorStart; - attribute vec3 instanceColorEnd; - - #ifdef WORLD_UNITS - - varying vec4 worldPos; - varying vec3 worldStart; - varying vec3 worldEnd; - - #ifdef USE_DASH - +/* @__PURE__ */ ( () => { + + UniformsLib.line = { + + worldUnits: { value: 1 }, + linewidth: { value: 1 }, + resolution: { value: new Vector2( 1, 1 ) }, + dashOffset: { value: 0 }, + dashScale: { value: 1 }, + dashSize: { value: 1 }, + gapSize: { value: 1 } // todo FIX - maybe change to totalSize + + }; + + ShaderLib[ 'line' ] = { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.fog, + UniformsLib.line + ] ), + + vertexShader: + /* glsl */` + #include + #include + #include + #include + #include + + uniform float linewidth; + uniform vec2 resolution; + + attribute vec3 instanceStart; + attribute vec3 instanceEnd; + + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + + varying vec2 vUv; + + #endif + + #else + varying vec2 vUv; - + #endif - - #else - - varying vec2 vUv; - - #endif - - #ifdef USE_DASH - - uniform float dashScale; - attribute float instanceDistanceStart; - attribute float instanceDistanceEnd; - varying float vLineDistance; - - #endif - - void trimSegment( const in vec4 start, inout vec4 end ) { - - // trim end segment so it terminates between the camera plane and the near plane - - // conservative estimate of the near plane - float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column - float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column - float nearEstimate = - 0.5 * b / a; - - float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); - - end.xyz = mix( start.xyz, end.xyz, alpha ); - - } - - void main() { - - #ifdef USE_COLOR - - vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; - - #endif - + #ifdef USE_DASH - - vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; - vUv = uv; - - #endif - - float aspect = resolution.x / resolution.y; - - // camera space - vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); - vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); - - #ifdef WORLD_UNITS - - worldStart = start.xyz; - worldEnd = end.xyz; - - #else - - vUv = uv; - + + uniform float dashScale; + attribute float instanceDistanceStart; + attribute float instanceDistanceEnd; + varying float vLineDistance; + #endif - - // special case for perspective projection, and segments that terminate either in, or behind, the camera plane - // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space - // but we need to perform ndc-space calculations in the shader, so we must address this issue directly - // perhaps there is a more elegant solution -- WestLangley - - bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column - - if ( perspective ) { - - if ( start.z < 0.0 && end.z >= 0.0 ) { - - trimSegment( start, end ); - - } else if ( end.z < 0.0 && start.z >= 0.0 ) { - - trimSegment( end, start ); - - } - + + void trimSegment( const in vec4 start, inout vec4 end ) { + + // trim end segment so it terminates between the camera plane and the near plane + + // conservative estimate of the near plane + float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column + float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column + float nearEstimate = - 0.5 * b / a; + + float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); + + end.xyz = mix( start.xyz, end.xyz, alpha ); + } - - // clip space - vec4 clipStart = projectionMatrix * start; - vec4 clipEnd = projectionMatrix * end; - - // ndc space - vec3 ndcStart = clipStart.xyz / clipStart.w; - vec3 ndcEnd = clipEnd.xyz / clipEnd.w; - - // direction - vec2 dir = ndcEnd.xy - ndcStart.xy; - - // account for clip-space aspect ratio - dir.x *= aspect; - dir = normalize( dir ); - - #ifdef WORLD_UNITS - - // get the offset direction as perpendicular to the view vector - vec3 worldDir = normalize( end.xyz - start.xyz ); - vec3 offset; - if ( position.y < 0.5 ) { - - offset = normalize( cross( start.xyz, worldDir ) ); - - } else { - - offset = normalize( cross( end.xyz, worldDir ) ); - - } - - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; - - float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); - - // don't extend the line if we're rendering dashes because we - // won't be rendering the endcaps - #ifndef USE_DASH - - // extend the line bounds to encompass endcaps - start.xyz += - worldDir * linewidth * 0.5; - end.xyz += worldDir * linewidth * 0.5; - - // shift the position of the quad so it hugs the forward edge of the line - offset.xy -= dir * forwardOffset; - offset.z += 0.5; - + + void main() { + + #ifdef USE_COLOR + + vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; + #endif - - // endcaps - if ( position.y > 1.0 || position.y < 0.0 ) { - - offset.xy += dir * 2.0 * forwardOffset; - - } - - // adjust for linewidth - offset *= linewidth * 0.5; - - // set the world position - worldPos = ( position.y < 0.5 ) ? start : end; - worldPos.xyz += offset; - - // project the worldpos - vec4 clip = projectionMatrix * worldPos; - - // shift the depth of the projected points so the line - // segments overlap neatly - vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; - clip.z = clipPose.z * clip.w; - - #else - - vec2 offset = vec2( dir.y, - dir.x ); - // undo aspect ratio adjustment - dir.x /= aspect; - offset.x /= aspect; - - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; - - // endcaps - if ( position.y < 0.0 ) { - - offset += - dir; - - } else if ( position.y > 1.0 ) { - - offset += dir; - + + #ifdef USE_DASH + + vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; + vUv = uv; + + #endif + + float aspect = resolution.x / resolution.y; + + // camera space + vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + + #ifdef WORLD_UNITS + + worldStart = start.xyz; + worldEnd = end.xyz; + + #else + + vUv = uv; + + #endif + + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley + + bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column + + if ( perspective ) { + + if ( start.z < 0.0 && end.z >= 0.0 ) { + + trimSegment( start, end ); + + } else if ( end.z < 0.0 && start.z >= 0.0 ) { + + trimSegment( end, start ); + + } + } - - // adjust for linewidth - offset *= linewidth; - - // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... - offset /= resolution.y; - - // select end - vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; - - // back to clip space - offset *= clip.w; - - clip.xy += offset; - - #endif - - gl_Position = clip; - - vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation - - #include - #include - #include - - } - `, - - fragmentShader: - /* glsl */` - uniform vec3 diffuse; - uniform float opacity; - uniform float linewidth; - - #ifdef USE_DASH - - uniform float dashOffset; - uniform float dashSize; - uniform float gapSize; - - #endif - - varying float vLineDistance; - - #ifdef WORLD_UNITS - - varying vec4 worldPos; - varying vec3 worldStart; - varying vec3 worldEnd; - - #ifdef USE_DASH - - varying vec2 vUv; - - #endif - - #else - - varying vec2 vUv; - - #endif - - #include - #include - #include - #include - #include - - vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { - - float mua; - float mub; - - vec3 p13 = p1 - p3; - vec3 p43 = p4 - p3; - - vec3 p21 = p2 - p1; - - float d1343 = dot( p13, p43 ); - float d4321 = dot( p43, p21 ); - float d1321 = dot( p13, p21 ); - float d4343 = dot( p43, p43 ); - float d2121 = dot( p21, p21 ); - - float denom = d2121 * d4343 - d4321 * d4321; - - float numer = d1343 * d4321 - d1321 * d4343; - - mua = numer / denom; - mua = clamp( mua, 0.0, 1.0 ); - mub = ( d1343 + d4321 * ( mua ) ) / d4343; - mub = clamp( mub, 0.0, 1.0 ); - - return vec2( mua, mub ); - - } - - void main() { - - #include - + + // clip space + vec4 clipStart = projectionMatrix * start; + vec4 clipEnd = projectionMatrix * end; + + // ndc space + vec3 ndcStart = clipStart.xyz / clipStart.w; + vec3 ndcEnd = clipEnd.xyz / clipEnd.w; + + // direction + vec2 dir = ndcEnd.xy - ndcStart.xy; + + // account for clip-space aspect ratio + dir.x *= aspect; + dir = normalize( dir ); + + #ifdef WORLD_UNITS + + // get the offset direction as perpendicular to the view vector + vec3 worldDir = normalize( end.xyz - start.xyz ); + vec3 offset; + if ( position.y < 0.5 ) { + + offset = normalize( cross( start.xyz, worldDir ) ); + + } else { + + offset = normalize( cross( end.xyz, worldDir ) ); + + } + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); + + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + #ifndef USE_DASH + + // extend the line bounds to encompass endcaps + start.xyz += - worldDir * linewidth * 0.5; + end.xyz += worldDir * linewidth * 0.5; + + // shift the position of the quad so it hugs the forward edge of the line + offset.xy -= dir * forwardOffset; + offset.z += 0.5; + + #endif + + // endcaps + if ( position.y > 1.0 || position.y < 0.0 ) { + + offset.xy += dir * 2.0 * forwardOffset; + + } + + // adjust for linewidth + offset *= linewidth * 0.5; + + // set the world position + worldPos = ( position.y < 0.5 ) ? start : end; + worldPos.xyz += offset; + + // project the worldpos + vec4 clip = projectionMatrix * worldPos; + + // shift the depth of the projected points so the line + // segments overlap neatly + vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; + clip.z = clipPose.z * clip.w; + + #else + + vec2 offset = vec2( dir.y, - dir.x ); + // undo aspect ratio adjustment + dir.x /= aspect; + offset.x /= aspect; + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + // endcaps + if ( position.y < 0.0 ) { + + offset += - dir; + + } else if ( position.y > 1.0 ) { + + offset += dir; + + } + + // adjust for linewidth + offset *= linewidth; + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset /= resolution.y; + + // select end + vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; + + // back to clip space + offset *= clip.w; + + clip.xy += offset; + + #endif + + gl_Position = clip; + + vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation + + #include + #include + #include + + } + `, + + fragmentShader: + /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + uniform float linewidth; + #ifdef USE_DASH - - if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps - - if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX - + + uniform float dashOffset; + uniform float dashSize; + uniform float gapSize; + #endif - - float alpha = opacity; - + + varying float vLineDistance; + #ifdef WORLD_UNITS - - // Find the closest points on the view ray and the line segment - vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; - vec3 lineDir = worldEnd - worldStart; - vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); - - vec3 p1 = worldStart + lineDir * params.x; - vec3 p2 = rayEnd * params.y; - vec3 delta = p1 - p2; - float len = length( delta ); - float norm = len / linewidth; - - #ifndef USE_DASH - - #ifdef USE_ALPHA_TO_COVERAGE - - float dnorm = fwidth( norm ); - alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); - - #else - - if ( norm > 0.5 ) { - - discard; - - } - - #endif - + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + + varying vec2 vUv; + #endif - + #else - - #ifdef USE_ALPHA_TO_COVERAGE - - // artifacts appear on some hardware if a derivative is taken within a conditional - float a = vUv.x; - float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; - float len2 = a * a + b * b; - float dlen = fwidth( len2 ); - - if ( abs( vUv.y ) > 1.0 ) { - - alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); - - } - + + varying vec2 vUv; + + #endif + + #include + #include + #include + #include + #include + + vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { + + float mua; + float mub; + + vec3 p13 = p1 - p3; + vec3 p43 = p4 - p3; + + vec3 p21 = p2 - p1; + + float d1343 = dot( p13, p43 ); + float d4321 = dot( p43, p21 ); + float d1321 = dot( p13, p21 ); + float d4343 = dot( p43, p43 ); + float d2121 = dot( p21, p21 ); + + float denom = d2121 * d4343 - d4321 * d4321; + + float numer = d1343 * d4321 - d1321 * d4343; + + mua = numer / denom; + mua = clamp( mua, 0.0, 1.0 ); + mub = ( d1343 + d4321 * ( mua ) ) / d4343; + mub = clamp( mub, 0.0, 1.0 ); + + return vec2( mua, mub ); + + } + + void main() { + + #include + + #ifdef USE_DASH + + if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps + + if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX + + #endif + + float alpha = opacity; + + #ifdef WORLD_UNITS + + // Find the closest points on the view ray and the line segment + vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; + vec3 lineDir = worldEnd - worldStart; + vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); + + vec3 p1 = worldStart + lineDir * params.x; + vec3 p2 = rayEnd * params.y; + vec3 delta = p1 - p2; + float len = length( delta ); + float norm = len / linewidth; + + #ifndef USE_DASH + + #ifdef USE_ALPHA_TO_COVERAGE + + float dnorm = fwidth( norm ); + alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); + + #else + + if ( norm > 0.5 ) { + + discard; + + } + + #endif + + #endif + #else - - if ( abs( vUv.y ) > 1.0 ) { - + + #ifdef USE_ALPHA_TO_COVERAGE + + // artifacts appear on some hardware if a derivative is taken within a conditional float a = vUv.x; float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; float len2 = a * a + b * b; - - if ( len2 > 1.0 ) discard; - - } - + float dlen = fwidth( len2 ); + + if ( abs( vUv.y ) > 1.0 ) { + + alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); + + } + + #else + + if ( abs( vUv.y ) > 1.0 ) { + + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; + + if ( len2 > 1.0 ) discard; + + } + + #endif + #endif + + vec4 diffuseColor = vec4( diffuse, alpha ); + + #include + #include + + gl_FragColor = vec4( diffuseColor.rgb, alpha ); + + #include + #include + #include + #include + + } + ` + }; - #endif - - vec4 diffuseColor = vec4( diffuse, alpha ); - - #include - #include - - gl_FragColor = vec4( diffuseColor.rgb, alpha ); - - #include - #include - #include - #include - - } - ` -}; +} )(); class LineMaterial extends ShaderMaterial { diff --git a/examples/jsm/lines/LineSegments2.js b/examples/jsm/lines/LineSegments2.js index e2a3e2a14b9a24..8f8f1a4c12caee 100644 --- a/examples/jsm/lines/LineSegments2.js +++ b/examples/jsm/lines/LineSegments2.js @@ -13,21 +13,21 @@ import { import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; import { LineMaterial } from '../lines/LineMaterial.js'; -const _start = new Vector3(); -const _end = new Vector3(); +const _start = /* @__PURE__ */ new Vector3(); +const _end = /* @__PURE__ */ new Vector3(); -const _start4 = new Vector4(); -const _end4 = new Vector4(); +const _start4 = /* @__PURE__ */ new Vector4(); +const _end4 = /* @__PURE__ */ new Vector4(); -const _ssOrigin = new Vector4(); -const _ssOrigin3 = new Vector3(); -const _mvMatrix = new Matrix4(); -const _line = new Line3(); -const _closestPoint = new Vector3(); +const _ssOrigin = /* @__PURE__ */ new Vector4(); +const _ssOrigin3 = /* @__PURE__ */ new Vector3(); +const _mvMatrix = /* @__PURE__ */ new Matrix4(); +const _line = /* @__PURE__ */ new Line3(); +const _closestPoint = /* @__PURE__ */ new Vector3(); -const _box = new Box3(); -const _sphere = new Sphere(); -const _clipToWorldVector = new Vector4(); +const _box = /* @__PURE__ */ new Box3(); +const _sphere = /* @__PURE__ */ new Sphere(); +const _clipToWorldVector = /* @__PURE__ */ new Vector4(); let _ray, _lineWidth; diff --git a/examples/jsm/lines/LineSegmentsGeometry.js b/examples/jsm/lines/LineSegmentsGeometry.js index c7cf8774bc9c41..b7518f4bfbe58e 100644 --- a/examples/jsm/lines/LineSegmentsGeometry.js +++ b/examples/jsm/lines/LineSegmentsGeometry.js @@ -9,8 +9,8 @@ import { WireframeGeometry } from 'three'; -const _box = new Box3(); -const _vector = new Vector3(); +const _box = /* @__PURE__ */ new Box3(); +const _vector = /* @__PURE__ */ new Vector3(); class LineSegmentsGeometry extends InstancedBufferGeometry { diff --git a/examples/jsm/lines/Wireframe.js b/examples/jsm/lines/Wireframe.js index cfa65aa6d62dcb..6db8bb81c8a052 100644 --- a/examples/jsm/lines/Wireframe.js +++ b/examples/jsm/lines/Wireframe.js @@ -7,8 +7,8 @@ import { import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; import { LineMaterial } from '../lines/LineMaterial.js'; -const _start = new Vector3(); -const _end = new Vector3(); +const _start = /* @__PURE__ */ new Vector3(); +const _end = /* @__PURE__ */ new Vector3(); class Wireframe extends Mesh { diff --git a/examples/jsm/loaders/FBXLoader.js b/examples/jsm/loaders/FBXLoader.js index dbbcc9821a545a..a847393371509d 100644 --- a/examples/jsm/loaders/FBXLoader.js +++ b/examples/jsm/loaders/FBXLoader.js @@ -3942,8 +3942,8 @@ function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) { } -const tempEuler = new Euler(); -const tempVec = new Vector3(); +const tempEuler = /* @__PURE__ */ new Euler(); +const tempVec = /* @__PURE__ */ new Vector3(); // generate transformation from FBX transform data // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index 8436a3d2d6f61a..6d24550ec8d101 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -2030,7 +2030,7 @@ class GLTFCubicSplineInterpolant extends Interpolant { } -const _q = new Quaternion(); +const _q = /* @__PURE__ */ new Quaternion(); class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { @@ -2419,7 +2419,7 @@ function getImageURIMimeType( uri ) { } -const _identityMatrix = new Matrix4(); +const _identityMatrix = /* @__PURE__ */ new Matrix4(); /* GLTF PARSER */ diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index 3a2c1ca3727631..414c9450877edd 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -79,6 +79,377 @@ let _zstd; class KTX2Loader extends Loader { + /* CONSTANTS */ + + static BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, + }; + + static TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, + }; + + static EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, + }; + + + /* WEB WORKER */ + + static BasisWorker = function () { + + let config; + let transcoderPending; + let BasisModule; + + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + + self.addEventListener( 'message', function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; + + case 'transcode': + transcoderPending.then( () => { + + try { + + const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); + + self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } + + } ); + break; + + } + + } ); + + function init( wasmBinary ) { + + transcoderPending = new Promise( ( resolve ) => { + + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef + + } ).then( () => { + + BasisModule.initializeBasis(); + + if ( BasisModule.KTX2File === undefined ) { + + console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); + + } + + } ); + + } + + function transcode( buffer ) { + + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + + function cleanup() { + + ktx2File.close(); + ktx2File.delete(); + + } + + if ( ! ktx2File.isValid() ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); + + } + + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const layerCount = ktx2File.getLayers() || 1; + const levelCount = ktx2File.getLevels(); + const faceCount = ktx2File.getFaces(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdFlags = ktx2File.getDFDFlags(); + + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + + if ( ! width || ! height || ! levelCount ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid texture' ); + + } + + if ( ! ktx2File.startTranscoding() ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); + + } + + const faces = []; + const buffers = []; + + for ( let face = 0; face < faceCount; face ++ ) { + + const mipmaps = []; + + for ( let mip = 0; mip < levelCount; mip ++ ) { + + const layerMips = []; + + let mipWidth, mipHeight; + + for ( let layer = 0; layer < layerCount; layer ++ ) { + + const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); + + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + + console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + + } + + if ( levelCount > 1 ) { + + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; + + } else { + + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; + + } + + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + + if ( ! status ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + + } + + layerMips.push( dst ); + + } + + const mipData = concat( layerMips ); + + mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); + buffers.push( mipData.buffer ); + + } + + faces.push( { mipmaps, width, height, format: engineFormat } ); + + } + + cleanup(); + + return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; + + } + + // + + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityETC1S - b.priorityETC1S; + + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityUASTC - b.priorityUASTC; + + } ); + + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + + let transcoderFormat; + let engineFormat; + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + + for ( let i = 0; i < options.length; i ++ ) { + + const opt = options[ i ]; + + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + + return { transcoderFormat, engineFormat }; + + } + + console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); + + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; + + return { transcoderFormat, engineFormat }; + + } + + function isPowerOfTwo( value ) { + + if ( value <= 2 ) return true; + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + + } + + /** Concatenates N byte arrays. */ + function concat( arrays ) { + + if ( arrays.length === 1 ) return arrays[ 0 ]; + + let totalByteLength = 0; + + for ( let i = 0; i < arrays.length; i ++ ) { + + const array = arrays[ i ]; + totalByteLength += array.byteLength; + + } + + const result = new Uint8Array( totalByteLength ); + + let byteOffset = 0; + + for ( let i = 0; i < arrays.length; i ++ ) { + + const array = arrays[ i ]; + result.set( array, byteOffset ); + + byteOffset += array.byteLength; + + } + + return result; + + } + + }; + constructor( manager ) { super( manager ); @@ -339,377 +710,6 @@ class KTX2Loader extends Loader { } -/* CONSTANTS */ - -KTX2Loader.BasisFormat = { - ETC1S: 0, - UASTC_4x4: 1, -}; - -KTX2Loader.TranscoderFormat = { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, -}; - -KTX2Loader.EngineFormat = { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, -}; - - -/* WEB WORKER */ - -KTX2Loader.BasisWorker = function () { - - let config; - let transcoderPending; - let BasisModule; - - const EngineFormat = _EngineFormat; // eslint-disable-line no-undef - const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef - const BasisFormat = _BasisFormat; // eslint-disable-line no-undef - - self.addEventListener( 'message', function ( e ) { - - const message = e.data; - - switch ( message.type ) { - - case 'init': - config = message.config; - init( message.transcoderBinary ); - break; - - case 'transcode': - transcoderPending.then( () => { - - try { - - const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); - - self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); - - } catch ( error ) { - - console.error( error ); - - self.postMessage( { type: 'error', id: message.id, error: error.message } ); - - } - - } ); - break; - - } - - } ); - - function init( wasmBinary ) { - - transcoderPending = new Promise( ( resolve ) => { - - BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; - BASIS( BasisModule ); // eslint-disable-line no-undef - - } ).then( () => { - - BasisModule.initializeBasis(); - - if ( BasisModule.KTX2File === undefined ) { - - console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); - - } - - } ); - - } - - function transcode( buffer ) { - - const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); - - function cleanup() { - - ktx2File.close(); - ktx2File.delete(); - - } - - if ( ! ktx2File.isValid() ) { - - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); - - } - - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; - const width = ktx2File.getWidth(); - const height = ktx2File.getHeight(); - const layerCount = ktx2File.getLayers() || 1; - const levelCount = ktx2File.getLevels(); - const faceCount = ktx2File.getFaces(); - const hasAlpha = ktx2File.getHasAlpha(); - const dfdFlags = ktx2File.getDFDFlags(); - - const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); - - if ( ! width || ! height || ! levelCount ) { - - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid texture' ); - - } - - if ( ! ktx2File.startTranscoding() ) { - - cleanup(); - throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); - - } - - const faces = []; - const buffers = []; - - for ( let face = 0; face < faceCount; face ++ ) { - - const mipmaps = []; - - for ( let mip = 0; mip < levelCount; mip ++ ) { - - const layerMips = []; - - let mipWidth, mipHeight; - - for ( let layer = 0; layer < layerCount; layer ++ ) { - - const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); - - if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { - - console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); - - } - - if ( levelCount > 1 ) { - - mipWidth = levelInfo.origWidth; - mipHeight = levelInfo.origHeight; - - } else { - - // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with - // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. - // See mrdoob/three.js#25908. - mipWidth = levelInfo.width; - mipHeight = levelInfo.height; - - } - - const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); - const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); - - if ( ! status ) { - - cleanup(); - throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); - - } - - layerMips.push( dst ); - - } - - const mipData = concat( layerMips ); - - mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); - buffers.push( mipData.buffer ); - - } - - faces.push( { mipmaps, width, height, format: engineFormat } ); - - } - - cleanup(); - - return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; - - } - - // - - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [ BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], - engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], - engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], - engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], - engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1 ], - engineFormat: [ EngineFormat.RGB_ETC1_Format ], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], - engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ]; - - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityETC1S - b.priorityETC1S; - - } ); - const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityUASTC - b.priorityUASTC; - - } ); - - function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { - - let transcoderFormat; - let engineFormat; - - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; - - for ( let i = 0; i < options.length; i ++ ) { - - const opt = options[ i ]; - - if ( ! config[ opt.if ] ) continue; - if ( ! opt.basisFormat.includes( basisFormat ) ) continue; - if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; - if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; - - transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; - engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; - - return { transcoderFormat, engineFormat }; - - } - - console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); - - transcoderFormat = TranscoderFormat.RGBA32; - engineFormat = EngineFormat.RGBAFormat; - - return { transcoderFormat, engineFormat }; - - } - - function isPowerOfTwo( value ) { - - if ( value <= 2 ) return true; - - return ( value & ( value - 1 ) ) === 0 && value !== 0; - - } - - /** Concatenates N byte arrays. */ - function concat( arrays ) { - - if ( arrays.length === 1 ) return arrays[ 0 ]; - - let totalByteLength = 0; - - for ( let i = 0; i < arrays.length; i ++ ) { - - const array = arrays[ i ]; - totalByteLength += array.byteLength; - - } - - const result = new Uint8Array( totalByteLength ); - - let byteOffset = 0; - - for ( let i = 0; i < arrays.length; i ++ ) { - - const array = arrays[ i ]; - result.set( array, byteOffset ); - - byteOffset += array.byteLength; - - } - - return result; - - } - -}; - // // Parsing for non-Basis textures. These textures are may have supercompression // like Zstd, but they do not require transcoding. diff --git a/examples/jsm/loaders/LDrawLoader.js b/examples/jsm/loaders/LDrawLoader.js index 14ca28db63322c..793812d2af3c26 100644 --- a/examples/jsm/loaders/LDrawLoader.js +++ b/examples/jsm/loaders/LDrawLoader.js @@ -42,8 +42,8 @@ const MAIN_EDGE_COLOUR_CODE = '24'; const COLOR_SPACE_LDRAW = SRGBColorSpace; -const _tempVec0 = new Vector3(); -const _tempVec1 = new Vector3(); +const _tempVec0 = /* @__PURE__ */ new Vector3(); +const _tempVec1 = /* @__PURE__ */ new Vector3(); class LDrawConditionalLineMaterial extends ShaderMaterial { @@ -205,7 +205,7 @@ function generateFaceNormals( faces ) { } -const _ray = new Ray(); +const _ray = /* @__PURE__ */ new Ray(); function smoothNormals( faces, lineSegments, checkSubSegments = false ) { // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because diff --git a/examples/jsm/loaders/OBJLoader.js b/examples/jsm/loaders/OBJLoader.js index 7792458abce807..f3e79ac1e276da 100644 --- a/examples/jsm/loaders/OBJLoader.js +++ b/examples/jsm/loaders/OBJLoader.js @@ -25,14 +25,14 @@ const _material_use_pattern = /^usemtl /; const _map_use_pattern = /^usemap /; const _face_vertex_data_separator_pattern = /\s+/; -const _vA = new Vector3(); -const _vB = new Vector3(); -const _vC = new Vector3(); +const _vA = /* @__PURE__ */ new Vector3(); +const _vB = /* @__PURE__ */ new Vector3(); +const _vC = /* @__PURE__ */ new Vector3(); -const _ab = new Vector3(); -const _cb = new Vector3(); +const _ab = /* @__PURE__ */ new Vector3(); +const _cb = /* @__PURE__ */ new Vector3(); -const _color = new Color(); +const _color = /* @__PURE__ */ new Color(); function ParserState() { diff --git a/examples/jsm/loaders/PLYLoader.js b/examples/jsm/loaders/PLYLoader.js index 3834bfbfd36984..fd4c732f0a66f2 100644 --- a/examples/jsm/loaders/PLYLoader.js +++ b/examples/jsm/loaders/PLYLoader.js @@ -42,7 +42,7 @@ import { * */ -const _color = new Color(); +const _color = /* @__PURE__ */ new Color(); class PLYLoader extends Loader { diff --git a/examples/jsm/loaders/lwo/IFFParser.js b/examples/jsm/loaders/lwo/IFFParser.js index f083043a3f6988..ec141c14828701 100644 --- a/examples/jsm/loaders/lwo/IFFParser.js +++ b/examples/jsm/loaders/lwo/IFFParser.js @@ -35,18 +35,16 @@ import { LWO2Parser } from './LWO2Parser.js'; import { LWO3Parser } from './LWO3Parser.js'; -function IFFParser( ) { +class IFFParser { - this.debugger = new Debugger(); - // this.debugger.enable(); // un-comment to log IFF hierarchy. + constructor() { -} - -IFFParser.prototype = { + this.debugger = new Debugger(); + // this.debugger.enable(); // un-comment to log IFF hierarchy. - constructor: IFFParser, + } - parse: function ( buffer ) { + parse( buffer ) { this.reader = new DataViewReader( buffer ); @@ -82,7 +80,7 @@ IFFParser.prototype = { return this.tree; - }, + } parseTopForm() { @@ -120,13 +118,11 @@ IFFParser.prototype = { return; - }, - + } /// // FORM PARSING METHODS /// - // Forms are organisational and can contain any number of sub chunks and sub forms // FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * } parseForm( length ) { @@ -137,7 +133,6 @@ IFFParser.prototype = { // SKIPPED FORMS // if skipForm( length ) is called, the entire form and any sub forms and chunks are skipped - case 'ISEQ': // Image sequence case 'ANIM': // plug in animation case 'STCC': // Color-cycling Still @@ -199,8 +194,7 @@ IFFParser.prototype = { this.parseEnvelope( length ); break; - // CLIP FORM AND SUB FORMS - + // CLIP FORM AND SUB FORMS case 'CLIP': if ( this.tree.format === 'LWO2' ) { @@ -226,14 +220,12 @@ IFFParser.prototype = { }; break; - // Not in spec, used by texture nodes - + // Not in spec, used by texture nodes case 'IMST': this.parseImageStateForm( length ); break; - // SURF FORM AND SUB FORMS - + // SURF FORM AND SUB FORMS case 'SURF': this.parseSurfaceForm( length ); break; @@ -269,8 +261,7 @@ IFFParser.prototype = { this.parseEntryForm( length ); break; - // Image Map Layer - + // Image Map Layer case 'IMAP': this.parseImageMap( length ); break; @@ -279,8 +270,7 @@ IFFParser.prototype = { this.parseXVAL( 'amplitude', length ); break; - //Texture Mapping Form - + //Texture Mapping Form case 'TMAP': this.setupForm( 'textureMap', length ); break; @@ -306,7 +296,7 @@ IFFParser.prototype = { this.debugger.nodeID = type; this.debugger.log(); - }, + } setupForm( type, length ) { @@ -331,13 +321,13 @@ IFFParser.prototype = { } - }, + } skipForm( length ) { this.reader.skip( length - 4 ); - }, + } parseUnknownForm( type, length ) { @@ -346,7 +336,7 @@ IFFParser.prototype = { printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 ); this.reader.skip( length - 4 ); - }, + } parseSurfaceForm( length ) { @@ -355,7 +345,7 @@ IFFParser.prototype = { var name = this.reader.getString(); var surface = { - attributes: {}, // LWO2 style non-node attributes will go here + attributes: {}, connections: {}, name: name, inputName: name, @@ -370,14 +360,14 @@ IFFParser.prototype = { this.currentForm = surface; this.currentFormEnd = this.reader.offset + length; - }, + } parseSurfaceLwo2( length ) { var name = this.reader.getString(); var surface = { - attributes: {}, // LWO2 style non-node attributes will go here + attributes: {}, connections: {}, name: name, nodes: {}, @@ -391,7 +381,7 @@ IFFParser.prototype = { this.currentForm = surface; this.currentFormEnd = this.reader.offset + length; - }, + } parseSubNode( length ) { @@ -411,7 +401,7 @@ IFFParser.prototype = { this.currentFormEnd = this.reader.offset + length; - }, + } // collect attributes from all nodes at the top level of a surface parseConnections( length ) { @@ -421,7 +411,7 @@ IFFParser.prototype = { this.currentForm = this.currentSurface.connections; - }, + } // surface node attribute data, e.g. specular, roughness etc parseEntryForm( length ) { @@ -432,7 +422,7 @@ IFFParser.prototype = { this.setupForm( name, length ); - }, + } // parse values from material - doesn't match up to other LWO3 data types // sub form of entry form @@ -462,7 +452,7 @@ IFFParser.prototype = { } - }, + } // holds various data about texture node image state // Data other thanmipMapLevel unknown @@ -472,7 +462,7 @@ IFFParser.prototype = { this.currentForm.mipMapLevel = this.reader.getFloat32(); - }, + } // LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node) parseImageMap( length ) { @@ -488,7 +478,7 @@ IFFParser.prototype = { this.reader.skip( 10 ); // unknown, could be an issue if it contains a VX - }, + } parseTextureNodeAttribute( type ) { @@ -526,19 +516,18 @@ IFFParser.prototype = { this.reader.skip( 2 ); // unknown - }, + } // ENVL forms are currently ignored parseEnvelope( length ) { this.reader.skip( length - 4 ); // skipping entirely for now - }, + } /// // CHUNK PARSING METHODS /// - // clips can either be defined inside a surface node, or at the top // level and they have a different format in each case parseClip( length ) { @@ -570,7 +559,7 @@ IFFParser.prototype = { this.tree.textures.push( texture ); this.currentForm = texture; - }, + } parseClipLwo2( length ) { @@ -602,14 +591,14 @@ IFFParser.prototype = { this.tree.textures.push( texture ); this.currentForm = texture; - }, + } parseImage() { this.reader.skip( 8 ); // unknown this.currentForm.fileName = this.reader.getString(); - }, + } parseXVAL( type, length ) { @@ -620,7 +609,7 @@ IFFParser.prototype = { this.reader.setOffset( endOffset ); // set end offset directly to skip optional envelope - }, + } parseXVAL3( type, length ) { @@ -635,7 +624,7 @@ IFFParser.prototype = { this.reader.setOffset( endOffset ); - }, + } // Tags associated with an object // OTAG { type[ID4], tag-string[S0] } @@ -647,7 +636,7 @@ IFFParser.prototype = { tagString: this.reader.getString() }; - }, + } // Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered. // LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2] @@ -655,8 +644,8 @@ IFFParser.prototype = { var layer = { number: this.reader.getUint16(), - flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden. - pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present + flags: this.reader.getUint16(), + pivot: this.reader.getFloat32Array( 3 ), name: this.reader.getString(), }; @@ -665,10 +654,11 @@ IFFParser.prototype = { var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength + // if we have not reached then end of the layer block, there must be a parent defined this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent - }, + } // VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors // Converting from left to right handed coordinate system: @@ -683,12 +673,11 @@ IFFParser.prototype = { } - }, + } // parse VMAP or VMAD // Associates a set of floating-point vectors with a set of points. // VMAP: { type[ID4], dimension[U2], name[S0], ( vert[VX], value[F4] # dimension ) * } - // VMAD Associates a set of floating-point vectors with the vertices of specific polygons. // Similar to VMAP UVs, but associates with polygon vertices rather than points // to solve to problem of UV seams: VMAD chunks are paired with VMAPs of the same name, @@ -744,7 +733,7 @@ IFFParser.prototype = { } - }, + } parseUVMapping( name, finalOffset, discontinuous ) { @@ -783,7 +772,7 @@ IFFParser.prototype = { } - }, + } parseMorphTargets( name, finalOffset, type ) { @@ -808,7 +797,7 @@ IFFParser.prototype = { type: type, }; - }, + } // A list of polygons for the current layer. // POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * } @@ -847,7 +836,7 @@ IFFParser.prototype = { this.currentLayer.geometry = geometryData; - }, + } // Lists the tag strings that can be associated with polygons by the PTAG chunk. // TAGS { tag-string[S0] * } @@ -855,7 +844,7 @@ IFFParser.prototype = { this.tree.tags = this.reader.getStringArray( length ); - }, + } // Associates tags of a given type with polygons in the most recent POLS chunk. // PTAG { type[ID4], ( poly[VX], tag[U2] ) * } @@ -870,7 +859,7 @@ IFFParser.prototype = { } - }, + } parseMaterialIndices( finalOffset ) { @@ -886,7 +875,7 @@ IFFParser.prototype = { } - }, + } parseUnknownCHUNK( blockID, length ) { @@ -894,33 +883,31 @@ IFFParser.prototype = { // print the chunk plus some bytes padding either side // printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 ); - var data = this.reader.getString( length ); this.currentForm[ blockID ] = data; } -}; +} -function DataViewReader( buffer ) { - this.dv = new DataView( buffer ); - this.offset = 0; - this._textDecoder = new TextDecoder(); - this._bytes = new Uint8Array( buffer ); +class DataViewReader { -} + constructor( buffer ) { -DataViewReader.prototype = { + this.dv = new DataView( buffer ); + this.offset = 0; + this._textDecoder = new TextDecoder(); + this._bytes = new Uint8Array( buffer ); - constructor: DataViewReader, + } - size: function () { + size() { return this.dv.buffer.byteLength; - }, + } setOffset( offset ) { @@ -934,54 +921,54 @@ DataViewReader.prototype = { } - }, + } - endOfFile: function () { + endOfFile() { if ( this.offset >= this.size() ) return true; return false; - }, + } - skip: function ( length ) { + skip( length ) { this.offset += length; - }, + } - getUint8: function () { + getUint8() { var value = this.dv.getUint8( this.offset ); this.offset += 1; return value; - }, + } - getUint16: function () { + getUint16() { var value = this.dv.getUint16( this.offset ); this.offset += 2; return value; - }, + } - getInt32: function () { + getInt32() { var value = this.dv.getInt32( this.offset, false ); this.offset += 4; return value; - }, + } - getUint32: function () { + getUint32() { var value = this.dv.getUint32( this.offset, false ); this.offset += 4; return value; - }, + } - getUint64: function () { + getUint64() { var low, high; @@ -989,17 +976,17 @@ DataViewReader.prototype = { low = this.getUint32(); return high * 0x100000000 + low; - }, + } - getFloat32: function () { + getFloat32() { var value = this.dv.getFloat32( this.offset, false ); this.offset += 4; return value; - }, + } - getFloat32Array: function ( size ) { + getFloat32Array( size ) { var a = []; @@ -1011,17 +998,17 @@ DataViewReader.prototype = { return a; - }, + } - getFloat64: function () { + getFloat64() { var value = this.dv.getFloat64( this.offset, this.littleEndian ); this.offset += 8; return value; - }, + } - getFloat64Array: function ( size ) { + getFloat64Array( size ) { var a = []; @@ -1033,7 +1020,7 @@ DataViewReader.prototype = { return a; - }, + } // get variable-length index data type // VX ::= index[U2] | (index + 0xFF000000)[U4] @@ -1053,16 +1040,16 @@ DataViewReader.prototype = { return firstByte * 256 + this.getUint8(); - }, + } // An ID tag is a sequence of 4 bytes containing 7-bit ASCII values getIDTag() { return this.getString( 4 ); - }, + } - getString: function ( size ) { + getString( size ) { if ( size === 0 ) return; @@ -1095,9 +1082,9 @@ DataViewReader.prototype = { return result; - }, + } - getStringArray: function ( size ) { + getStringArray( size ) { var a = this.getString( size ); a = a.split( '\0' ); @@ -1106,29 +1093,28 @@ DataViewReader.prototype = { } -}; +} + // ************** DEBUGGER ************** -function Debugger( ) { +class Debugger { - this.active = false; - this.depth = 0; - this.formList = []; + constructor() { -} + this.active = false; + this.depth = 0; + this.formList = []; -Debugger.prototype = { - - constructor: Debugger, + } - enable: function () { + enable() { this.active = true; - }, + } - log: function () { + log() { if ( ! this.active ) return; @@ -1169,9 +1155,9 @@ Debugger.prototype = { this.skipped = false; - }, + } - closeForms: function () { + closeForms() { if ( ! this.active ) return; @@ -1189,7 +1175,8 @@ Debugger.prototype = { } -}; +} + // ************** UTILITY FUNCTIONS ************** diff --git a/examples/jsm/materials/MeshGouraudMaterial.js b/examples/jsm/materials/MeshGouraudMaterial.js index 21758cf43cd2d1..1c046ba50736a8 100644 --- a/examples/jsm/materials/MeshGouraudMaterial.js +++ b/examples/jsm/materials/MeshGouraudMaterial.js @@ -9,7 +9,7 @@ import { UniformsUtils, UniformsLib, ShaderMaterial, Color, MultiplyOperation } const GouraudShader = { - uniforms: UniformsUtils.merge( [ + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ UniformsLib.common, UniformsLib.specularmap, UniformsLib.envmap, @@ -19,7 +19,7 @@ const GouraudShader = { UniformsLib.fog, UniformsLib.lights, { - emissive: { value: new Color( 0x000000 ) } + emissive: { value: /* @__PURE__ */ new Color( 0x000000 ) } } ] ), diff --git a/examples/jsm/math/Capsule.js b/examples/jsm/math/Capsule.js index 08f191c136e7e1..a6f6554567f8fe 100644 --- a/examples/jsm/math/Capsule.js +++ b/examples/jsm/math/Capsule.js @@ -2,9 +2,9 @@ import { Vector3 } from 'three'; -const _v1 = new Vector3(); -const _v2 = new Vector3(); -const _v3 = new Vector3(); +const _v1 = /* @__PURE__ */ new Vector3(); +const _v2 = /* @__PURE__ */ new Vector3(); +const _v3 = /* @__PURE__ */ new Vector3(); const EPS = 1e-10; diff --git a/examples/jsm/math/ConvexHull.js b/examples/jsm/math/ConvexHull.js index b1368a4ed8ea1a..46acf2712e8692 100644 --- a/examples/jsm/math/ConvexHull.js +++ b/examples/jsm/math/ConvexHull.js @@ -12,11 +12,11 @@ import { const Visible = 0; const Deleted = 1; -const _v1 = new Vector3(); -const _line3 = new Line3(); -const _plane = new Plane(); -const _closestPoint = new Vector3(); -const _triangle = new Triangle(); +const _v1 = /* @__PURE__ */ new Vector3(); +const _line3 = /* @__PURE__ */ new Line3(); +const _plane = /* @__PURE__ */ new Plane(); +const _closestPoint = /* @__PURE__ */ new Vector3(); +const _triangle = /* @__PURE__ */ new Triangle(); class ConvexHull { diff --git a/examples/jsm/math/MeshSurfaceSampler.js b/examples/jsm/math/MeshSurfaceSampler.js index da9dd93703d548..350c146c209365 100644 --- a/examples/jsm/math/MeshSurfaceSampler.js +++ b/examples/jsm/math/MeshSurfaceSampler.js @@ -15,9 +15,11 @@ import { * - https://stackoverflow.com/a/4322940/1314762 */ -const _face = new Triangle(); -const _color = new Vector3(); -const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2(); +const _face = /* @__PURE__ */ new Triangle(); +const _color = /* @__PURE__ */ new Vector3(); +const _uva = /* @__PURE__ */ new Vector2(); +const _uvb = /* @__PURE__ */ new Vector2(); +const _uvc = /* @__PURE__ */ new Vector2(); class MeshSurfaceSampler { diff --git a/examples/jsm/math/OBB.js b/examples/jsm/math/OBB.js index 29ae1286dc39ba..eb9d979453cf7c 100644 --- a/examples/jsm/math/OBB.js +++ b/examples/jsm/math/OBB.js @@ -11,13 +11,13 @@ import { const a = { c: null, // center - u: [ new Vector3(), new Vector3(), new Vector3() ], // basis vectors + u: [ /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3(), /* @__PURE__ */ /* @__PURE__ */ new Vector3() ], // basis vectors e: [] // half width }; const b = { c: null, // center - u: [ new Vector3(), new Vector3(), new Vector3() ], // basis vectors + u: [ /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3() ], // basis vectors e: [] // half width }; @@ -25,17 +25,17 @@ const R = [[], [], []]; const AbsR = [[], [], []]; const t = []; -const xAxis = new Vector3(); -const yAxis = new Vector3(); -const zAxis = new Vector3(); -const v1 = new Vector3(); -const size = new Vector3(); -const closestPoint = new Vector3(); -const rotationMatrix = new Matrix3(); -const aabb = new Box3(); -const matrix = new Matrix4(); -const inverse = new Matrix4(); -const localRay = new Ray(); +const xAxis = /* @__PURE__ */ new Vector3(); +const yAxis = /* @__PURE__ */ new Vector3(); +const zAxis = /* @__PURE__ */ new Vector3(); +const v1 = /* @__PURE__ */ new Vector3(); +const size = /* @__PURE__ */ new Vector3(); +const closestPoint = /* @__PURE__ */ new Vector3(); +const rotationMatrix = /* @__PURE__ */ new Matrix3(); +const aabb = /* @__PURE__ */ new Box3(); +const matrix = /* @__PURE__ */ new Matrix4(); +const inverse = /* @__PURE__ */ new Matrix4(); +const localRay = /* @__PURE__ */ new Ray(); // OBB @@ -418,6 +418,6 @@ class OBB { } -const obb = new OBB(); +const obb = /* @__PURE__ */ new OBB(); export { OBB }; diff --git a/examples/jsm/math/Octree.js b/examples/jsm/math/Octree.js index fda28705cf8488..b20d2f02b95df2 100644 --- a/examples/jsm/math/Octree.js +++ b/examples/jsm/math/Octree.js @@ -9,13 +9,13 @@ import { import { Capsule } from '../math/Capsule.js'; -const _v1 = new Vector3(); -const _v2 = new Vector3(); -const _plane = new Plane(); -const _line1 = new Line3(); -const _line2 = new Line3(); -const _sphere = new Sphere(); -const _capsule = new Capsule(); +const _v1 = /* @__PURE__ */ new Vector3(); +const _v2 = /* @__PURE__ */ new Vector3(); +const _plane = /* @__PURE__ */ new Plane(); +const _line1 = /* @__PURE__ */ new Line3(); +const _line2 = /* @__PURE__ */ new Line3(); +const _sphere = /* @__PURE__ */ new Sphere(); +const _capsule = /* @__PURE__ */ new Capsule(); class Octree { diff --git a/examples/jsm/misc/ConvexObjectBreaker.js b/examples/jsm/misc/ConvexObjectBreaker.js index 69e2aa3fa7e216..378559b24355a9 100644 --- a/examples/jsm/misc/ConvexObjectBreaker.js +++ b/examples/jsm/misc/ConvexObjectBreaker.js @@ -35,7 +35,7 @@ import { ConvexGeometry } from '../geometries/ConvexGeometry.js'; * */ -const _v1 = new Vector3(); +const _v1 = /* @__PURE__ */ new Vector3(); class ConvexObjectBreaker { diff --git a/examples/jsm/misc/Gyroscope.js b/examples/jsm/misc/Gyroscope.js index 9269c9c819112d..c508eec3f2a800 100644 --- a/examples/jsm/misc/Gyroscope.js +++ b/examples/jsm/misc/Gyroscope.js @@ -4,13 +4,13 @@ import { Vector3 } from 'three'; -const _translationObject = new Vector3(); -const _quaternionObject = new Quaternion(); -const _scaleObject = new Vector3(); +const _translationObject = /* @__PURE__ */ new Vector3(); +const _quaternionObject = /* @__PURE__ */ new Quaternion(); +const _scaleObject = /* @__PURE__ */ new Vector3(); -const _translationWorld = new Vector3(); -const _quaternionWorld = new Quaternion(); -const _scaleWorld = new Vector3(); +const _translationWorld = /* @__PURE__ */ new Vector3(); +const _quaternionWorld = /* @__PURE__ */ new Quaternion(); +const _scaleWorld = /* @__PURE__ */ new Vector3(); class Gyroscope extends Object3D { diff --git a/examples/jsm/modifiers/EdgeSplitModifier.js b/examples/jsm/modifiers/EdgeSplitModifier.js index 4ae83add37ff5e..48210b00bf4ded 100644 --- a/examples/jsm/modifiers/EdgeSplitModifier.js +++ b/examples/jsm/modifiers/EdgeSplitModifier.js @@ -5,9 +5,9 @@ import { } from 'three'; import * as BufferGeometryUtils from '../utils/BufferGeometryUtils.js'; -const _A = new Vector3(); -const _B = new Vector3(); -const _C = new Vector3(); +const _A = /* @__PURE__ */ new Vector3(); +const _B = /* @__PURE__ */ new Vector3(); +const _C = /* @__PURE__ */ new Vector3(); class EdgeSplitModifier { @@ -274,6 +274,4 @@ class EdgeSplitModifier { } - - export { EdgeSplitModifier }; diff --git a/examples/jsm/modifiers/SimplifyModifier.js b/examples/jsm/modifiers/SimplifyModifier.js index a3be03080d4d1b..4544c55c4c238e 100644 --- a/examples/jsm/modifiers/SimplifyModifier.js +++ b/examples/jsm/modifiers/SimplifyModifier.js @@ -15,7 +15,8 @@ import * as BufferGeometryUtils from '../utils/BufferGeometryUtils.js'; * - http://www.melax.com/polychop/ */ -const _cb = new Vector3(), _ab = new Vector3(); +const _cb = /* @__PURE__ */ new Vector3(); +const _ab = /* @__PURE__ */ new Vector3(); class SimplifyModifier { diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index 42436dcee99208..7efd1eec700977 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -17,6 +17,27 @@ import { class Lensflare extends Mesh { + static Geometry = /* @__PURE__ */ ( function () { + + const geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + return geometry; + + } )(); + constructor() { super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); @@ -287,108 +308,87 @@ class Lensflare extends Mesh { class LensflareElement { - constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { - - this.texture = texture; - this.size = size; - this.distance = distance; - this.color = color; - - } - -} - -LensflareElement.Shader = { - - uniforms: { - - 'map': { value: null }, - 'occlusionMap': { value: null }, - 'color': { value: null }, - 'scale': { value: null }, - 'screenPosition': { value: null } - - }, + static Shader = { - vertexShader: /* glsl */` + uniforms: { - precision highp float; + 'map': { value: null }, + 'occlusionMap': { value: null }, + 'color': { value: null }, + 'scale': { value: null }, + 'screenPosition': { value: null } - uniform vec3 screenPosition; - uniform vec2 scale; + }, - uniform sampler2D occlusionMap; + vertexShader: /* glsl */` - attribute vec3 position; - attribute vec2 uv; + precision highp float; - varying vec2 vUV; - varying float vVisibility; + uniform vec3 screenPosition; + uniform vec2 scale; - void main() { + uniform sampler2D occlusionMap; - vUV = uv; + attribute vec3 position; + attribute vec2 uv; - vec2 pos = position.xy; + varying vec2 vUV; + varying float vVisibility; - vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); + void main() { - vVisibility = visibility.r / 9.0; - vVisibility *= 1.0 - visibility.g / 9.0; - vVisibility *= visibility.b / 9.0; + vUV = uv; - gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); + vec2 pos = position.xy; - }`, + vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); - fragmentShader: /* glsl */` + vVisibility = visibility.r / 9.0; + vVisibility *= 1.0 - visibility.g / 9.0; + vVisibility *= visibility.b / 9.0; - precision highp float; + gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); - uniform sampler2D map; - uniform vec3 color; + }`, - varying vec2 vUV; - varying float vVisibility; + fragmentShader: /* glsl */` - void main() { + precision highp float; - vec4 texture = texture2D( map, vUV ); - texture.a *= vVisibility; - gl_FragColor = texture; - gl_FragColor.rgb *= color; + uniform sampler2D map; + uniform vec3 color; - }` + varying vec2 vUV; + varying float vVisibility; -}; + void main() { -Lensflare.Geometry = ( function () { + vec4 texture = texture2D( map, vUV ); + texture.a *= vVisibility; + gl_FragColor = texture; + gl_FragColor.rgb *= color; - const geometry = new BufferGeometry(); + }` - const float32Array = new Float32Array( [ - - 1, - 1, 0, 0, 0, - 1, - 1, 0, 1, 0, - 1, 1, 0, 1, 1, - - 1, 1, 0, 0, 1 - ] ); + }; - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { - geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + this.texture = texture; + this.size = size; + this.distance = distance; + this.color = color; - return geometry; + } -} )(); +} export { Lensflare, LensflareElement }; diff --git a/examples/jsm/objects/Reflector.js b/examples/jsm/objects/Reflector.js index a175403048733a..8e39aa64aedc91 100644 --- a/examples/jsm/objects/Reflector.js +++ b/examples/jsm/objects/Reflector.js @@ -14,6 +14,75 @@ import { class Reflector extends Mesh { + static ReflectorShader = { + + name: 'ReflectorShader', + + uniforms: { + + 'color': { + value: null + }, + + 'tDiffuse': { + value: null + }, + + 'textureMatrix': { + value: null + } + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + #include + #include + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + #include + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + varying vec4 vUv; + + #include + + float blendOverlay( float base, float blend ) { + + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + + } + + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } + + void main() { + + #include + + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + + #include + #include + + }` + }; + constructor( geometry, options = {} ) { super( geometry ); @@ -192,73 +261,4 @@ class Reflector extends Mesh { } -Reflector.ReflectorShader = { - - name: 'ReflectorShader', - - uniforms: { - - 'color': { - value: null - }, - - 'tDiffuse': { - value: null - }, - - 'textureMatrix': { - value: null - } - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - #include - #include - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - #include - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - varying vec4 vUv; - - #include - - float blendOverlay( float base, float blend ) { - - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - - } - - vec3 blendOverlay( vec3 base, vec3 blend ) { - - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - - } - - void main() { - - #include - - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - - #include - #include - - }` -}; - export { Reflector }; diff --git a/examples/jsm/objects/ReflectorForSSRPass.js b/examples/jsm/objects/ReflectorForSSRPass.js index fd5fc0b0fcce6f..3be0ccc48a3be0 100644 --- a/examples/jsm/objects/ReflectorForSSRPass.js +++ b/examples/jsm/objects/ReflectorForSSRPass.js @@ -17,6 +17,103 @@ import { class ReflectorForSSRPass extends Mesh { + static ReflectorShader = { + + defines: { + DISTANCE_ATTENUATION: true, + FRESNEL: true, + }, + + uniforms: { + + color: { value: null }, + tDiffuse: { value: null }, + tDepth: { value: null }, + textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, + maxDistance: { value: 180 }, + opacity: { value: 0.5 }, + fresnelCoe: { value: null }, + virtualCameraNear: { value: null }, + virtualCameraFar: { value: null }, + virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + uniform sampler2D tDepth; + uniform float maxDistance; + uniform float opacity; + uniform float fresnelCoe; + uniform float virtualCameraNear; + uniform float virtualCameraFar; + uniform mat4 virtualCameraProjectionMatrix; + uniform mat4 virtualCameraProjectionMatrixInverse; + uniform mat4 virtualCameraMatrixWorld; + uniform vec2 resolution; + varying vec4 vUv; + #include + float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } + vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } + float getDepth( const in vec2 uv ) { + return texture2D( tDepth, uv ).x; + } + float getViewZ( const in float depth ) { + return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); + } + vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { + vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc + clipPosition *= clipW; //clip + return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view + } + void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + #ifdef useDepthTexture + vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; + uv.x=1.-uv.x; + float depth = texture2DProj( tDepth, vUv ).r; + float viewZ = getViewZ( depth ); + float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; + vec3 viewPosition=getViewPosition( uv, depth, clipW ); + vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; + if(worldPosition.y>maxDistance) discard; + float op=opacity; + #ifdef DISTANCE_ATTENUATION + float ratio=1.-(worldPosition.y/maxDistance); + float attenuation=ratio*ratio; + op=opacity*attenuation; + #endif + #ifdef FRESNEL + op*=fresnelCoe; + #endif + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); + #else + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #endif + } + `, + }; + constructor( geometry, options = {} ) { super( geometry ); @@ -249,101 +346,4 @@ class ReflectorForSSRPass extends Mesh { } -ReflectorForSSRPass.ReflectorShader = { - - defines: { - DISTANCE_ATTENUATION: true, - FRESNEL: true, - }, - - uniforms: { - - color: { value: null }, - tDiffuse: { value: null }, - tDepth: { value: null }, - textureMatrix: { value: new Matrix4() }, - maxDistance: { value: 180 }, - opacity: { value: 0.5 }, - fresnelCoe: { value: null }, - virtualCameraNear: { value: null }, - virtualCameraFar: { value: null }, - virtualCameraProjectionMatrix: { value: new Matrix4() }, - virtualCameraMatrixWorld: { value: new Matrix4() }, - virtualCameraProjectionMatrixInverse: { value: new Matrix4() }, - resolution: { value: new Vector2() }, - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - uniform sampler2D tDepth; - uniform float maxDistance; - uniform float opacity; - uniform float fresnelCoe; - uniform float virtualCameraNear; - uniform float virtualCameraFar; - uniform mat4 virtualCameraProjectionMatrix; - uniform mat4 virtualCameraProjectionMatrixInverse; - uniform mat4 virtualCameraMatrixWorld; - uniform vec2 resolution; - varying vec4 vUv; - #include - float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } - vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } - float getDepth( const in vec2 uv ) { - return texture2D( tDepth, uv ).x; - } - float getViewZ( const in float depth ) { - return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); - } - vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { - vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc - clipPosition *= clipW; //clip - return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view - } - void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - #ifdef useDepthTexture - vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; - uv.x=1.-uv.x; - float depth = texture2DProj( tDepth, vUv ).r; - float viewZ = getViewZ( depth ); - float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; - vec3 viewPosition=getViewPosition( uv, depth, clipW ); - vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; - if(worldPosition.y>maxDistance) discard; - float op=opacity; - #ifdef DISTANCE_ATTENUATION - float ratio=1.-(worldPosition.y/maxDistance); - float attenuation=ratio*ratio; - op=opacity*attenuation; - #endif - #ifdef FRESNEL - op*=fresnelCoe; - #endif - gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); - #else - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #endif - } - `, -}; - export { ReflectorForSSRPass }; diff --git a/examples/jsm/objects/Refractor.js b/examples/jsm/objects/Refractor.js index 5d557a6a86db19..c067838f26b3e7 100644 --- a/examples/jsm/objects/Refractor.js +++ b/examples/jsm/objects/Refractor.js @@ -15,6 +15,68 @@ import { class Refractor extends Mesh { + static RefractorShader = { + + uniforms: { + + 'color': { + value: null + }, + + 'tDiffuse': { + value: null + }, + + 'textureMatrix': { + value: null + } + + }, + + vertexShader: /* glsl */` + + uniform mat4 textureMatrix; + + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + + uniform vec3 color; + uniform sampler2D tDiffuse; + + varying vec4 vUv; + + float blendOverlay( float base, float blend ) { + + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + + } + + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } + + void main() { + + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + + #include + #include + + }` + + }; + constructor( geometry, options = {} ) { super( geometry ); @@ -259,66 +321,4 @@ class Refractor extends Mesh { } -Refractor.RefractorShader = { - - uniforms: { - - 'color': { - value: null - }, - - 'tDiffuse': { - value: null - }, - - 'textureMatrix': { - value: null - } - - }, - - vertexShader: /* glsl */` - - uniform mat4 textureMatrix; - - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - uniform vec3 color; - uniform sampler2D tDiffuse; - - varying vec4 vUv; - - float blendOverlay( float base, float blend ) { - - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - - } - - vec3 blendOverlay( vec3 base, vec3 blend ) { - - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - - } - - void main() { - - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - - #include - #include - - }` - -}; - export { Refractor }; diff --git a/examples/jsm/objects/ShadowMesh.js b/examples/jsm/objects/ShadowMesh.js index d43236a4954dde..c545e4b1930bf1 100644 --- a/examples/jsm/objects/ShadowMesh.js +++ b/examples/jsm/objects/ShadowMesh.js @@ -10,7 +10,7 @@ import { * A shadow Mesh that follows a shadow-casting Mesh in the scene, but is confined to a single plane. */ -const _shadowMatrix = new Matrix4(); +const _shadowMatrix = /* @__PURE__ */ new Matrix4(); class ShadowMesh extends Mesh { diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index 68908bb9c0833b..347f397e015047 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -23,195 +23,195 @@ import { class Sky extends Mesh { - constructor() { - - const shader = Sky.SkyShader; - - const material = new ShaderMaterial( { - name: 'SkyShader', - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, - uniforms: UniformsUtils.clone( shader.uniforms ), - side: BackSide, - depthWrite: false - } ); + static SkyShader = { + + uniforms: { + 'turbidity': { value: 2 }, + 'rayleigh': { value: 1 }, + 'mieCoefficient': { value: 0.005 }, + 'mieDirectionalG': { value: 0.8 }, + 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, + 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } + }, + + vertexShader: /* glsl */` + uniform vec3 sunPosition; + uniform float rayleigh; + uniform float turbidity; + uniform float mieCoefficient; + uniform vec3 up; + + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; + + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; + + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); + + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); + + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; + + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } + + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } + + void main() { + + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - super( new BoxGeometry( 1, 1, 1 ), material ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - this.isSky = true; - - } - -} + vSunDirection = normalize( sunPosition ); -Sky.SkyShader = { + vSunE = sunIntensity( dot( vSunDirection, up ) ); - uniforms: { - 'turbidity': { value: 2 }, - 'rayleigh': { value: 1 }, - 'mieCoefficient': { value: 0.005 }, - 'mieDirectionalG': { value: 0.8 }, - 'sunPosition': { value: new Vector3() }, - 'up': { value: new Vector3( 0, 1, 0 ) } - }, - - vertexShader: /* glsl */` - uniform vec3 sunPosition; - uniform float rayleigh; - uniform float turbidity; - uniform float mieCoefficient; - uniform vec3 up; - - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); - } + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } + }`, - void main() { + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; + uniform float mieDirectionalG; + uniform vec3 up; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; - vSunDirection = normalize( sunPosition ); + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - vSunE = sunIntensity( dot( vSunDirection, up ) ); + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); + } - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; + void main() { - }`, + vec3 direction = normalize( vWorldPosition - cameraPosition ); - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - uniform float mieDirectionalG; - uniform vec3 up; + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; + // in scattering + float cosTheta = dot( direction, vSunDirection ); - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - void main() { + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - vec3 direction = normalize( vWorldPosition - cameraPosition ); + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; + gl_FragColor = vec4( retColor, 1.0 ); - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); + #include + #include - // in scattering - float cosTheta = dot( direction, vSunDirection ); + }` - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; + }; - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; - - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; - - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; + constructor() { - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); + const shader = Sky.SkyShader; - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); + const material = new ShaderMaterial( { + name: 'SkyShader', + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: UniformsUtils.clone( shader.uniforms ), + side: BackSide, + depthWrite: false + } ); - gl_FragColor = vec4( retColor, 1.0 ); + super( new BoxGeometry( 1, 1, 1 ), material ); - #include - #include + this.isSky = true; - }` + } -}; +} export { Sky }; diff --git a/examples/jsm/objects/Water2.js b/examples/jsm/objects/Water2.js index 4989b953334020..04a9453b5a616f 100644 --- a/examples/jsm/objects/Water2.js +++ b/examples/jsm/objects/Water2.js @@ -23,6 +23,150 @@ import { Refractor } from '../objects/Refractor.js'; class Water extends Mesh { + static WaterShader = { + + uniforms: { + + color: { + value: null + }, + + reflectivity: { + value: 0 + }, + + tReflectionMap: { + value: null + }, + + tRefractionMap: { + value: null + }, + + tNormalMap0: { + value: null + }, + + tNormalMap1: { + value: null + }, + + textureMatrix: { + value: null + }, + + config: { + value: /* @__PURE__ */ new Vector4() + } + + }, + + vertexShader: /* glsl */` + + #include + #include + #include + + uniform mat4 textureMatrix; + + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; + + void main() { + + vUv = uv; + vCoord = textureMatrix * vec4( position, 1.0 ); + + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vToEye = cameraPosition - worldPosition.xyz; + + vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex + gl_Position = projectionMatrix * mvPosition; + + #include + #include + + }`, + + fragmentShader: /* glsl */` + + #include + #include + #include + + uniform sampler2D tReflectionMap; + uniform sampler2D tRefractionMap; + uniform sampler2D tNormalMap0; + uniform sampler2D tNormalMap1; + + #ifdef USE_FLOWMAP + uniform sampler2D tFlowMap; + #else + uniform vec2 flowDirection; + #endif + + uniform vec3 color; + uniform float reflectivity; + uniform vec4 config; + + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; + + void main() { + + #include + + float flowMapOffset0 = config.x; + float flowMapOffset1 = config.y; + float halfCycle = config.z; + float scale = config.w; + + vec3 toEye = normalize( vToEye ); + + // determine flow direction + vec2 flow; + #ifdef USE_FLOWMAP + flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; + #else + flow = flowDirection; + #endif + flow.x *= - 1.0; + + // sample normal maps (distort uvs with flowdata) + vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); + vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); + + // linear interpolate to get the final normal color + float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; + vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); + + // calculate normal vector + vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); + + // calculate the fresnel term to blend reflection and refraction maps + float theta = max( dot( toEye, normal ), 0.0 ); + float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); + + // calculate final uv coords + vec3 coord = vCoord.xyz / vCoord.w; + vec2 uv = coord.xy + coord.z * normal.xz * 0.05; + + vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); + vec4 refractColor = texture2D( tRefractionMap, uv ); + + // multiply water color with the mix of both textures + gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); + + #include + #include + #include + + }` + + }; + constructor( geometry, options = {} ) { super( geometry ); @@ -203,156 +347,4 @@ class Water extends Mesh { } -Water.WaterShader = { - - uniforms: { - - 'color': { - type: 'c', - value: null - }, - - 'reflectivity': { - type: 'f', - value: 0 - }, - - 'tReflectionMap': { - type: 't', - value: null - }, - - 'tRefractionMap': { - type: 't', - value: null - }, - - 'tNormalMap0': { - type: 't', - value: null - }, - - 'tNormalMap1': { - type: 't', - value: null - }, - - 'textureMatrix': { - type: 'm4', - value: null - }, - - 'config': { - type: 'v4', - value: new Vector4() - } - - }, - - vertexShader: /* glsl */` - - #include - #include - #include - - uniform mat4 textureMatrix; - - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; - - void main() { - - vUv = uv; - vCoord = textureMatrix * vec4( position, 1.0 ); - - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vToEye = cameraPosition - worldPosition.xyz; - - vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex - gl_Position = projectionMatrix * mvPosition; - - #include - #include - - }`, - - fragmentShader: /* glsl */` - - #include - #include - #include - - uniform sampler2D tReflectionMap; - uniform sampler2D tRefractionMap; - uniform sampler2D tNormalMap0; - uniform sampler2D tNormalMap1; - - #ifdef USE_FLOWMAP - uniform sampler2D tFlowMap; - #else - uniform vec2 flowDirection; - #endif - - uniform vec3 color; - uniform float reflectivity; - uniform vec4 config; - - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; - - void main() { - - #include - - float flowMapOffset0 = config.x; - float flowMapOffset1 = config.y; - float halfCycle = config.z; - float scale = config.w; - - vec3 toEye = normalize( vToEye ); - - // determine flow direction - vec2 flow; - #ifdef USE_FLOWMAP - flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; - #else - flow = flowDirection; - #endif - flow.x *= - 1.0; - - // sample normal maps (distort uvs with flowdata) - vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); - vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); - - // linear interpolate to get the final normal color - float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; - vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); - - // calculate normal vector - vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); - - // calculate the fresnel term to blend reflection and refraction maps - float theta = max( dot( toEye, normal ), 0.0 ); - float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); - - // calculate final uv coords - vec3 coord = vCoord.xyz / vCoord.w; - vec2 uv = coord.xy + coord.z * normal.xz * 0.05; - - vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); - vec4 refractColor = texture2D( tRefractionMap, uv ); - - // multiply water color with the mix of both textures - gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); - - #include - #include - #include - - }` - -}; - export { Water }; diff --git a/examples/jsm/offscreen/jank.js b/examples/jsm/offscreen/jank.js index 2a4f1e78268869..b6a3d68003116d 100644 --- a/examples/jsm/offscreen/jank.js +++ b/examples/jsm/offscreen/jank.js @@ -42,4 +42,5 @@ function jank() { } +export { initJank }; export default initJank; diff --git a/examples/jsm/offscreen/offscreen.js b/examples/jsm/offscreen/offscreen.js index ba69f0a4d5d645..935c23388a393f 100644 --- a/examples/jsm/offscreen/offscreen.js +++ b/examples/jsm/offscreen/offscreen.js @@ -1,8 +1,12 @@ -import init from './scene.js'; +import { init } from './scene.js'; -self.onmessage = function ( message ) { +/* @__PURE__ */ ( () => { - const data = message.data; - init( data.drawingSurface, data.width, data.height, data.pixelRatio, data.path ); + self.onmessage = function ( message ) { -}; + const data = message.data; + init( data.drawingSurface, data.width, data.height, data.pixelRatio, data.path ); + + }; + +} ); diff --git a/examples/jsm/offscreen/scene.js b/examples/jsm/offscreen/scene.js index fa18baafe5d5c6..5f22277aa60ee9 100644 --- a/examples/jsm/offscreen/scene.js +++ b/examples/jsm/offscreen/scene.js @@ -83,4 +83,5 @@ function random() { } +export { init }; export default init; diff --git a/examples/jsm/physics/RapierPhysics.js b/examples/jsm/physics/RapierPhysics.js index 8f55433b606f09..797cb9a640eacf 100644 --- a/examples/jsm/physics/RapierPhysics.js +++ b/examples/jsm/physics/RapierPhysics.js @@ -4,8 +4,8 @@ const RAPIER_PATH = 'https://cdn.skypack.dev/@dimforge/rapier3d-compat@0.11.2'; const frameRate = 60; -const _scale = new Vector3( 1, 1, 1 ); -const ZERO = new Vector3(); +const _scale = /* @__PURE__ */ new Vector3( 1, 1, 1 ); +const ZERO = /* @__PURE__ */ new Vector3(); let RAPIER = null; diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index 14b0554d1bcf88..8bb0f36951a3a4 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -11,6 +11,9 @@ import { ConvolutionShader } from '../shaders/ConvolutionShader.js'; class BloomPass extends Pass { + static blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); + static blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); + constructor( strength = 1, kernelSize = 25, sigma = 4 ) { super(); @@ -166,7 +169,4 @@ const CombineShader = { }; -BloomPass.blurX = new Vector2( 0.001953125, 0.0 ); -BloomPass.blurY = new Vector2( 0.0, 0.001953125 ); - export { BloomPass }; diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index f96a7f46fd9ca3..14a2e14c635887 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -18,6 +18,9 @@ import { CopyShader } from '../shaders/CopyShader.js'; class OutlinePass extends Pass { + static BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); + static BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); + constructor( resolution, scene, camera, selectedObjects ) { super(); @@ -648,7 +651,4 @@ class OutlinePass extends Pass { } -OutlinePass.BlurDirectionX = new Vector2( 1.0, 0.0 ); -OutlinePass.BlurDirectionY = new Vector2( 0.0, 1.0 ); - export { OutlinePass }; diff --git a/examples/jsm/postprocessing/Pass.js b/examples/jsm/postprocessing/Pass.js index d00ad47064ef87..4d001f5692727b 100644 --- a/examples/jsm/postprocessing/Pass.js +++ b/examples/jsm/postprocessing/Pass.js @@ -39,13 +39,13 @@ class Pass { // Helper for passes that need to fill the viewport with a single quad. -const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); +const _camera = /* @__PURE__ */ new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); // https://github.com/mrdoob/three.js/pull/21358 -const _geometry = new BufferGeometry(); -_geometry.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); -_geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); +const _geometry = /* @__PURE__ */ new BufferGeometry(); +/* @__PURE__ */ _geometry.setAttribute( 'position', /* @__PURE__ */ new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); +/* @__PURE__ */ _geometry.setAttribute( 'uv', /* @__PURE__ */ new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); class FullScreenQuad { diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index 09061c5ef0bf21..99517cd2f64090 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -29,6 +29,12 @@ import { CopyShader } from '../shaders/CopyShader.js'; class SAOPass extends Pass { + static OUTPUT = { + 'Default': 0, + 'SAO': 1, + 'Normal': 2 + }; + constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { super(); @@ -327,10 +333,4 @@ class SAOPass extends Pass { } -SAOPass.OUTPUT = { - 'Default': 0, - 'SAO': 1, - 'Normal': 2 -}; - export { SAOPass }; diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index f011e1aac340a8..a5ae94dc29a690 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -32,6 +32,14 @@ import { CopyShader } from '../shaders/CopyShader.js'; class SSAOPass extends Pass { + static OUTPUT = { + 'Default': 0, + 'SSAO': 1, + 'Blur': 2, + 'Depth': 3, + 'Normal': 4 + }; + constructor( scene, camera, width, height, kernelSize = 32 ) { super(); @@ -409,12 +417,4 @@ class SSAOPass extends Pass { } -SSAOPass.OUTPUT = { - 'Default': 0, - 'SSAO': 1, - 'Blur': 2, - 'Depth': 3, - 'Normal': 4 -}; - export { SSAOPass }; diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index a1429c5c4a655c..f762b0e9aaa660 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -23,6 +23,15 @@ import { CopyShader } from '../shaders/CopyShader.js'; class SSRPass extends Pass { + static OUTPUT = { + 'Default': 0, + 'SSR': 1, + 'Beauty': 3, + 'Depth': 4, + 'Normal': 5, + 'Metalness': 7, + }; + constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { super(); @@ -629,13 +638,4 @@ class SSRPass extends Pass { } -SSRPass.OUTPUT = { - 'Default': 0, - 'SSR': 1, - 'Beauty': 3, - 'Depth': 4, - 'Normal': 5, - 'Metalness': 7, -}; - export { SSRPass }; diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index a53754288d9890..8e3f1bdc1662b6 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -24,6 +24,9 @@ import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js */ class UnrealBloomPass extends Pass { + static BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); + static BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); + constructor( resolution, strength, radius, threshold ) { super(); @@ -409,7 +412,4 @@ class UnrealBloomPass extends Pass { } -UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 ); -UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 ); - export { UnrealBloomPass }; diff --git a/examples/jsm/renderers/CSS2DRenderer.js b/examples/jsm/renderers/CSS2DRenderer.js index 6d67931b97ff44..fe78adc5b039f6 100644 --- a/examples/jsm/renderers/CSS2DRenderer.js +++ b/examples/jsm/renderers/CSS2DRenderer.js @@ -54,11 +54,11 @@ class CSS2DObject extends Object3D { // -const _vector = new Vector3(); -const _viewMatrix = new Matrix4(); -const _viewProjectionMatrix = new Matrix4(); -const _a = new Vector3(); -const _b = new Vector3(); +const _vector = /* @__PURE__ */ new Vector3(); +const _viewMatrix = /* @__PURE__ */ new Matrix4(); +const _viewProjectionMatrix = /* @__PURE__ */ new Matrix4(); +const _a = /* @__PURE__ */ new Vector3(); +const _b = /* @__PURE__ */ new Vector3(); class CSS2DRenderer { diff --git a/examples/jsm/renderers/CSS3DRenderer.js b/examples/jsm/renderers/CSS3DRenderer.js index 0dffeeaef4eed4..8473efeb5766bd 100644 --- a/examples/jsm/renderers/CSS3DRenderer.js +++ b/examples/jsm/renderers/CSS3DRenderer.js @@ -9,9 +9,9 @@ import { * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs */ -const _position = new Vector3(); -const _quaternion = new Quaternion(); -const _scale = new Vector3(); +const _position = /* @__PURE__ */ new Vector3(); +const _quaternion = /* @__PURE__ */ new Quaternion(); +const _scale = /* @__PURE__ */ new Vector3(); class CSS3DObject extends Object3D { @@ -82,8 +82,8 @@ class CSS3DSprite extends CSS3DObject { // -const _matrix = new Matrix4(); -const _matrix2 = new Matrix4(); +const _matrix = /* @__PURE__ */ new Matrix4(); +const _matrix2 = /* @__PURE__ */ new Matrix4(); class CSS3DRenderer { diff --git a/examples/jsm/shaders/BokehShader2.js b/examples/jsm/shaders/BokehShader2.js index 80190f8731fbfa..e2c798e6b9e607 100644 --- a/examples/jsm/shaders/BokehShader2.js +++ b/examples/jsm/shaders/BokehShader2.js @@ -43,7 +43,7 @@ const BokehShader = { 'pentagon': { value: 0 }, 'shaderFocus': { value: 1 }, - 'focusCoords': { value: new Vector2() } + 'focusCoords': { value: /* @__PURE__ */ new Vector2() } }, diff --git a/examples/jsm/shaders/ColorCorrectionShader.js b/examples/jsm/shaders/ColorCorrectionShader.js index df93a63ff9ec47..7ee506308148ac 100644 --- a/examples/jsm/shaders/ColorCorrectionShader.js +++ b/examples/jsm/shaders/ColorCorrectionShader.js @@ -11,9 +11,9 @@ const ColorCorrectionShader = { uniforms: { 'tDiffuse': { value: null }, - 'powRGB': { value: new Vector3( 2, 2, 2 ) }, - 'mulRGB': { value: new Vector3( 1, 1, 1 ) }, - 'addRGB': { value: new Vector3( 0, 0, 0 ) } + 'powRGB': { value: /* @__PURE__ */ new Vector3( 2, 2, 2 ) }, + 'mulRGB': { value: /* @__PURE__ */ new Vector3( 1, 1, 1 ) }, + 'addRGB': { value: /* @__PURE__ */ new Vector3( 0, 0, 0 ) } }, diff --git a/examples/jsm/shaders/ColorifyShader.js b/examples/jsm/shaders/ColorifyShader.js index 12bced7ea66b6c..9271bfd7fb6616 100644 --- a/examples/jsm/shaders/ColorifyShader.js +++ b/examples/jsm/shaders/ColorifyShader.js @@ -13,7 +13,7 @@ const ColorifyShader = { uniforms: { 'tDiffuse': { value: null }, - 'color': { value: new Color( 0xffffff ) } + 'color': { value: /* @__PURE__ */ new Color( 0xffffff ) } }, diff --git a/examples/jsm/shaders/ConvolutionShader.js b/examples/jsm/shaders/ConvolutionShader.js index 774b2090eae76d..51f15b40154059 100644 --- a/examples/jsm/shaders/ConvolutionShader.js +++ b/examples/jsm/shaders/ConvolutionShader.js @@ -21,7 +21,7 @@ const ConvolutionShader = { uniforms: { 'tDiffuse': { value: null }, - 'uImageIncrement': { value: new Vector2( 0.001953125, 0.0 ) }, + 'uImageIncrement': { value: /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ) }, 'cKernel': { value: [] } }, diff --git a/examples/jsm/shaders/DepthLimitedBlurShader.js b/examples/jsm/shaders/DepthLimitedBlurShader.js index d8a933483c6f8b..799049df552963 100644 --- a/examples/jsm/shaders/DepthLimitedBlurShader.js +++ b/examples/jsm/shaders/DepthLimitedBlurShader.js @@ -14,8 +14,8 @@ const DepthLimitedBlurShader = { }, uniforms: { 'tDiffuse': { value: null }, - 'size': { value: new Vector2( 512, 512 ) }, - 'sampleUvOffsets': { value: [ new Vector2( 0, 0 ) ] }, + 'size': { value: /* @__PURE__ */ new Vector2( 512, 512 ) }, + 'sampleUvOffsets': { value: [ /* @__PURE__ */ new Vector2( 0, 0 ) ] }, 'sampleWeights': { value: [ 1.0 ] }, 'tDepth': { value: null }, 'cameraNear': { value: 10 }, diff --git a/examples/jsm/shaders/DotScreenShader.js b/examples/jsm/shaders/DotScreenShader.js index 6adac2e54ff25a..cbaf5d3b58aa52 100644 --- a/examples/jsm/shaders/DotScreenShader.js +++ b/examples/jsm/shaders/DotScreenShader.js @@ -15,8 +15,8 @@ const DotScreenShader = { uniforms: { 'tDiffuse': { value: null }, - 'tSize': { value: new Vector2( 256, 256 ) }, - 'center': { value: new Vector2( 0.5, 0.5 ) }, + 'tSize': { value: /* @__PURE__ */ new Vector2( 256, 256 ) }, + 'center': { value: /* @__PURE__ */ new Vector2( 0.5, 0.5 ) }, 'angle': { value: 1.57 }, 'scale': { value: 1.0 } diff --git a/examples/jsm/shaders/FXAAShader.js b/examples/jsm/shaders/FXAAShader.js index 299a792e7340e7..1683ea0aeab493 100644 --- a/examples/jsm/shaders/FXAAShader.js +++ b/examples/jsm/shaders/FXAAShader.js @@ -15,7 +15,7 @@ const FXAAShader = { uniforms: { 'tDiffuse': { value: null }, - 'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) } + 'resolution': { value: /* @__PURE__ */ new Vector2( 1 / 1024, 1 / 512 ) } }, diff --git a/examples/jsm/shaders/FreiChenShader.js b/examples/jsm/shaders/FreiChenShader.js index 2a85f2f938c224..a39eced85e059b 100644 --- a/examples/jsm/shaders/FreiChenShader.js +++ b/examples/jsm/shaders/FreiChenShader.js @@ -14,7 +14,7 @@ const FreiChenShader = { uniforms: { 'tDiffuse': { value: null }, - 'aspect': { value: new Vector2( 512, 512 ) } + 'aspect': { value: /* @__PURE__ */ new Vector2( 512, 512 ) } }, vertexShader: /* glsl */` diff --git a/examples/jsm/shaders/GodRaysShader.js b/examples/jsm/shaders/GodRaysShader.js index 65afbb135e7104..dd8f25865ac3ee 100644 --- a/examples/jsm/shaders/GodRaysShader.js +++ b/examples/jsm/shaders/GodRaysShader.js @@ -83,7 +83,7 @@ const GodRaysGenerateShader = { value: 1.0 }, vSunPositionScreenSpace: { - value: new Vector3() + value: /* @__PURE__ */ new Vector3() } }, @@ -254,7 +254,7 @@ const GodRaysFakeSunShader = { uniforms: { vSunPositionScreenSpace: { - value: new Vector3() + value: /* @__PURE__ */ new Vector3() }, fAspect: { @@ -262,11 +262,11 @@ const GodRaysFakeSunShader = { }, sunColor: { - value: new Color( 0xffee00 ) + value: /* @__PURE__ */ new Color( 0xffee00 ) }, bgColor: { - value: new Color( 0x000000 ) + value: /* @__PURE__ */ new Color( 0x000000 ) } }, diff --git a/examples/jsm/shaders/LuminosityHighPassShader.js b/examples/jsm/shaders/LuminosityHighPassShader.js index 38937db7ad6b39..dcf6750b93db57 100644 --- a/examples/jsm/shaders/LuminosityHighPassShader.js +++ b/examples/jsm/shaders/LuminosityHighPassShader.js @@ -16,7 +16,7 @@ const LuminosityHighPassShader = { 'tDiffuse': { value: null }, 'luminosityThreshold': { value: 1.0 }, 'smoothWidth': { value: 1.0 }, - 'defaultColor': { value: new Color( 0x000000 ) }, + 'defaultColor': { value: /* @__PURE__ */ new Color( 0x000000 ) }, 'defaultOpacity': { value: 0.0 } }, diff --git a/examples/jsm/shaders/MMDToonShader.js b/examples/jsm/shaders/MMDToonShader.js index 7c8a3cff0dd2f9..0b9517f01b1a3e 100644 --- a/examples/jsm/shaders/MMDToonShader.js +++ b/examples/jsm/shaders/MMDToonShader.js @@ -77,7 +77,7 @@ const MMDToonShader = { MATCAP_BLENDING_ADD: true, }, - uniforms: UniformsUtils.merge( [ + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ ShaderLib.toon.uniforms, ShaderLib.phong.uniforms, ShaderLib.matcap.uniforms, diff --git a/examples/jsm/shaders/NormalMapShader.js b/examples/jsm/shaders/NormalMapShader.js index ecc88da6eefdc6..fba74c5e3b4bf9 100644 --- a/examples/jsm/shaders/NormalMapShader.js +++ b/examples/jsm/shaders/NormalMapShader.js @@ -12,8 +12,8 @@ const NormalMapShader = { uniforms: { 'heightMap': { value: null }, - 'resolution': { value: new Vector2( 512, 512 ) }, - 'scale': { value: new Vector2( 1, 1 ) }, + 'resolution': { value: /* @__PURE__ */ new Vector2( 512, 512 ) }, + 'scale': { value: /* @__PURE__ */ new Vector2( 1, 1 ) }, 'height': { value: 0.05 } }, diff --git a/examples/jsm/shaders/SAOShader.js b/examples/jsm/shaders/SAOShader.js index 751865308dcfb4..51248b64e4a8d3 100644 --- a/examples/jsm/shaders/SAOShader.js +++ b/examples/jsm/shaders/SAOShader.js @@ -19,12 +19,12 @@ const SAOShader = { 'tDepth': { value: null }, 'tDiffuse': { value: null }, 'tNormal': { value: null }, - 'size': { value: new Vector2( 512, 512 ) }, + 'size': { value: /* @__PURE__ */ new Vector2( 512, 512 ) }, 'cameraNear': { value: 1 }, 'cameraFar': { value: 100 }, - 'cameraProjectionMatrix': { value: new Matrix4() }, - 'cameraInverseProjectionMatrix': { value: new Matrix4() }, + 'cameraProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, + 'cameraInverseProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, 'scale': { value: 1.0 }, 'intensity': { value: 0.1 }, diff --git a/examples/jsm/shaders/SMAAShader.js b/examples/jsm/shaders/SMAAShader.js index d1bfb47e213df5..8441560e0db16f 100644 --- a/examples/jsm/shaders/SMAAShader.js +++ b/examples/jsm/shaders/SMAAShader.js @@ -19,7 +19,7 @@ const SMAAEdgesShader = { uniforms: { 'tDiffuse': { value: null }, - 'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) } + 'resolution': { value: /* @__PURE__ */ new Vector2( 1 / 1024, 1 / 512 ) } }, @@ -129,7 +129,7 @@ const SMAAWeightsShader = { 'tDiffuse': { value: null }, 'tArea': { value: null }, 'tSearch': { value: null }, - 'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) } + 'resolution': { value: /* @__PURE__ */ new Vector2( 1 / 1024, 1 / 512 ) } }, @@ -373,7 +373,7 @@ const SMAABlendShader = { 'tDiffuse': { value: null }, 'tColor': { value: null }, - 'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) } + 'resolution': { value: /* @__PURE__ */ new Vector2( 1 / 1024, 1 / 512 ) } }, diff --git a/examples/jsm/shaders/SSAOShader.js b/examples/jsm/shaders/SSAOShader.js index 0f8c1e08efdff6..706cea890a805b 100644 --- a/examples/jsm/shaders/SSAOShader.js +++ b/examples/jsm/shaders/SSAOShader.js @@ -26,8 +26,8 @@ const SSAOShader = { 'cameraNear': { value: null }, 'cameraFar': { value: null }, 'resolution': { value: new Vector2() }, - 'cameraProjectionMatrix': { value: new Matrix4() }, - 'cameraInverseProjectionMatrix': { value: new Matrix4() }, + 'cameraProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, + 'cameraInverseProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, 'kernelRadius': { value: 8 }, 'minDistance': { value: 0.005 }, 'maxDistance': { value: 0.05 }, @@ -247,7 +247,7 @@ const SSAOBlurShader = { uniforms: { 'tDiffuse': { value: null }, - 'resolution': { value: new Vector2() } + 'resolution': { value: /* @__PURE__ */ new Vector2() } }, diff --git a/examples/jsm/shaders/SSRShader.js b/examples/jsm/shaders/SSRShader.js index 54455f6743d0d8..844e60b81d9301 100644 --- a/examples/jsm/shaders/SSRShader.js +++ b/examples/jsm/shaders/SSRShader.js @@ -26,9 +26,9 @@ const SSRShader = { 'tDepth': { value: null }, 'cameraNear': { value: null }, 'cameraFar': { value: null }, - 'resolution': { value: new Vector2() }, - 'cameraProjectionMatrix': { value: new Matrix4() }, - 'cameraInverseProjectionMatrix': { value: new Matrix4() }, + 'resolution': { value: /* @__PURE__ */ new Vector2() }, + 'cameraProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, + 'cameraInverseProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, 'opacity': { value: .5 }, 'maxDistance': { value: 180 }, 'cameraRange': { value: 0 }, @@ -303,7 +303,7 @@ const SSRBlurShader = { uniforms: { 'tDiffuse': { value: null }, - 'resolution': { value: new Vector2() }, + 'resolution': { value: /* @__PURE__ */ new Vector2() }, 'opacity': { value: .5 }, }, diff --git a/examples/jsm/shaders/SobelOperatorShader.js b/examples/jsm/shaders/SobelOperatorShader.js index cebcbd2691d96a..a28e2063f5a509 100644 --- a/examples/jsm/shaders/SobelOperatorShader.js +++ b/examples/jsm/shaders/SobelOperatorShader.js @@ -14,7 +14,7 @@ const SobelOperatorShader = { uniforms: { 'tDiffuse': { value: null }, - 'resolution': { value: new Vector2() } + 'resolution': { value: /* @__PURE__ */ new Vector2() } }, diff --git a/examples/jsm/shaders/SubsurfaceScatteringShader.js b/examples/jsm/shaders/SubsurfaceScatteringShader.js index 9f41f316efbafd..ce456f64e5a4cc 100644 --- a/examples/jsm/shaders/SubsurfaceScatteringShader.js +++ b/examples/jsm/shaders/SubsurfaceScatteringShader.js @@ -24,7 +24,7 @@ const meshphong_frag_body = ShaderChunk[ 'meshphong_frag' ].slice( ShaderChunk[ const SubsurfaceScatteringShader = { - uniforms: UniformsUtils.merge( [ + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ ShaderLib[ 'phong' ].uniforms, { 'thicknessMap': { value: null }, diff --git a/examples/jsm/shaders/ToonShader.js b/examples/jsm/shaders/ToonShader.js index 29c85338d72f99..61d1ebecfebd9e 100644 --- a/examples/jsm/shaders/ToonShader.js +++ b/examples/jsm/shaders/ToonShader.js @@ -16,12 +16,12 @@ const ToonShader1 = { uniforms: { - 'uDirLightPos': { value: new Vector3() }, - 'uDirLightColor': { value: new Color( 0xeeeeee ) }, + 'uDirLightPos': { value: /* @__PURE__ */ new Vector3() }, + 'uDirLightColor': { value: /* @__PURE__ */ new Color( 0xeeeeee ) }, - 'uAmbientLightColor': { value: new Color( 0x050505 ) }, + 'uAmbientLightColor': { value: /* @__PURE__ */ new Color( 0x050505 ) }, - 'uBaseColor': { value: new Color( 0xffffff ) } + 'uBaseColor': { value: /* @__PURE__ */ new Color( 0xffffff ) } }, @@ -90,16 +90,16 @@ const ToonShader2 = { uniforms: { - 'uDirLightPos': { value: new Vector3() }, - 'uDirLightColor': { value: new Color( 0xeeeeee ) }, + 'uDirLightPos': { value: /* @__PURE__ */ new Vector3() }, + 'uDirLightColor': { value: /* @__PURE__ */ new Color( 0xeeeeee ) }, - 'uAmbientLightColor': { value: new Color( 0x050505 ) }, + 'uAmbientLightColor': { value: /* @__PURE__ */ new Color( 0x050505 ) }, - 'uBaseColor': { value: new Color( 0xeeeeee ) }, - 'uLineColor1': { value: new Color( 0x808080 ) }, - 'uLineColor2': { value: new Color( 0x000000 ) }, - 'uLineColor3': { value: new Color( 0x000000 ) }, - 'uLineColor4': { value: new Color( 0x000000 ) } + 'uBaseColor': { value: /* @__PURE__ */ new Color( 0xeeeeee ) }, + 'uLineColor1': { value: /* @__PURE__ */ new Color( 0x808080 ) }, + 'uLineColor2': { value: /* @__PURE__ */ new Color( 0x000000 ) }, + 'uLineColor3': { value: /* @__PURE__ */ new Color( 0x000000 ) }, + 'uLineColor4': { value: /* @__PURE__ */ new Color( 0x000000 ) } }, @@ -158,16 +158,16 @@ const ToonShaderHatching = { uniforms: { - 'uDirLightPos': { value: new Vector3() }, - 'uDirLightColor': { value: new Color( 0xeeeeee ) }, + 'uDirLightPos': { value: /* @__PURE__ */ new Vector3() }, + 'uDirLightColor': { value: /* @__PURE__ */ new Color( 0xeeeeee ) }, - 'uAmbientLightColor': { value: new Color( 0x050505 ) }, + 'uAmbientLightColor': { value: /* @__PURE__ */ new Color( 0x050505 ) }, - 'uBaseColor': { value: new Color( 0xffffff ) }, - 'uLineColor1': { value: new Color( 0x000000 ) }, - 'uLineColor2': { value: new Color( 0x000000 ) }, - 'uLineColor3': { value: new Color( 0x000000 ) }, - 'uLineColor4': { value: new Color( 0x000000 ) } + 'uBaseColor': { value: /* @__PURE__ */ new Color( 0xffffff ) }, + 'uLineColor1': { value: /* @__PURE__ */ new Color( 0x000000 ) }, + 'uLineColor2': { value: /* @__PURE__ */ new Color( 0x000000 ) }, + 'uLineColor3': { value: /* @__PURE__ */ new Color( 0x000000 ) }, + 'uLineColor4': { value: /* @__PURE__ */ new Color( 0x000000 ) } }, @@ -254,13 +254,13 @@ const ToonShaderDotted = { uniforms: { - 'uDirLightPos': { value: new Vector3() }, - 'uDirLightColor': { value: new Color( 0xeeeeee ) }, + 'uDirLightPos': { value: /* @__PURE__ */ new Vector3() }, + 'uDirLightColor': { value: /* @__PURE__ */ new Color( 0xeeeeee ) }, - 'uAmbientLightColor': { value: new Color( 0x050505 ) }, + 'uAmbientLightColor': { value: /* @__PURE__ */ new Color( 0x050505 ) }, - 'uBaseColor': { value: new Color( 0xffffff ) }, - 'uLineColor1': { value: new Color( 0x000000 ) } + 'uBaseColor': { value: /* @__PURE__ */ new Color( 0xffffff ) }, + 'uLineColor1': { value: /* @__PURE__ */ new Color( 0x000000 ) } }, diff --git a/examples/jsm/shaders/TriangleBlurShader.js b/examples/jsm/shaders/TriangleBlurShader.js index 50147751f791a6..73dd2fca6d3b53 100644 --- a/examples/jsm/shaders/TriangleBlurShader.js +++ b/examples/jsm/shaders/TriangleBlurShader.js @@ -17,7 +17,7 @@ const TriangleBlurShader = { uniforms: { 'texture': { value: null }, - 'delta': { value: new Vector2( 1, 1 ) } + 'delta': { value: /* @__PURE__ */ new Vector2( 1, 1 ) } }, diff --git a/examples/jsm/shaders/VelocityShader.js b/examples/jsm/shaders/VelocityShader.js index a053d927f6ff53..161955ff6db8d2 100644 --- a/examples/jsm/shaders/VelocityShader.js +++ b/examples/jsm/shaders/VelocityShader.js @@ -10,13 +10,13 @@ import { const VelocityShader = { - uniforms: UniformsUtils.merge( [ + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ UniformsLib.common, UniformsLib.displacementmap, { - modelMatrixPrev: { value: new Matrix4() }, - currentProjectionViewMatrix: { value: new Matrix4() }, - previousProjectionViewMatrix: { value: new Matrix4() } + modelMatrixPrev: { value: /* @__PURE__ */ new Matrix4() }, + currentProjectionViewMatrix: { value: /* @__PURE__ */ new Matrix4() }, + previousProjectionViewMatrix: { value: /* @__PURE__ */ new Matrix4() } } ] ), diff --git a/examples/jsm/shaders/VolumeShader.js b/examples/jsm/shaders/VolumeShader.js index 1ef52c56a651ae..27d391584dada9 100644 --- a/examples/jsm/shaders/VolumeShader.js +++ b/examples/jsm/shaders/VolumeShader.js @@ -12,10 +12,10 @@ import { const VolumeRenderShader1 = { uniforms: { - 'u_size': { value: new Vector3( 1, 1, 1 ) }, + 'u_size': { value: /* @__PURE__ */ new Vector3( 1, 1, 1 ) }, 'u_renderstyle': { value: 0 }, 'u_renderthreshold': { value: 0.5 }, - 'u_clim': { value: new Vector2( 1, 1 ) }, + 'u_clim': { value: /* @__PURE__ */ new Vector2( 1, 1 ) }, 'u_data': { value: null }, 'u_cmdata': { value: null } }, diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index 6856a21b17aa45..b9d116c93ab95c 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -174,6 +174,8 @@ class VRButton { } + static xrSessionIsGranted = false; + static registerSessionGrantedListener() { if ( 'xr' in navigator ) { @@ -194,7 +196,6 @@ class VRButton { } -VRButton.xrSessionIsGranted = false; -VRButton.registerSessionGrantedListener(); +/* @__PURE__ */ VRButton.registerSessionGrantedListener(); export { VRButton }; diff --git a/examples/jsm/webxr/XRHandPrimitiveModel.js b/examples/jsm/webxr/XRHandPrimitiveModel.js index 0cb3fdcad78fb8..91c6cd38148f62 100644 --- a/examples/jsm/webxr/XRHandPrimitiveModel.js +++ b/examples/jsm/webxr/XRHandPrimitiveModel.js @@ -8,8 +8,8 @@ import { Vector3 } from 'three'; -const _matrix = new Matrix4(); -const _vector = new Vector3(); +const _matrix = /* @__PURE__ */ new Matrix4(); +const _vector = /* @__PURE__ */ new Vector3(); class XRHandPrimitiveModel { From 9fe9f6b76d0e1612ea0d7544b9d0f337583297d1 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Fri, 6 Oct 2023 21:49:57 -0500 Subject: [PATCH 02/45] Update MaterialXLoader.js --- examples/jsm/loaders/MaterialXLoader.js | 146 ++++++++++++------------ 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/examples/jsm/loaders/MaterialXLoader.js b/examples/jsm/loaders/MaterialXLoader.js index a7a928c6199c12..4885fd6c3a146d 100644 --- a/examples/jsm/loaders/MaterialXLoader.js +++ b/examples/jsm/loaders/MaterialXLoader.js @@ -42,89 +42,89 @@ class MtlXElement { const MtlXElements = [ // << Math >> - new MtlXElement( 'add', add, [ 'in1', 'in2' ] ), - new MtlXElement( 'subtract', sub, [ 'in1', 'in2' ] ), - new MtlXElement( 'multiply', mul, [ 'in1', 'in2' ] ), - new MtlXElement( 'divide', div, [ 'in1', 'in2' ] ), - new MtlXElement( 'modulo', mod, [ 'in1', 'in2' ] ), - new MtlXElement( 'absval', abs, [ 'in1', 'in2' ] ), - new MtlXElement( 'sign', sign, [ 'in1', 'in2' ] ), - new MtlXElement( 'floor', floor, [ 'in1', 'in2' ] ), - new MtlXElement( 'ceil', ceil, [ 'in1', 'in2' ] ), - new MtlXElement( 'round', round, [ 'in1', 'in2' ] ), - new MtlXElement( 'power', pow, [ 'in1', 'in2' ] ), - new MtlXElement( 'sin', sin, [ 'in' ] ), - new MtlXElement( 'cos', cos, [ 'in' ] ), - new MtlXElement( 'tan', tan, [ 'in' ] ), - new MtlXElement( 'asin', asin, [ 'in' ] ), - new MtlXElement( 'acos', acos, [ 'in' ] ), - new MtlXElement( 'atan2', atan2, [ 'in1', 'in2' ] ), - new MtlXElement( 'sqrt', sqrt, [ 'in' ] ), - //new MtlXElement( 'ln', ... ), - new MtlXElement( 'exp', exp, [ 'in' ] ), - new MtlXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ), - new MtlXElement( 'min', min, [ 'in1', 'in2' ] ), - new MtlXElement( 'max', max, [ 'in1', 'in2' ] ), - new MtlXElement( 'normalize', normalize, [ 'in' ] ), - new MtlXElement( 'magnitude', length, [ 'in1', 'in2' ] ), - new MtlXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ), - new MtlXElement( 'crossproduct', cross, [ 'in' ] ), - //new MtlXElement( 'transformpoint', ... ), - //new MtlXElement( 'transformvector', ... ), - //new MtlXElement( 'transformnormal', ... ), - //new MtlXElement( 'transformmatrix', ... ), - new MtlXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ), - //new MtlXElement( 'transpose', ... ), - //new MtlXElement( 'determinant', ... ), - //new MtlXElement( 'invertmatrix', ... ), - //new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ), - //new MtlXElement( 'rotate3d', ... ), - //new MtlXElement( 'arrayappend', ... ), - //new MtlXElement( 'dot', ... ), + /* @__PURE__ */ new MtlXElement( 'add', add, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'subtract', sub, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'multiply', mul, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'divide', div, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'modulo', mod, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'absval', abs, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'sign', sign, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'floor', floor, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'ceil', ceil, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'round', round, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'power', pow, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'sin', sin, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'cos', cos, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'tan', tan, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'asin', asin, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'acos', acos, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'atan2', atan2, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'sqrt', sqrt, [ 'in' ] ), + // /* @__PURE__ */ new MtlXElement( 'ln', ... ), + /* @__PURE__ */ new MtlXElement( 'exp', exp, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ), + /* @__PURE__ */ new MtlXElement( 'min', min, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'max', max, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'normalize', normalize, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'magnitude', length, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'crossproduct', cross, [ 'in' ] ), + // /* @__PURE__ */ new MtlXElement( 'transformpoint', ... ), + // /* @__PURE__ */ new MtlXElement( 'transformvector', ... ), + // /* @__PURE__ */ new MtlXElement( 'transformnormal', ... ), + // /* @__PURE__ */ new MtlXElement( 'transformmatrix', ... ), + /* @__PURE__ */ new MtlXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ), + // /* @__PURE__ */ new MtlXElement( 'transpose', ... ), + // /* @__PURE__ */ new MtlXElement( 'determinant', ... ), + // /* @__PURE__ */ new MtlXElement( 'invertmatrix', ... ), + // /* @__PURE__ */ new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ), + // /* @__PURE__ */ new MtlXElement( 'rotate3d', ... ), + // /* @__PURE__ */ new MtlXElement( 'arrayappend', ... ), + // /* @__PURE__ */ new MtlXElement( 'dot', ... ), // << Adjustment >> - new MtlXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ), - new MtlXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ), - //new MtlXElement( 'curveadjust', ... ), - //new MtlXElement( 'curvelookup', ... ), - new MtlXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ), - new MtlXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ), - new MtlXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ), + /* @__PURE__ */ new MtlXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ), + // /* @__PURE__ */ new MtlXElement( 'curveadjust', ... ), + // /* @__PURE__ */ new MtlXElement( 'curvelookup', ... ), + /* @__PURE__ */ new MtlXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ), + /* @__PURE__ */ new MtlXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ), + /* @__PURE__ */ new MtlXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ), // << Mix >> - new MtlXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ), + /* @__PURE__ */ new MtlXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ), // << Channel >> - new MtlXElement( 'combine2', vec2, [ 'in1', 'in2' ] ), - new MtlXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ), - new MtlXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ), + /* @__PURE__ */ new MtlXElement( 'combine2', vec2, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ), + /* @__PURE__ */ new MtlXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ), // << Procedural >> - new MtlXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ), - new MtlXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ), - new MtlXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ), - new MtlXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ), - new MtlXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), - new MtlXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), - new MtlXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ), - new MtlXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ), - new MtlXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ), - new MtlXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), - new MtlXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), + /* @__PURE__ */ new MtlXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ), + /* @__PURE__ */ new MtlXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ), + /* @__PURE__ */ new MtlXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ), + /* @__PURE__ */ new MtlXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ), + /* @__PURE__ */ new MtlXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), + /* @__PURE__ */ new MtlXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), + /* @__PURE__ */ new MtlXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ), + /* @__PURE__ */ new MtlXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ), + /* @__PURE__ */ new MtlXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ), + /* @__PURE__ */ new MtlXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), + /* @__PURE__ */ new MtlXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), // << Supplemental >> - //new MtlXElement( 'tiledimage', ... ), - //new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ), - //new MtlXElement( 'ramp4', ... ), - //new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ), - new MtlXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ), - new MtlXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ), - //new MtlXElement( 'hsvadjust', ... ), - new MtlXElement( 'saturate', saturation, [ 'in', 'amount' ] ), - //new MtlXElement( 'extract', ... ), - //new MtlXElement( 'separate2', ... ), - //new MtlXElement( 'separate3', ... ), - //new MtlXElement( 'separate4', ... ) + // /* @__PURE__ */ new MtlXElement( 'tiledimage', ... ), + // /* @__PURE__ */ new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ), + // /* @__PURE__ */ new MtlXElement( 'ramp4', ... ), + // /* @__PURE__ */ new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ), + /* @__PURE__ */ new MtlXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ), + /* @__PURE__ */ new MtlXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ), + // /* @__PURE__ */ new MtlXElement( 'hsvadjust', ... ), + /* @__PURE__ */ new MtlXElement( 'saturate', saturation, [ 'in', 'amount' ] ), + // /* @__PURE__ */ new MtlXElement( 'extract', ... ), + // /* @__PURE__ */ new MtlXElement( 'separate2', ... ), + // /* @__PURE__ */ new MtlXElement( 'separate3', ... ), + // /* @__PURE__ */ new MtlXElement( 'separate4', ... ) ]; From d59337898c643378adbf4ff96ffd6b367004c292 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:11:39 -0500 Subject: [PATCH 03/45] Nits --- examples/jsm/offscreen/offscreen.js | 2 +- examples/jsm/shaders/SSAOShader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jsm/offscreen/offscreen.js b/examples/jsm/offscreen/offscreen.js index 935c23388a393f..de843e1193f7c4 100644 --- a/examples/jsm/offscreen/offscreen.js +++ b/examples/jsm/offscreen/offscreen.js @@ -9,4 +9,4 @@ import { init } from './scene.js'; }; -} ); +} )(); diff --git a/examples/jsm/shaders/SSAOShader.js b/examples/jsm/shaders/SSAOShader.js index 706cea890a805b..24a002d61d4160 100644 --- a/examples/jsm/shaders/SSAOShader.js +++ b/examples/jsm/shaders/SSAOShader.js @@ -25,7 +25,7 @@ const SSAOShader = { 'kernel': { value: null }, 'cameraNear': { value: null }, 'cameraFar': { value: null }, - 'resolution': { value: new Vector2() }, + 'resolution': { value: /* @__PURE__ */ new Vector2() }, 'cameraProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, 'cameraInverseProjectionMatrix': { value: /* @__PURE__ */ new Matrix4() }, 'kernelRadius': { value: 8 }, From 71e5eb981e0e2eb6a358f76a2492f48365562763 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:48:53 -0500 Subject: [PATCH 04/45] CurveModifier, XR --- .../jsm/libs/motion-controllers.module.js | 749 ++++++++++-------- examples/jsm/modifiers/CurveModifier.js | 2 +- .../jsm/shaders/SubsurfaceScatteringShader.js | 4 +- 3 files changed, 431 insertions(+), 324 deletions(-) diff --git a/examples/jsm/libs/motion-controllers.module.js b/examples/jsm/libs/motion-controllers.module.js index 9b2caaee6d1c4c..a0a0a162b92d0c 100644 --- a/examples/jsm/libs/motion-controllers.module.js +++ b/examples/jsm/libs/motion-controllers.module.js @@ -3,139 +3,177 @@ */ const Constants = { - Handedness: Object.freeze({ - NONE: 'none', - LEFT: 'left', - RIGHT: 'right' - }), - - ComponentState: Object.freeze({ - DEFAULT: 'default', - TOUCHED: 'touched', - PRESSED: 'pressed' - }), - - ComponentProperty: Object.freeze({ - BUTTON: 'button', - X_AXIS: 'xAxis', - Y_AXIS: 'yAxis', - STATE: 'state' - }), - - ComponentType: Object.freeze({ - TRIGGER: 'trigger', - SQUEEZE: 'squeeze', - TOUCHPAD: 'touchpad', - THUMBSTICK: 'thumbstick', - BUTTON: 'button' - }), - - ButtonTouchThreshold: 0.05, - - AxisTouchThreshold: 0.1, - - VisualResponseProperty: Object.freeze({ - TRANSFORM: 'transform', - VISIBILITY: 'visibility' - }) + Handedness: { + NONE: 'none', + LEFT: 'left', + RIGHT: 'right' + }, + + ComponentState: { + DEFAULT: 'default', + TOUCHED: 'touched', + PRESSED: 'pressed' + }, + + ComponentProperty: { + BUTTON: 'button', + X_AXIS: 'xAxis', + Y_AXIS: 'yAxis', + STATE: 'state' + }, + + ComponentType: { + TRIGGER: 'trigger', + SQUEEZE: 'squeeze', + TOUCHPAD: 'touchpad', + THUMBSTICK: 'thumbstick', + BUTTON: 'button' + }, + + ButtonTouchThreshold: 0.05, + + AxisTouchThreshold: 0.1, + + VisualResponseProperty: { + TRANSFORM: 'transform', + VISIBILITY: 'visibility' + } }; /** * @description Static helper function to fetch a JSON file and turn it into a JS object * @param {string} path - Path to JSON file to be fetched */ -async function fetchJsonFile(path) { - const response = await fetch(path); - if (!response.ok) { - throw new Error(response.statusText); - } else { - return response.json(); - } +async function fetchJsonFile( path ) { + + const response = await fetch( path ); + if ( ! response.ok ) { + + throw new Error( response.statusText ); + + } else { + + return response.json(); + + } + } -async function fetchProfilesList(basePath) { - if (!basePath) { - throw new Error('No basePath supplied'); - } +async function fetchProfilesList( basePath ) { + + if ( ! basePath ) { + + throw new Error( 'No basePath supplied' ); + + } + + const profileListFileName = 'profilesList.json'; + const profilesList = await fetchJsonFile( `${basePath}/${profileListFileName}` ); + return profilesList; - const profileListFileName = 'profilesList.json'; - const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`); - return profilesList; } -async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) { - if (!xrInputSource) { - throw new Error('No xrInputSource supplied'); - } - - if (!basePath) { - throw new Error('No basePath supplied'); - } - - // Get the list of profiles - const supportedProfilesList = await fetchProfilesList(basePath); - - // Find the relative path to the first requested profile that is recognized - let match; - xrInputSource.profiles.some((profileId) => { - const supportedProfile = supportedProfilesList[profileId]; - if (supportedProfile) { - match = { - profileId, - profilePath: `${basePath}/${supportedProfile.path}`, - deprecated: !!supportedProfile.deprecated - }; - } - return !!match; - }); - - if (!match) { - if (!defaultProfile) { - throw new Error('No matching profile name found'); - } - - const supportedProfile = supportedProfilesList[defaultProfile]; - if (!supportedProfile) { - throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`); - } - - match = { - profileId: defaultProfile, - profilePath: `${basePath}/${supportedProfile.path}`, - deprecated: !!supportedProfile.deprecated - }; - } - - const profile = await fetchJsonFile(match.profilePath); - - let assetPath; - if (getAssetPath) { - let layout; - if (xrInputSource.handedness === 'any') { - layout = profile.layouts[Object.keys(profile.layouts)[0]]; - } else { - layout = profile.layouts[xrInputSource.handedness]; - } - if (!layout) { - throw new Error( - `No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}` - ); - } - - if (layout.assetPath) { - assetPath = match.profilePath.replace('profile.json', layout.assetPath); - } - } - - return { profile, assetPath }; +async function fetchProfile( xrInputSource, basePath, defaultProfile = null, getAssetPath = true ) { + + if ( ! xrInputSource ) { + + throw new Error( 'No xrInputSource supplied' ); + + } + + if ( ! basePath ) { + + throw new Error( 'No basePath supplied' ); + + } + + // Get the list of profiles + const supportedProfilesList = await fetchProfilesList( basePath ); + + // Find the relative path to the first requested profile that is recognized + let match; + xrInputSource.profiles.some( ( profileId ) => { + + const supportedProfile = supportedProfilesList[ profileId ]; + if ( supportedProfile ) { + + match = { + profileId, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !! supportedProfile.deprecated + }; + + } + + return !! match; + + } ); + + if ( ! match ) { + + if ( ! defaultProfile ) { + + throw new Error( 'No matching profile name found' ); + + } + + const supportedProfile = supportedProfilesList[ defaultProfile ]; + if ( ! supportedProfile ) { + + throw new Error( `No matching profile name found and default profile "${defaultProfile}" missing.` ); + + } + + match = { + profileId: defaultProfile, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !! supportedProfile.deprecated + }; + + } + + const profile = await fetchJsonFile( match.profilePath ); + + let assetPath; + if ( getAssetPath ) { + + let layout; + if ( xrInputSource.handedness === 'any' ) { + + layout = profile.layouts[ Object.keys( profile.layouts )[ 0 ] ]; + + } else { + + layout = profile.layouts[ xrInputSource.handedness ]; + + } + + if ( ! layout ) { + + throw new Error( + `No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}` + ); + + } + + if ( layout.assetPath ) { + + assetPath = match.profilePath.replace( 'profile.json', layout.assetPath ); + + } + + } + + return { profile, assetPath }; + } /** @constant {Object} */ const defaultComponentValues = { - xAxis: 0, - yAxis: 0, - button: 0, - state: Constants.ComponentState.DEFAULT + xAxis: 0, + yAxis: 0, + button: 0, + state: Constants.ComponentState.DEFAULT }; /** @@ -146,26 +184,30 @@ const defaultComponentValues = { * @param {number} x The original x coordinate in the range -1 to 1 * @param {number} y The original y coordinate in the range -1 to 1 */ -function normalizeAxes(x = 0, y = 0) { - let xAxis = x; - let yAxis = y; - - // Determine if the point is outside the bounds of the circle - // and, if so, place it on the edge of the circle - const hypotenuse = Math.sqrt((x * x) + (y * y)); - if (hypotenuse > 1) { - const theta = Math.atan2(y, x); - xAxis = Math.cos(theta); - yAxis = Math.sin(theta); - } - - // Scale and move the circle so values are in the interpolation range. The circle's origin moves - // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5. - const result = { - normalizedXAxis: (xAxis * 0.5) + 0.5, - normalizedYAxis: (yAxis * 0.5) + 0.5 - }; - return result; +function normalizeAxes( x = 0, y = 0 ) { + + let xAxis = x; + let yAxis = y; + + // Determine if the point is outside the bounds of the circle + // and, if so, place it on the edge of the circle + const hypotenuse = Math.sqrt( ( x * x ) + ( y * y ) ); + if ( hypotenuse > 1 ) { + + const theta = Math.atan2( y, x ); + xAxis = Math.cos( theta ); + yAxis = Math.sin( theta ); + + } + + // Scale and move the circle so values are in the interpolation range. The circle's origin moves + // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5. + const result = { + normalizedXAxis: ( xAxis * 0.5 ) + 0.5, + normalizedYAxis: ( yAxis * 0.5 ) + 0.5 + }; + return result; + } /** @@ -177,23 +219,28 @@ function normalizeAxes(x = 0, y = 0) { * interpolating between the range of motion nodes. */ class VisualResponse { - constructor(visualResponseDescription) { - this.componentProperty = visualResponseDescription.componentProperty; - this.states = visualResponseDescription.states; - this.valueNodeName = visualResponseDescription.valueNodeName; - this.valueNodeProperty = visualResponseDescription.valueNodeProperty; - - if (this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM) { - this.minNodeName = visualResponseDescription.minNodeName; - this.maxNodeName = visualResponseDescription.maxNodeName; - } - - // Initializes the response's current value based on default data - this.value = 0; - this.updateFromComponent(defaultComponentValues); - } - - /** + + constructor( visualResponseDescription ) { + + this.componentProperty = visualResponseDescription.componentProperty; + this.states = visualResponseDescription.states; + this.valueNodeName = visualResponseDescription.valueNodeName; + this.valueNodeProperty = visualResponseDescription.valueNodeProperty; + + if ( this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM ) { + + this.minNodeName = visualResponseDescription.minNodeName; + this.maxNodeName = visualResponseDescription.maxNodeName; + + } + + // Initializes the response's current value based on default data + this.value = 0; + this.updateFromComponent( defaultComponentValues ); + + } + + /** * Computes the visual response's interpolation weight based on component state * @param {Object} componentValues - The component from which to update * @param {number} xAxis - The reported X axis value of the component @@ -201,132 +248,170 @@ class VisualResponse { * @param {number} button - The reported value of the component's button * @param {string} state - The component's active state */ - updateFromComponent({ - xAxis, yAxis, button, state - }) { - const { normalizedXAxis, normalizedYAxis } = normalizeAxes(xAxis, yAxis); - switch (this.componentProperty) { - case Constants.ComponentProperty.X_AXIS: - this.value = (this.states.includes(state)) ? normalizedXAxis : 0.5; - break; - case Constants.ComponentProperty.Y_AXIS: - this.value = (this.states.includes(state)) ? normalizedYAxis : 0.5; - break; - case Constants.ComponentProperty.BUTTON: - this.value = (this.states.includes(state)) ? button : 0; - break; - case Constants.ComponentProperty.STATE: - if (this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY) { - this.value = (this.states.includes(state)); - } else { - this.value = this.states.includes(state) ? 1.0 : 0.0; - } - break; - default: - throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`); - } - } + updateFromComponent( { + xAxis, yAxis, button, state + } ) { + + const { normalizedXAxis, normalizedYAxis } = normalizeAxes( xAxis, yAxis ); + switch ( this.componentProperty ) { + + case Constants.ComponentProperty.X_AXIS: + this.value = ( this.states.includes( state ) ) ? normalizedXAxis : 0.5; + break; + case Constants.ComponentProperty.Y_AXIS: + this.value = ( this.states.includes( state ) ) ? normalizedYAxis : 0.5; + break; + case Constants.ComponentProperty.BUTTON: + this.value = ( this.states.includes( state ) ) ? button : 0; + break; + case Constants.ComponentProperty.STATE: + if ( this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY ) { + + this.value = ( this.states.includes( state ) ); + + } else { + + this.value = this.states.includes( state ) ? 1.0 : 0.0; + + } + + break; + default: + throw new Error( `Unexpected visualResponse componentProperty ${this.componentProperty}` ); + + } + + } + } class Component { - /** + + /** * @param {Object} componentId - Id of the component * @param {Object} componentDescription - Description of the component to be created */ - constructor(componentId, componentDescription) { - if (!componentId - || !componentDescription - || !componentDescription.visualResponses - || !componentDescription.gamepadIndices - || Object.keys(componentDescription.gamepadIndices).length === 0) { - throw new Error('Invalid arguments supplied'); - } - - this.id = componentId; - this.type = componentDescription.type; - this.rootNodeName = componentDescription.rootNodeName; - this.touchPointNodeName = componentDescription.touchPointNodeName; - - // Build all the visual responses for this component - this.visualResponses = {}; - Object.keys(componentDescription.visualResponses).forEach((responseName) => { - const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]); - this.visualResponses[responseName] = visualResponse; - }); - - // Set default values - this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices); - - this.values = { - state: Constants.ComponentState.DEFAULT, - button: (this.gamepadIndices.button !== undefined) ? 0 : undefined, - xAxis: (this.gamepadIndices.xAxis !== undefined) ? 0 : undefined, - yAxis: (this.gamepadIndices.yAxis !== undefined) ? 0 : undefined - }; - } - - get data() { - const data = { id: this.id, ...this.values }; - return data; - } - - /** + constructor( componentId, componentDescription ) { + + if ( ! componentId + || ! componentDescription + || ! componentDescription.visualResponses + || ! componentDescription.gamepadIndices + || Object.keys( componentDescription.gamepadIndices ).length === 0 ) { + + throw new Error( 'Invalid arguments supplied' ); + + } + + this.id = componentId; + this.type = componentDescription.type; + this.rootNodeName = componentDescription.rootNodeName; + this.touchPointNodeName = componentDescription.touchPointNodeName; + + // Build all the visual responses for this component + this.visualResponses = {}; + Object.keys( componentDescription.visualResponses ).forEach( ( responseName ) => { + + const visualResponse = new VisualResponse( componentDescription.visualResponses[ responseName ] ); + this.visualResponses[ responseName ] = visualResponse; + + } ); + + // Set default values + this.gamepadIndices = Object.assign( {}, componentDescription.gamepadIndices ); + + this.values = { + state: Constants.ComponentState.DEFAULT, + button: ( this.gamepadIndices.button !== undefined ) ? 0 : undefined, + xAxis: ( this.gamepadIndices.xAxis !== undefined ) ? 0 : undefined, + yAxis: ( this.gamepadIndices.yAxis !== undefined ) ? 0 : undefined + }; + + } + + get data() { + + const data = { id: this.id, ...this.values }; + return data; + + } + + /** * @description Poll for updated data based on current gamepad state * @param {Object} gamepad - The gamepad object from which the component data should be polled */ - updateFromGamepad(gamepad) { - // Set the state to default before processing other data sources - this.values.state = Constants.ComponentState.DEFAULT; - - // Get and normalize button - if (this.gamepadIndices.button !== undefined - && gamepad.buttons.length > this.gamepadIndices.button) { - const gamepadButton = gamepad.buttons[this.gamepadIndices.button]; - this.values.button = gamepadButton.value; - this.values.button = (this.values.button < 0) ? 0 : this.values.button; - this.values.button = (this.values.button > 1) ? 1 : this.values.button; - - // Set the state based on the button - if (gamepadButton.pressed || this.values.button === 1) { - this.values.state = Constants.ComponentState.PRESSED; - } else if (gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold) { - this.values.state = Constants.ComponentState.TOUCHED; - } - } - - // Get and normalize x axis value - if (this.gamepadIndices.xAxis !== undefined - && gamepad.axes.length > this.gamepadIndices.xAxis) { - this.values.xAxis = gamepad.axes[this.gamepadIndices.xAxis]; - this.values.xAxis = (this.values.xAxis < -1) ? -1 : this.values.xAxis; - this.values.xAxis = (this.values.xAxis > 1) ? 1 : this.values.xAxis; - - // If the state is still default, check if the xAxis makes it touched - if (this.values.state === Constants.ComponentState.DEFAULT - && Math.abs(this.values.xAxis) > Constants.AxisTouchThreshold) { - this.values.state = Constants.ComponentState.TOUCHED; - } - } - - // Get and normalize Y axis value - if (this.gamepadIndices.yAxis !== undefined - && gamepad.axes.length > this.gamepadIndices.yAxis) { - this.values.yAxis = gamepad.axes[this.gamepadIndices.yAxis]; - this.values.yAxis = (this.values.yAxis < -1) ? -1 : this.values.yAxis; - this.values.yAxis = (this.values.yAxis > 1) ? 1 : this.values.yAxis; - - // If the state is still default, check if the yAxis makes it touched - if (this.values.state === Constants.ComponentState.DEFAULT - && Math.abs(this.values.yAxis) > Constants.AxisTouchThreshold) { - this.values.state = Constants.ComponentState.TOUCHED; - } - } - - // Update the visual response weights based on the current component data - Object.values(this.visualResponses).forEach((visualResponse) => { - visualResponse.updateFromComponent(this.values); - }); - } + updateFromGamepad( gamepad ) { + + // Set the state to default before processing other data sources + this.values.state = Constants.ComponentState.DEFAULT; + + // Get and normalize button + if ( this.gamepadIndices.button !== undefined + && gamepad.buttons.length > this.gamepadIndices.button ) { + + const gamepadButton = gamepad.buttons[ this.gamepadIndices.button ]; + this.values.button = gamepadButton.value; + this.values.button = ( this.values.button < 0 ) ? 0 : this.values.button; + this.values.button = ( this.values.button > 1 ) ? 1 : this.values.button; + + // Set the state based on the button + if ( gamepadButton.pressed || this.values.button === 1 ) { + + this.values.state = Constants.ComponentState.PRESSED; + + } else if ( gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold ) { + + this.values.state = Constants.ComponentState.TOUCHED; + + } + + } + + // Get and normalize x axis value + if ( this.gamepadIndices.xAxis !== undefined + && gamepad.axes.length > this.gamepadIndices.xAxis ) { + + this.values.xAxis = gamepad.axes[ this.gamepadIndices.xAxis ]; + this.values.xAxis = ( this.values.xAxis < - 1 ) ? - 1 : this.values.xAxis; + this.values.xAxis = ( this.values.xAxis > 1 ) ? 1 : this.values.xAxis; + + // If the state is still default, check if the xAxis makes it touched + if ( this.values.state === Constants.ComponentState.DEFAULT + && Math.abs( this.values.xAxis ) > Constants.AxisTouchThreshold ) { + + this.values.state = Constants.ComponentState.TOUCHED; + + } + + } + + // Get and normalize Y axis value + if ( this.gamepadIndices.yAxis !== undefined + && gamepad.axes.length > this.gamepadIndices.yAxis ) { + + this.values.yAxis = gamepad.axes[ this.gamepadIndices.yAxis ]; + this.values.yAxis = ( this.values.yAxis < - 1 ) ? - 1 : this.values.yAxis; + this.values.yAxis = ( this.values.yAxis > 1 ) ? 1 : this.values.yAxis; + + // If the state is still default, check if the yAxis makes it touched + if ( this.values.state === Constants.ComponentState.DEFAULT + && Math.abs( this.values.yAxis ) > Constants.AxisTouchThreshold ) { + + this.values.state = Constants.ComponentState.TOUCHED; + + } + + } + + // Update the visual response weights based on the current component data + Object.values( this.visualResponses ).forEach( ( visualResponse ) => { + + visualResponse.updateFromComponent( this.values ); + + } ); + + } + } /** @@ -335,63 +420,85 @@ class Component { * @author Nell Waliczek / https://github.com/NellWaliczek */ class MotionController { - /** + + /** * @param {Object} xrInputSource - The XRInputSource to build the MotionController around * @param {Object} profile - The best matched profile description for the supplied xrInputSource * @param {Object} assetUrl */ - constructor(xrInputSource, profile, assetUrl) { - if (!xrInputSource) { - throw new Error('No xrInputSource supplied'); - } - - if (!profile) { - throw new Error('No profile supplied'); - } - - this.xrInputSource = xrInputSource; - this.assetUrl = assetUrl; - this.id = profile.profileId; - - // Build child components as described in the profile description - this.layoutDescription = profile.layouts[xrInputSource.handedness]; - this.components = {}; - Object.keys(this.layoutDescription.components).forEach((componentId) => { - const componentDescription = this.layoutDescription.components[componentId]; - this.components[componentId] = new Component(componentId, componentDescription); - }); - - // Initialize components based on current gamepad state - this.updateFromGamepad(); - } - - get gripSpace() { - return this.xrInputSource.gripSpace; - } - - get targetRaySpace() { - return this.xrInputSource.targetRaySpace; - } - - /** + constructor( xrInputSource, profile, assetUrl ) { + + if ( ! xrInputSource ) { + + throw new Error( 'No xrInputSource supplied' ); + + } + + if ( ! profile ) { + + throw new Error( 'No profile supplied' ); + + } + + this.xrInputSource = xrInputSource; + this.assetUrl = assetUrl; + this.id = profile.profileId; + + // Build child components as described in the profile description + this.layoutDescription = profile.layouts[ xrInputSource.handedness ]; + this.components = {}; + Object.keys( this.layoutDescription.components ).forEach( ( componentId ) => { + + const componentDescription = this.layoutDescription.components[ componentId ]; + this.components[ componentId ] = new Component( componentId, componentDescription ); + + } ); + + // Initialize components based on current gamepad state + this.updateFromGamepad(); + + } + + get gripSpace() { + + return this.xrInputSource.gripSpace; + + } + + get targetRaySpace() { + + return this.xrInputSource.targetRaySpace; + + } + + /** * @description Returns a subset of component data for simplified debugging */ - get data() { - const data = []; - Object.values(this.components).forEach((component) => { - data.push(component.data); - }); - return data; - } - - /** + get data() { + + const data = []; + Object.values( this.components ).forEach( ( component ) => { + + data.push( component.data ); + + } ); + return data; + + } + + /** * @description Poll for updated data based on current gamepad state */ - updateFromGamepad() { - Object.values(this.components).forEach((component) => { - component.updateFromGamepad(this.xrInputSource.gamepad); - }); - } + updateFromGamepad() { + + Object.values( this.components ).forEach( ( component ) => { + + component.updateFromGamepad( this.xrInputSource.gamepad ); + + } ); + + } + } export { Constants, MotionController, fetchProfile, fetchProfilesList }; diff --git a/examples/jsm/modifiers/CurveModifier.js b/examples/jsm/modifiers/CurveModifier.js index fc3f49c2761c87..d856d01847402e 100644 --- a/examples/jsm/modifiers/CurveModifier.js +++ b/examples/jsm/modifiers/CurveModifier.js @@ -248,7 +248,7 @@ export class Flow { } } -const matrix = new Matrix4(); +const matrix = /* @__PURE__ */ new Matrix4(); /** * A helper class for creating instanced versions of flow, where the instances are placed on the curve. diff --git a/examples/jsm/shaders/SubsurfaceScatteringShader.js b/examples/jsm/shaders/SubsurfaceScatteringShader.js index ce456f64e5a4cc..b9749196a70095 100644 --- a/examples/jsm/shaders/SubsurfaceScatteringShader.js +++ b/examples/jsm/shaders/SubsurfaceScatteringShader.js @@ -28,7 +28,7 @@ const SubsurfaceScatteringShader = { ShaderLib[ 'phong' ].uniforms, { 'thicknessMap': { value: null }, - 'thicknessColor': { value: new Color( 0xffffff ) }, + 'thicknessColor': { value: /* @__PURE__ */ new Color( 0xffffff ) }, 'thicknessDistortion': { value: 0.1 }, 'thicknessAmbient': { value: 0.0 }, 'thicknessAttenuation': { value: 0.1 }, @@ -67,7 +67,7 @@ const SubsurfaceScatteringShader = { meshphong_frag_body.replace( '#include ', - replaceAll( + /* @__PURE__ */ replaceAll( ShaderChunk[ 'lights_fragment_begin' ], 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', [ From 98a0bd342a9cc64388de5127fde13f4111db6840 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 06:09:13 -0500 Subject: [PATCH 05/45] Undo export changes to offscreen --- examples/jsm/offscreen/jank.js | 1 - examples/jsm/offscreen/offscreen.js | 2 +- examples/jsm/offscreen/scene.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/jsm/offscreen/jank.js b/examples/jsm/offscreen/jank.js index b6a3d68003116d..2a4f1e78268869 100644 --- a/examples/jsm/offscreen/jank.js +++ b/examples/jsm/offscreen/jank.js @@ -42,5 +42,4 @@ function jank() { } -export { initJank }; export default initJank; diff --git a/examples/jsm/offscreen/offscreen.js b/examples/jsm/offscreen/offscreen.js index de843e1193f7c4..e117aaa57a372a 100644 --- a/examples/jsm/offscreen/offscreen.js +++ b/examples/jsm/offscreen/offscreen.js @@ -1,4 +1,4 @@ -import { init } from './scene.js'; +import init from './scene.js'; /* @__PURE__ */ ( () => { diff --git a/examples/jsm/offscreen/scene.js b/examples/jsm/offscreen/scene.js index 5f22277aa60ee9..fa18baafe5d5c6 100644 --- a/examples/jsm/offscreen/scene.js +++ b/examples/jsm/offscreen/scene.js @@ -83,5 +83,4 @@ function random() { } -export { init }; export default init; From b903a8fcec389a6303bae3198937b96dc1b7974e Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:30:47 -0500 Subject: [PATCH 06/45] Addons: remove static keyword from members/methods --- .../jsm/animation/AnimationClipCreator.js | 26 +- examples/jsm/capabilities/WebGL.js | 26 +- examples/jsm/capabilities/WebGPU.js | 10 +- examples/jsm/exporters/DRACOExporter.js | 41 +- examples/jsm/exporters/GLTFExporter.js | 396 ++++----- examples/jsm/lights/LightProbeGenerator.js | 10 +- .../jsm/lights/RectAreaLightUniformsLib.js | 6 +- examples/jsm/loaders/KTX2Loader.js | 823 +++++++++--------- examples/jsm/loaders/SVGLoader.js | 21 +- examples/jsm/math/ColorConverter.js | 10 +- examples/jsm/math/ConvexHull.js | 48 +- examples/jsm/misc/ConvexObjectBreaker.js | 20 +- examples/jsm/nodes/materials/NodeMaterial.js | 8 +- examples/jsm/objects/Lensflare.js | 72 +- examples/jsm/objects/Reflector.js | 142 +-- examples/jsm/objects/ReflectorForSSRPass.js | 198 ++--- examples/jsm/objects/Refractor.js | 128 +-- examples/jsm/objects/Sky.js | 46 +- examples/jsm/objects/Water2.js | 292 ++++--- examples/jsm/postprocessing/BloomPass.js | 10 +- examples/jsm/postprocessing/OutlinePass.js | 10 +- examples/jsm/postprocessing/SAOPass.js | 16 +- examples/jsm/postprocessing/SSAOPass.js | 20 +- examples/jsm/postprocessing/SSRPass.js | 22 +- .../jsm/postprocessing/UnrealBloomPass.js | 10 +- examples/jsm/utils/LDrawUtils.js | 6 +- examples/jsm/webxr/ARButton.js | 6 +- examples/jsm/webxr/VRButton.js | 12 +- examples/jsm/webxr/XRButton.js | 6 +- 29 files changed, 1260 insertions(+), 1181 deletions(-) diff --git a/examples/jsm/animation/AnimationClipCreator.js b/examples/jsm/animation/AnimationClipCreator.js index cf9ee6bb425477..daa243daf44a69 100644 --- a/examples/jsm/animation/AnimationClipCreator.js +++ b/examples/jsm/animation/AnimationClipCreator.js @@ -7,9 +7,9 @@ import { VectorKeyframeTrack } from 'three'; -class AnimationClipCreator { +const AnimationClipCreator = { - static CreateRotationAnimation( period, axis = 'x' ) { + CreateRotationAnimation( period, axis = 'x' ) { const times = [ 0, period ], values = [ 0, 360 ]; @@ -19,9 +19,9 @@ class AnimationClipCreator { return new AnimationClip( null, period, [ track ] ); - } + }, - static CreateScaleAxisAnimation( period, axis = 'x' ) { + CreateScaleAxisAnimation( period, axis = 'x' ) { const times = [ 0, period ], values = [ 0, 1 ]; @@ -31,9 +31,9 @@ class AnimationClipCreator { return new AnimationClip( null, period, [ track ] ); - } + }, - static CreateShakeAnimation( duration, shakeScale ) { + CreateShakeAnimation( duration, shakeScale ) { const times = [], values = [], tmp = new Vector3(); @@ -53,9 +53,9 @@ class AnimationClipCreator { return new AnimationClip( null, duration, [ track ] ); - } + }, - static CreatePulsationAnimation( duration, pulseScale ) { + CreatePulsationAnimation( duration, pulseScale ) { const times = [], values = [], tmp = new Vector3(); @@ -75,9 +75,9 @@ class AnimationClipCreator { return new AnimationClip( null, duration, [ track ] ); - } + }, - static CreateVisibilityAnimation( duration ) { + CreateVisibilityAnimation( duration ) { const times = [ 0, duration / 2, duration ], values = [ true, false, true ]; @@ -87,9 +87,9 @@ class AnimationClipCreator { return new AnimationClip( null, duration, [ track ] ); - } + }, - static CreateMaterialColorAnimation( duration, colors ) { + CreateMaterialColorAnimation( duration, colors ) { const times = [], values = [], timeStep = duration / colors.length; @@ -111,6 +111,6 @@ class AnimationClipCreator { } -} +}; export { AnimationClipCreator }; diff --git a/examples/jsm/capabilities/WebGL.js b/examples/jsm/capabilities/WebGL.js index 2abf2614fccbc0..9ddc7ad454d119 100644 --- a/examples/jsm/capabilities/WebGL.js +++ b/examples/jsm/capabilities/WebGL.js @@ -1,6 +1,6 @@ -class WebGL { +const WebGL = { - static isWebGLAvailable() { + isWebGLAvailable() { try { @@ -13,9 +13,9 @@ class WebGL { } - } + }, - static isWebGL2Available() { + isWebGL2Available() { try { @@ -28,9 +28,9 @@ class WebGL { } - } + }, - static isColorSpaceAvailable( colorSpace ) { + isColorSpaceAvailable( colorSpace ) { try { @@ -45,21 +45,21 @@ class WebGL { } - } + }, - static getWebGLErrorMessage() { + getWebGLErrorMessage() { return this.getErrorMessage( 1 ); - } + }, - static getWebGL2ErrorMessage() { + getWebGL2ErrorMessage() { return this.getErrorMessage( 2 ); - } + }, - static getErrorMessage( version ) { + getErrorMessage( version ) { const names = { 1: 'WebGL', @@ -103,6 +103,6 @@ class WebGL { } -} +}; export default WebGL; diff --git a/examples/jsm/capabilities/WebGPU.js b/examples/jsm/capabilities/WebGPU.js index 3587718e259ccb..01e685766c2c93 100644 --- a/examples/jsm/capabilities/WebGPU.js +++ b/examples/jsm/capabilities/WebGPU.js @@ -18,15 +18,15 @@ if ( navigator.gpu !== undefined ) { } -class WebGPU { +const WebGPU = { - static isAvailable() { + isAvailable() { return isAvailable; - } + }, - static getErrorMessage() { + getErrorMessage() { const message = 'Your browser does not support WebGPU yet'; @@ -48,6 +48,6 @@ class WebGPU { } -} +}; export default WebGPU; diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 7ef4a209f211b9..9de867ac1b76eb 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -18,24 +18,6 @@ import { Color } from 'three'; /* global DracoEncoderModule */ class DRACOExporter { - // Encoder methods - - static MESH_EDGEBREAKER_ENCODING = 1; - static MESH_SEQUENTIAL_ENCODING = 0; - - // Geometry type - - static POINT_CLOUD = 0; - static TRIANGULAR_MESH = 1; - - // Attribute type - - static INVALID = - 1; - static POSITION = 0; - static NORMAL = 1; - static COLOR = 2; - static TEX_COORD = 3; - static GENERIC = 4; parse( object, options = {} ) { @@ -230,6 +212,29 @@ class DRACOExporter { } +/* @__PURE__ */ Object.assign( DRACOExporter, { + + // Encoder methods + + MESH_EDGEBREAKER_ENCODING: 1, + MESH_SEQUENTIAL_ENCODING: 0, + + // Geometry type + + POINT_CLOUD: 0, + TRIANGULAR_MESH: 1, + + // Attribute type + + INVALID: - 1, + POSITION: 0, + NORMAL: 1, + COLOR: 2, + TEX_COORD: 3, + GENERIC: 4 + +} ); + function createVertexColorSRGBArray( attribute ) { // While .drc files do not specify colorspace, the only 'official' tooling diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 8e5fda7dff7cb0..3493156e9de76d 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -65,202 +65,6 @@ const KHR_mesh_quantization_ExtraAttrTypes = { class GLTFExporter { - /** - * Static utility functions - */ - static Utils = { - - insertKeyframe: function ( track, time ) { - - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); - - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - - let index; - - if ( track.times.length === 0 ) { - - times[ 0 ] = time; - - for ( let i = 0; i < valueSize; i ++ ) { - - values[ i ] = 0; - - } - - index = 0; - - } else if ( time < track.times[ 0 ] ) { - - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - - times[ 0 ] = time; - times.set( track.times, 1 ); - - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); - - index = 0; - - } else if ( time > track.times[ track.times.length - 1 ] ) { - - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - - return track.times.length - 1; - - } - - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); - - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); - - index = times.length - 1; - - } else { - - for ( let i = 0; i < track.times.length; i ++ ) { - - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); - - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - - index = i + 1; - - break; - - } - - } - - } - - track.times = times; - track.values = values; - - return index; - - }, - - mergeMorphTargetTracks: function ( clip, root ) { - - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; - - for ( let i = 0; i < sourceTracks.length; ++ i ) { - - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; - - } - - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); - - } - - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); - - } - - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - - if ( targetIndex === undefined ) { - - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - - } - - let mergedTrack; - - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - - mergedTrack = sourceTrack.clone(); - - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; - - } - - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; - - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); - - continue; - - } - - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - - } - - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - - } - - } - - clip.tracks = tracks; - - return clip; - - } - - }; - constructor() { this.pluginCallbacks = []; @@ -3228,4 +3032,204 @@ class GLTFMeshGpuInstancing { } +/* @__PURE__ */ Object.assign( GLTFExporter, { + + /** + * Static utility functions + */ + Utils: { + + insertKeyframe: function ( track, time ) { + + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); + + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + + let index; + + if ( track.times.length === 0 ) { + + times[ 0 ] = time; + + for ( let i = 0; i < valueSize; i ++ ) { + + values[ i ] = 0; + + } + + index = 0; + + } else if ( time < track.times[ 0 ] ) { + + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + + times[ 0 ] = time; + times.set( track.times, 1 ); + + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); + + index = 0; + + } else if ( time > track.times[ track.times.length - 1 ] ) { + + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + + return track.times.length - 1; + + } + + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); + + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); + + index = times.length - 1; + + } else { + + for ( let i = 0; i < track.times.length; i ++ ) { + + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); + + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + + index = i + 1; + + break; + + } + + } + + } + + track.times = times; + track.values = values; + + return index; + + }, + + mergeMorphTargetTracks: function ( clip, root ) { + + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; + + for ( let i = 0; i < sourceTracks.length; ++ i ) { + + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; + + } + + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + + } + + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); + + } + + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + + if ( targetIndex === undefined ) { + + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + + } + + let mergedTrack; + + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + + mergedTrack = sourceTrack.clone(); + + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + + } + + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; + + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); + + continue; + + } + + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + + } + + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + + } + + } + + clip.tracks = tracks; + + return clip; + + } + + } + +} ); + export { GLTFExporter }; diff --git a/examples/jsm/lights/LightProbeGenerator.js b/examples/jsm/lights/LightProbeGenerator.js index 4bd9896c8cd3fd..47a62d8cc1c821 100644 --- a/examples/jsm/lights/LightProbeGenerator.js +++ b/examples/jsm/lights/LightProbeGenerator.js @@ -10,10 +10,10 @@ import { DataUtils } from 'three'; -class LightProbeGenerator { +const LightProbeGenerator = { // https://www.ppsloan.org/publications/StupidSH36.pdf - static fromCubeTexture( cubeTexture ) { + fromCubeTexture( cubeTexture ) { let totalWeight = 0; @@ -124,9 +124,9 @@ class LightProbeGenerator { return new LightProbe( sh ); - } + }, - static fromCubeRenderTarget( renderer, cubeRenderTarget ) { + fromCubeRenderTarget( renderer, cubeRenderTarget ) { // The renderTarget must be set to RGBA in order to make readRenderTargetPixels works let totalWeight = 0; @@ -256,7 +256,7 @@ class LightProbeGenerator { } -} +}; function convertColorToLinear( color, colorSpace ) { diff --git a/examples/jsm/lights/RectAreaLightUniformsLib.js b/examples/jsm/lights/RectAreaLightUniformsLib.js index cf916b31ab251e..4daf7683f61417 100644 --- a/examples/jsm/lights/RectAreaLightUniformsLib.js +++ b/examples/jsm/lights/RectAreaLightUniformsLib.js @@ -28,9 +28,9 @@ import { // by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt // code: https://github.com/selfshadow/ltc_code/ -class RectAreaLightUniformsLib { +const RectAreaLightUniformsLib = { - static init() { + init() { // source: https://github.com/selfshadow/ltc_code/tree/master/fit/results/ltc.js @@ -74,6 +74,6 @@ class RectAreaLightUniformsLib { } -} +}; export { RectAreaLightUniformsLib }; diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index 414c9450877edd..6a2c88939ace86 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -79,636 +79,639 @@ let _zstd; class KTX2Loader extends Loader { - /* CONSTANTS */ - - static BasisFormat = { - ETC1S: 0, - UASTC_4x4: 1, - }; + constructor( manager ) { - static TranscoderFormat = { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, - }; + super( manager ); - static EngineFormat = { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, - }; + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; + this.workerPool = new WorkerPool(); + this.workerSourceURL = ''; + this.workerConfig = null; - /* WEB WORKER */ + if ( typeof MSC_TRANSCODER !== 'undefined' ) { - static BasisWorker = function () { + console.warn( - let config; - let transcoderPending; - let BasisModule; + 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' - const EngineFormat = _EngineFormat; // eslint-disable-line no-undef - const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef - const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + ); - self.addEventListener( 'message', function ( e ) { + } - const message = e.data; + } - switch ( message.type ) { + setTranscoderPath( path ) { - case 'init': - config = message.config; - init( message.transcoderBinary ); - break; + this.transcoderPath = path; - case 'transcode': - transcoderPending.then( () => { + return this; - try { + } - const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); + setWorkerLimit( num ) { - self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); + this.workerPool.setWorkerLimit( num ); - } catch ( error ) { + return this; - console.error( error ); + } - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + detectSupport( renderer ) { - } + if ( renderer.isWebGPURenderer === true ) { - } ); - break; + this.workerConfig = { + astcSupported: renderer.hasFeature( 'texture-compression-astc' ), + etc1Supported: false, + etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), + dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), + bptcSupported: false, + pvrtcSupported: false + }; - } + } else { - } ); + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + }; - function init( wasmBinary ) { + if ( renderer.capabilities.isWebGL2 ) { - transcoderPending = new Promise( ( resolve ) => { + // https://github.com/mrdoob/three.js/pull/22928 + this.workerConfig.etc1Supported = false; - BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; - BASIS( BasisModule ); // eslint-disable-line no-undef + } - } ).then( () => { + } - BasisModule.initializeBasis(); + return this; - if ( BasisModule.KTX2File === undefined ) { + } - console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); + init() { - } + if ( ! this.transcoderPending ) { - } ); + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); - } + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); - function transcode( buffer ) { + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { - const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + const fn = KTX2Loader.BasisWorker.toString(); - function cleanup() { + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); - ktx2File.close(); - ktx2File.delete(); + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; - } + this.workerPool.setWorkerCreator( () => { - if ( ! ktx2File.isValid() ) { + const worker = new Worker( this.workerSourceURL ); + const transcoderBinary = this.transcoderBinary.slice( 0 ); - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); + worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); - } + return worker; - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; - const width = ktx2File.getWidth(); - const height = ktx2File.getHeight(); - const layerCount = ktx2File.getLayers() || 1; - const levelCount = ktx2File.getLevels(); - const faceCount = ktx2File.getFaces(); - const hasAlpha = ktx2File.getHasAlpha(); - const dfdFlags = ktx2File.getDFDFlags(); + } ); - const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + } ); - if ( ! width || ! height || ! levelCount ) { + if ( _activeLoaders > 0 ) { - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid texture' ); + // Each instance loads a transcoder and allocates workers, increasing network and memory cost. - } + console.warn( - if ( ! ktx2File.startTranscoding() ) { + 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' + + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' - cleanup(); - throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); + ); } - const faces = []; - const buffers = []; + _activeLoaders ++; - for ( let face = 0; face < faceCount; face ++ ) { + } - const mipmaps = []; + return this.transcoderPending; - for ( let mip = 0; mip < levelCount; mip ++ ) { + } - const layerMips = []; + load( url, onLoad, onProgress, onError ) { - let mipWidth, mipHeight; + if ( this.workerConfig === null ) { - for ( let layer = 0; layer < layerCount; layer ++ ) { + throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); - const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); + } - if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + const loader = new FileLoader( this.manager ); - console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); - } + loader.load( url, ( buffer ) => { - if ( levelCount > 1 ) { + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { - mipWidth = levelInfo.origWidth; - mipHeight = levelInfo.origHeight; + const cachedTask = _taskCache.get( buffer ); - } else { + return cachedTask.promise.then( onLoad ).catch( onError ); - // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with - // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. - // See mrdoob/three.js#25908. - mipWidth = levelInfo.width; - mipHeight = levelInfo.height; + } - } + this._createTexture( buffer ) + .then( ( texture ) => onLoad ? onLoad( texture ) : null ) + .catch( onError ); - const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); - const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + }, onProgress, onError ); - if ( ! status ) { + } - cleanup(); - throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + _createTextureFrom( transcodeResult, container ) { - } + const { faces, width, height, format, type, error, dfdFlags } = transcodeResult; - layerMips.push( dst ); + if ( type === 'error' ) return Promise.reject( error ); - } + let texture; - const mipData = concat( layerMips ); + if ( container.faceCount === 6 ) { - mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); - buffers.push( mipData.buffer ); + texture = new CompressedCubeTexture( faces, format, UnsignedByteType ); - } + } else { - faces.push( { mipmaps, width, height, format: engineFormat } ); + const mipmaps = faces[ 0 ].mipmaps; - } + texture = container.layerCount > 1 + ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) + : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); - cleanup(); + } - return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; + texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + + texture.needsUpdate = true; + texture.colorSpace = parseColorSpace( container ); + texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); + + return texture; + + } + + /** + * @param {ArrayBuffer} buffer + * @param {object?} config + * @return {Promise} + */ + async _createTexture( buffer, config = {} ) { + + const container = read( new Uint8Array( buffer ) ); + + if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { + + return createRawTexture( container ); } // + const taskConfig = config; + const texturePending = this.init().then( () => { - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [ BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], - engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], - engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], - engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], - engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1 ], - engineFormat: [ EngineFormat.RGB_ETC1_Format ], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], - engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ]; + return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] ); - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + } ).then( ( e ) => this._createTextureFrom( e.data, container ) ); - return a.priorityETC1S - b.priorityETC1S; + // Cache the task result. + _taskCache.set( buffer, { promise: texturePending } ); - } ); - const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + return texturePending; - return a.priorityUASTC - b.priorityUASTC; + } - } ); + dispose() { - function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + this.workerPool.dispose(); + if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); - let transcoderFormat; - let engineFormat; + _activeLoaders --; - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + return this; - for ( let i = 0; i < options.length; i ++ ) { + } - const opt = options[ i ]; +} - if ( ! config[ opt.if ] ) continue; - if ( ! opt.basisFormat.includes( basisFormat ) ) continue; - if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; - if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; +/* @__PURE__ */ Object.assign( KTX2Loader, { - transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; - engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + /* CONSTANTS */ - return { transcoderFormat, engineFormat }; + BasisFormat: { + ETC1S: 0, + UASTC_4x4: 1, + }, - } + TranscoderFormat: { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, + }, - console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); + EngineFormat: { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, + }, - transcoderFormat = TranscoderFormat.RGBA32; - engineFormat = EngineFormat.RGBAFormat; - return { transcoderFormat, engineFormat }; + /* WEB WORKER */ - } + BasisWorker: function () { - function isPowerOfTwo( value ) { + let config; + let transcoderPending; + let BasisModule; - if ( value <= 2 ) return true; + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef - return ( value & ( value - 1 ) ) === 0 && value !== 0; + self.addEventListener( 'message', function ( e ) { - } + const message = e.data; - /** Concatenates N byte arrays. */ - function concat( arrays ) { + switch ( message.type ) { - if ( arrays.length === 1 ) return arrays[ 0 ]; + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; - let totalByteLength = 0; + case 'transcode': + transcoderPending.then( () => { - for ( let i = 0; i < arrays.length; i ++ ) { + try { - const array = arrays[ i ]; - totalByteLength += array.byteLength; + const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); - } + self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); - const result = new Uint8Array( totalByteLength ); + } catch ( error ) { - let byteOffset = 0; + console.error( error ); - for ( let i = 0; i < arrays.length; i ++ ) { + self.postMessage( { type: 'error', id: message.id, error: error.message } ); - const array = arrays[ i ]; - result.set( array, byteOffset ); + } - byteOffset += array.byteLength; + } ); + break; } - return result; - - } + } ); - }; + function init( wasmBinary ) { - constructor( manager ) { + transcoderPending = new Promise( ( resolve ) => { - super( manager ); + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef - this.transcoderPath = ''; - this.transcoderBinary = null; - this.transcoderPending = null; + } ).then( () => { - this.workerPool = new WorkerPool(); - this.workerSourceURL = ''; - this.workerConfig = null; + BasisModule.initializeBasis(); - if ( typeof MSC_TRANSCODER !== 'undefined' ) { + if ( BasisModule.KTX2File === undefined ) { - console.warn( + console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); - 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' - + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' + } - ); + } ); } - } - - setTranscoderPath( path ) { + function transcode( buffer ) { - this.transcoderPath = path; + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); - return this; + function cleanup() { - } + ktx2File.close(); + ktx2File.delete(); - setWorkerLimit( num ) { + } - this.workerPool.setWorkerLimit( num ); + if ( ! ktx2File.isValid() ) { - return this; + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); - } + } - detectSupport( renderer ) { + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const layerCount = ktx2File.getLayers() || 1; + const levelCount = ktx2File.getLevels(); + const faceCount = ktx2File.getFaces(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdFlags = ktx2File.getDFDFlags(); - if ( renderer.isWebGPURenderer === true ) { + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); - this.workerConfig = { - astcSupported: renderer.hasFeature( 'texture-compression-astc' ), - etc1Supported: false, - etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), - dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), - bptcSupported: false, - pvrtcSupported: false - }; + if ( ! width || ! height || ! levelCount ) { - } else { + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid texture' ); - this.workerConfig = { - astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), - etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), - etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), - dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), - bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), - pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) - || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) - }; + } - if ( renderer.capabilities.isWebGL2 ) { + if ( ! ktx2File.startTranscoding() ) { - // https://github.com/mrdoob/three.js/pull/22928 - this.workerConfig.etc1Supported = false; + cleanup(); + throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); } - } - - return this; + const faces = []; + const buffers = []; - } + for ( let face = 0; face < faceCount; face ++ ) { - init() { + const mipmaps = []; - if ( ! this.transcoderPending ) { + for ( let mip = 0; mip < levelCount; mip ++ ) { - // Load transcoder wrapper. - const jsLoader = new FileLoader( this.manager ); - jsLoader.setPath( this.transcoderPath ); - jsLoader.setWithCredentials( this.withCredentials ); - const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); + const layerMips = []; - // Load transcoder WASM binary. - const binaryLoader = new FileLoader( this.manager ); - binaryLoader.setPath( this.transcoderPath ); - binaryLoader.setResponseType( 'arraybuffer' ); - binaryLoader.setWithCredentials( this.withCredentials ); - const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); + let mipWidth, mipHeight; - this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) - .then( ( [ jsContent, binaryContent ] ) => { + for ( let layer = 0; layer < layerCount; layer ++ ) { - const fn = KTX2Loader.BasisWorker.toString(); + const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); - const body = [ - '/* constants */', - 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), - 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), - 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), - '/* basis_transcoder.js */', - jsContent, - '/* worker */', - fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) - ].join( '\n' ); + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { - this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); - this.transcoderBinary = binaryContent; + console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); - this.workerPool.setWorkerCreator( () => { + } - const worker = new Worker( this.workerSourceURL ); - const transcoderBinary = this.transcoderBinary.slice( 0 ); + if ( levelCount > 1 ) { - worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; - return worker; + } else { - } ); + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; - } ); + } - if ( _activeLoaders > 0 ) { + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); - // Each instance loads a transcoder and allocates workers, increasing network and memory cost. + if ( ! status ) { - console.warn( + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); - 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' - + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' + } - ); + layerMips.push( dst ); - } + } - _activeLoaders ++; + const mipData = concat( layerMips ); - } + mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); + buffers.push( mipData.buffer ); - return this.transcoderPending; + } - } + faces.push( { mipmaps, width, height, format: engineFormat } ); - load( url, onLoad, onProgress, onError ) { + } - if ( this.workerConfig === null ) { + cleanup(); - throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); + return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; } - const loader = new FileLoader( this.manager ); + // - loader.setResponseType( 'arraybuffer' ); - loader.setWithCredentials( this.withCredentials ); + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; - loader.load( url, ( buffer ) => { + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache.has( buffer ) ) { + return a.priorityETC1S - b.priorityETC1S; - const cachedTask = _taskCache.get( buffer ); + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - return cachedTask.promise.then( onLoad ).catch( onError ); + return a.priorityUASTC - b.priorityUASTC; - } + } ); - this._createTexture( buffer ) - .then( ( texture ) => onLoad ? onLoad( texture ) : null ) - .catch( onError ); + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { - }, onProgress, onError ); + let transcoderFormat; + let engineFormat; - } + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; - _createTextureFrom( transcodeResult, container ) { + for ( let i = 0; i < options.length; i ++ ) { - const { faces, width, height, format, type, error, dfdFlags } = transcodeResult; + const opt = options[ i ]; - if ( type === 'error' ) return Promise.reject( error ); + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; - let texture; + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; - if ( container.faceCount === 6 ) { + return { transcoderFormat, engineFormat }; - texture = new CompressedCubeTexture( faces, format, UnsignedByteType ); + } - } else { + console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); - const mipmaps = faces[ 0 ].mipmaps; + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; - texture = container.layerCount > 1 - ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) - : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + return { transcoderFormat, engineFormat }; } - texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; - texture.magFilter = LinearFilter; - texture.generateMipmaps = false; - - texture.needsUpdate = true; - texture.colorSpace = parseColorSpace( container ); - texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); + function isPowerOfTwo( value ) { - return texture; + if ( value <= 2 ) return true; - } + return ( value & ( value - 1 ) ) === 0 && value !== 0; - /** - * @param {ArrayBuffer} buffer - * @param {object?} config - * @return {Promise} - */ - async _createTexture( buffer, config = {} ) { + } - const container = read( new Uint8Array( buffer ) ); + /** Concatenates N byte arrays. */ + function concat( arrays ) { - if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { + if ( arrays.length === 1 ) return arrays[ 0 ]; - return createRawTexture( container ); + let totalByteLength = 0; - } + for ( let i = 0; i < arrays.length; i ++ ) { - // - const taskConfig = config; - const texturePending = this.init().then( () => { + const array = arrays[ i ]; + totalByteLength += array.byteLength; - return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] ); + } - } ).then( ( e ) => this._createTextureFrom( e.data, container ) ); + const result = new Uint8Array( totalByteLength ); - // Cache the task result. - _taskCache.set( buffer, { promise: texturePending } ); + let byteOffset = 0; - return texturePending; + for ( let i = 0; i < arrays.length; i ++ ) { - } + const array = arrays[ i ]; + result.set( array, byteOffset ); - dispose() { + byteOffset += array.byteLength; - this.workerPool.dispose(); - if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); + } - _activeLoaders --; + return result; - return this; + } } -} - +} ); // // Parsing for non-Basis textures. These textures are may have supercompression diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index 76a081b9ef821e..79037c13715af8 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -1916,7 +1916,11 @@ class SVGLoader extends Loader { } - static createShapes( shapePath ) { +} + +/* @__PURE__ */ Object.assign( SVGLoader, { + + createShapes( shapePath ) { // Param shapePath: a shapepath as returned by the parse function of this class // Returns Shape object @@ -2358,9 +2362,9 @@ class SVGLoader extends Loader { return shapesToReturn; - } + }, - static getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { + getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { // Param width: Stroke width // Param color: As returned by THREE.Color.getStyle() @@ -2383,9 +2387,9 @@ class SVGLoader extends Loader { strokeMiterLimit: miterLimit }; - } + }, - static pointsToStroke( points, style, arcDivisions, minDistance ) { + pointsToStroke( points, style, arcDivisions, minDistance ) { // Generates a stroke with some width around the given path. // The path can be open or closed (last point equals to first point) @@ -2412,9 +2416,9 @@ class SVGLoader extends Loader { return geometry; - } + }, - static pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { + pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { // This function can be called to update existing arrays or buffers. // Accepts same parameters as pointsToStroke, plus the buffers and optional offset. @@ -3166,7 +3170,6 @@ class SVGLoader extends Loader { } - -} +} ); export { SVGLoader }; diff --git a/examples/jsm/math/ColorConverter.js b/examples/jsm/math/ColorConverter.js index f45b3c7208ceb6..fdbb3a3f67b959 100644 --- a/examples/jsm/math/ColorConverter.js +++ b/examples/jsm/math/ColorConverter.js @@ -2,9 +2,9 @@ import { MathUtils } from 'three'; const _hsl = {}; -class ColorConverter { +const ColorConverter = { - static setHSV( color, h, s, v ) { + setHSV( color, h, s, v ) { // https://gist.github.com/xpansive/1337890#file-index-js @@ -14,9 +14,9 @@ class ColorConverter { return color.setHSL( h, ( s * v ) / ( ( h = ( 2 - s ) * v ) < 1 ? h : ( 2 - h ) ), h * 0.5 ); - } + }, - static getHSV( color, target ) { + getHSV( color, target ) { color.getHSL( _hsl ); @@ -31,6 +31,6 @@ class ColorConverter { } -} +}; export { ColorConverter }; diff --git a/examples/jsm/math/ConvexHull.js b/examples/jsm/math/ConvexHull.js index 46acf2712e8692..d57b9ecb917cdc 100644 --- a/examples/jsm/math/ConvexHull.js +++ b/examples/jsm/math/ConvexHull.js @@ -921,28 +921,6 @@ class Face { } - static create( a, b, c ) { - - const face = new Face(); - - const e0 = new HalfEdge( a, face ); - const e1 = new HalfEdge( b, face ); - const e2 = new HalfEdge( c, face ); - - // join edges - - e0.next = e2.prev = e1; - e1.next = e0.prev = e2; - e2.next = e1.prev = e0; - - // main half edge reference - - face.edge = e0; - - return face.compute(); - - } - getEdge( i ) { let edge = this.edge; @@ -991,6 +969,32 @@ class Face { } +/* @__PURE__ */ Object.assign( Face, { + + create( a, b, c ) { + + const face = new Face(); + + const e0 = new HalfEdge( a, face ); + const e1 = new HalfEdge( b, face ); + const e2 = new HalfEdge( c, face ); + + // join edges + + e0.next = e2.prev = e1; + e1.next = e0.prev = e2; + e2.next = e1.prev = e0; + + // main half edge reference + + face.edge = e0; + + return face.compute(); + + } + +} ); + // Entity for a Doubly-Connected Edge List (DCEL). class HalfEdge { diff --git a/examples/jsm/misc/ConvexObjectBreaker.js b/examples/jsm/misc/ConvexObjectBreaker.js index 378559b24355a9..dee14ee5165fc5 100644 --- a/examples/jsm/misc/ConvexObjectBreaker.js +++ b/examples/jsm/misc/ConvexObjectBreaker.js @@ -449,7 +449,11 @@ class ConvexObjectBreaker { } - static transformFreeVector( v, m ) { +} + +/* @__PURE__ */ Object.assign( ConvexObjectBreaker, { + + transformFreeVector( v, m ) { // input: // vector interpreted as a free vector @@ -464,9 +468,9 @@ class ConvexObjectBreaker { return v; - } + }, - static transformFreeVectorInverse( v, m ) { + transformFreeVectorInverse( v, m ) { // input: // vector interpreted as a free vector @@ -481,9 +485,9 @@ class ConvexObjectBreaker { return v; - } + }, - static transformTiedVectorInverse( v, m ) { + transformTiedVectorInverse( v, m ) { // input: // vector interpreted as a tied (ordinary) vector @@ -498,9 +502,9 @@ class ConvexObjectBreaker { return v; - } + }, - static transformPlaneToLocalSpace( plane, m, resultPlane ) { + transformPlaneToLocalSpace( plane, m, resultPlane ) { resultPlane.normal.copy( plane.normal ); resultPlane.constant = plane.constant; @@ -514,6 +518,6 @@ class ConvexObjectBreaker { } -} +} ); export { ConvexObjectBreaker }; diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index d917dc3f108e1f..5b4d9062b78a1b 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -489,7 +489,11 @@ class NodeMaterial extends ShaderMaterial { } - static fromMaterial( material ) { +} + +/* @__PURE__ */ Object.assign( NodeMaterial, { + + fromMaterial( material ) { if ( material.isNodeMaterial === true ) { // is already a node material @@ -517,7 +521,7 @@ class NodeMaterial extends ShaderMaterial { } -} +} ); export default NodeMaterial; diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index 7efd1eec700977..3b2c8b4fa2b86a 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -17,27 +17,6 @@ import { class Lensflare extends Mesh { - static Geometry = /* @__PURE__ */ ( function () { - - const geometry = new BufferGeometry(); - - const float32Array = new Float32Array( [ - - 1, - 1, 0, 0, 0, - 1, - 1, 0, 1, 0, - 1, 1, 0, 1, 1, - - 1, 1, 0, 0, 1 - ] ); - - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - - geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - - return geometry; - - } )(); - constructor() { super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); @@ -304,11 +283,49 @@ class Lensflare extends Mesh { } +/* @__PURE__ */ Object.assign( Lensflare, { + + Geometry: /* @__PURE__ */ ( function () { + + const geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + return geometry; + + } )() + +} ); + // class LensflareElement { - static Shader = { + constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { + + this.texture = texture; + this.size = size; + this.distance = distance; + this.color = color; + + } + +} + +/* @__PURE__ */ Object.assign( LensflareElement, { + + Shader: { uniforms: { @@ -378,17 +395,8 @@ class LensflareElement { }` - }; - - constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { - - this.texture = texture; - this.size = size; - this.distance = distance; - this.color = color; - } -} +} ); export { Lensflare, LensflareElement }; diff --git a/examples/jsm/objects/Reflector.js b/examples/jsm/objects/Reflector.js index 8e39aa64aedc91..b95e18cd081def 100644 --- a/examples/jsm/objects/Reflector.js +++ b/examples/jsm/objects/Reflector.js @@ -14,75 +14,6 @@ import { class Reflector extends Mesh { - static ReflectorShader = { - - name: 'ReflectorShader', - - uniforms: { - - 'color': { - value: null - }, - - 'tDiffuse': { - value: null - }, - - 'textureMatrix': { - value: null - } - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - #include - #include - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - #include - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - varying vec4 vUv; - - #include - - float blendOverlay( float base, float blend ) { - - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - - } - - vec3 blendOverlay( vec3 base, vec3 blend ) { - - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - - } - - void main() { - - #include - - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - - #include - #include - - }` - }; - constructor( geometry, options = {} ) { super( geometry ); @@ -261,4 +192,77 @@ class Reflector extends Mesh { } +/* @__PURE__ */ Object.assign( Reflector, { + + ReflectorShader: { + + name: 'ReflectorShader', + + uniforms: { + + 'color': { + value: null + }, + + 'tDiffuse': { + value: null + }, + + 'textureMatrix': { + value: null + } + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + #include + #include + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + #include + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + varying vec4 vUv; + + #include + + float blendOverlay( float base, float blend ) { + + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + + } + + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } + + void main() { + + #include + + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + + #include + #include + + }` + } + +} ); + export { Reflector }; diff --git a/examples/jsm/objects/ReflectorForSSRPass.js b/examples/jsm/objects/ReflectorForSSRPass.js index 3be0ccc48a3be0..52d77eb9ab7d31 100644 --- a/examples/jsm/objects/ReflectorForSSRPass.js +++ b/examples/jsm/objects/ReflectorForSSRPass.js @@ -17,103 +17,6 @@ import { class ReflectorForSSRPass extends Mesh { - static ReflectorShader = { - - defines: { - DISTANCE_ATTENUATION: true, - FRESNEL: true, - }, - - uniforms: { - - color: { value: null }, - tDiffuse: { value: null }, - tDepth: { value: null }, - textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, - maxDistance: { value: 180 }, - opacity: { value: 0.5 }, - fresnelCoe: { value: null }, - virtualCameraNear: { value: null }, - virtualCameraFar: { value: null }, - virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, - resolution: { value: /* @__PURE__ */ new Vector2() }, - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - uniform sampler2D tDepth; - uniform float maxDistance; - uniform float opacity; - uniform float fresnelCoe; - uniform float virtualCameraNear; - uniform float virtualCameraFar; - uniform mat4 virtualCameraProjectionMatrix; - uniform mat4 virtualCameraProjectionMatrixInverse; - uniform mat4 virtualCameraMatrixWorld; - uniform vec2 resolution; - varying vec4 vUv; - #include - float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } - vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } - float getDepth( const in vec2 uv ) { - return texture2D( tDepth, uv ).x; - } - float getViewZ( const in float depth ) { - return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); - } - vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { - vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc - clipPosition *= clipW; //clip - return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view - } - void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - #ifdef useDepthTexture - vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; - uv.x=1.-uv.x; - float depth = texture2DProj( tDepth, vUv ).r; - float viewZ = getViewZ( depth ); - float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; - vec3 viewPosition=getViewPosition( uv, depth, clipW ); - vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; - if(worldPosition.y>maxDistance) discard; - float op=opacity; - #ifdef DISTANCE_ATTENUATION - float ratio=1.-(worldPosition.y/maxDistance); - float attenuation=ratio*ratio; - op=opacity*attenuation; - #endif - #ifdef FRESNEL - op*=fresnelCoe; - #endif - gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); - #else - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #endif - } - `, - }; - constructor( geometry, options = {} ) { super( geometry ); @@ -346,4 +249,105 @@ class ReflectorForSSRPass extends Mesh { } +/* @__PURE__ */ Object.assign( ReflectorForSSRPass, { + + ReflectorShader: { + + defines: { + DISTANCE_ATTENUATION: true, + FRESNEL: true, + }, + + uniforms: { + + color: { value: null }, + tDiffuse: { value: null }, + tDepth: { value: null }, + textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, + maxDistance: { value: 180 }, + opacity: { value: 0.5 }, + fresnelCoe: { value: null }, + virtualCameraNear: { value: null }, + virtualCameraFar: { value: null }, + virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + uniform sampler2D tDepth; + uniform float maxDistance; + uniform float opacity; + uniform float fresnelCoe; + uniform float virtualCameraNear; + uniform float virtualCameraFar; + uniform mat4 virtualCameraProjectionMatrix; + uniform mat4 virtualCameraProjectionMatrixInverse; + uniform mat4 virtualCameraMatrixWorld; + uniform vec2 resolution; + varying vec4 vUv; + #include + float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } + vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } + float getDepth( const in vec2 uv ) { + return texture2D( tDepth, uv ).x; + } + float getViewZ( const in float depth ) { + return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); + } + vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { + vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc + clipPosition *= clipW; //clip + return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view + } + void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + #ifdef useDepthTexture + vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; + uv.x=1.-uv.x; + float depth = texture2DProj( tDepth, vUv ).r; + float viewZ = getViewZ( depth ); + float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; + vec3 viewPosition=getViewPosition( uv, depth, clipW ); + vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; + if(worldPosition.y>maxDistance) discard; + float op=opacity; + #ifdef DISTANCE_ATTENUATION + float ratio=1.-(worldPosition.y/maxDistance); + float attenuation=ratio*ratio; + op=opacity*attenuation; + #endif + #ifdef FRESNEL + op*=fresnelCoe; + #endif + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); + #else + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #endif + } + `, + } + +} ); + export { ReflectorForSSRPass }; diff --git a/examples/jsm/objects/Refractor.js b/examples/jsm/objects/Refractor.js index c067838f26b3e7..b096538062f3da 100644 --- a/examples/jsm/objects/Refractor.js +++ b/examples/jsm/objects/Refractor.js @@ -15,68 +15,6 @@ import { class Refractor extends Mesh { - static RefractorShader = { - - uniforms: { - - 'color': { - value: null - }, - - 'tDiffuse': { - value: null - }, - - 'textureMatrix': { - value: null - } - - }, - - vertexShader: /* glsl */` - - uniform mat4 textureMatrix; - - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - uniform vec3 color; - uniform sampler2D tDiffuse; - - varying vec4 vUv; - - float blendOverlay( float base, float blend ) { - - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - - } - - vec3 blendOverlay( vec3 base, vec3 blend ) { - - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - - } - - void main() { - - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - - #include - #include - - }` - - }; - constructor( geometry, options = {} ) { super( geometry ); @@ -321,4 +259,70 @@ class Refractor extends Mesh { } +/* @__PURE__ */ Object.assign( Refractor, { + + RefractorShader: { + + uniforms: { + + 'color': { + value: null + }, + + 'tDiffuse': { + value: null + }, + + 'textureMatrix': { + value: null + } + + }, + + vertexShader: /* glsl */` + + uniform mat4 textureMatrix; + + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + + uniform vec3 color; + uniform sampler2D tDiffuse; + + varying vec4 vUv; + + float blendOverlay( float base, float blend ) { + + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + + } + + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } + + void main() { + + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + + #include + #include + + }` + + } + +} ); + export { Refractor }; diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index 347f397e015047..98291980f90154 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -23,7 +23,30 @@ import { class Sky extends Mesh { - static SkyShader = { + constructor() { + + const shader = Sky.SkyShader; + + const material = new ShaderMaterial( { + name: 'SkyShader', + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: UniformsUtils.clone( shader.uniforms ), + side: BackSide, + depthWrite: false + } ); + + super( new BoxGeometry( 1, 1, 1 ), material ); + + this.isSky = true; + + } + +} + +/* @__PURE__ */ Object.assign( Sky, { + + SkyShader: { uniforms: { 'turbidity': { value: 2 }, @@ -191,27 +214,8 @@ class Sky extends Mesh { }` - }; - - constructor() { - - const shader = Sky.SkyShader; - - const material = new ShaderMaterial( { - name: 'SkyShader', - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, - uniforms: UniformsUtils.clone( shader.uniforms ), - side: BackSide, - depthWrite: false - } ); - - super( new BoxGeometry( 1, 1, 1 ), material ); - - this.isSky = true; - } -} +} ); export { Sky }; diff --git a/examples/jsm/objects/Water2.js b/examples/jsm/objects/Water2.js index 04a9453b5a616f..b0ae6ff17885ed 100644 --- a/examples/jsm/objects/Water2.js +++ b/examples/jsm/objects/Water2.js @@ -23,150 +23,6 @@ import { Refractor } from '../objects/Refractor.js'; class Water extends Mesh { - static WaterShader = { - - uniforms: { - - color: { - value: null - }, - - reflectivity: { - value: 0 - }, - - tReflectionMap: { - value: null - }, - - tRefractionMap: { - value: null - }, - - tNormalMap0: { - value: null - }, - - tNormalMap1: { - value: null - }, - - textureMatrix: { - value: null - }, - - config: { - value: /* @__PURE__ */ new Vector4() - } - - }, - - vertexShader: /* glsl */` - - #include - #include - #include - - uniform mat4 textureMatrix; - - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; - - void main() { - - vUv = uv; - vCoord = textureMatrix * vec4( position, 1.0 ); - - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vToEye = cameraPosition - worldPosition.xyz; - - vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex - gl_Position = projectionMatrix * mvPosition; - - #include - #include - - }`, - - fragmentShader: /* glsl */` - - #include - #include - #include - - uniform sampler2D tReflectionMap; - uniform sampler2D tRefractionMap; - uniform sampler2D tNormalMap0; - uniform sampler2D tNormalMap1; - - #ifdef USE_FLOWMAP - uniform sampler2D tFlowMap; - #else - uniform vec2 flowDirection; - #endif - - uniform vec3 color; - uniform float reflectivity; - uniform vec4 config; - - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; - - void main() { - - #include - - float flowMapOffset0 = config.x; - float flowMapOffset1 = config.y; - float halfCycle = config.z; - float scale = config.w; - - vec3 toEye = normalize( vToEye ); - - // determine flow direction - vec2 flow; - #ifdef USE_FLOWMAP - flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; - #else - flow = flowDirection; - #endif - flow.x *= - 1.0; - - // sample normal maps (distort uvs with flowdata) - vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); - vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); - - // linear interpolate to get the final normal color - float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; - vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); - - // calculate normal vector - vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); - - // calculate the fresnel term to blend reflection and refraction maps - float theta = max( dot( toEye, normal ), 0.0 ); - float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); - - // calculate final uv coords - vec3 coord = vCoord.xyz / vCoord.w; - vec2 uv = coord.xy + coord.z * normal.xz * 0.05; - - vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); - vec4 refractColor = texture2D( tRefractionMap, uv ); - - // multiply water color with the mix of both textures - gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); - - #include - #include - #include - - }` - - }; - constructor( geometry, options = {} ) { super( geometry ); @@ -347,4 +203,152 @@ class Water extends Mesh { } +/* @__PURE__ */ Object.assign( Water, { + + WaterShader: { + + uniforms: { + + color: { + value: null + }, + + reflectivity: { + value: 0 + }, + + tReflectionMap: { + value: null + }, + + tRefractionMap: { + value: null + }, + + tNormalMap0: { + value: null + }, + + tNormalMap1: { + value: null + }, + + textureMatrix: { + value: null + }, + + config: { + value: /* @__PURE__ */ new Vector4() + } + + }, + + vertexShader: /* glsl */` + + #include + #include + #include + + uniform mat4 textureMatrix; + + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; + + void main() { + + vUv = uv; + vCoord = textureMatrix * vec4( position, 1.0 ); + + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vToEye = cameraPosition - worldPosition.xyz; + + vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex + gl_Position = projectionMatrix * mvPosition; + + #include + #include + + }`, + + fragmentShader: /* glsl */` + + #include + #include + #include + + uniform sampler2D tReflectionMap; + uniform sampler2D tRefractionMap; + uniform sampler2D tNormalMap0; + uniform sampler2D tNormalMap1; + + #ifdef USE_FLOWMAP + uniform sampler2D tFlowMap; + #else + uniform vec2 flowDirection; + #endif + + uniform vec3 color; + uniform float reflectivity; + uniform vec4 config; + + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; + + void main() { + + #include + + float flowMapOffset0 = config.x; + float flowMapOffset1 = config.y; + float halfCycle = config.z; + float scale = config.w; + + vec3 toEye = normalize( vToEye ); + + // determine flow direction + vec2 flow; + #ifdef USE_FLOWMAP + flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; + #else + flow = flowDirection; + #endif + flow.x *= - 1.0; + + // sample normal maps (distort uvs with flowdata) + vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); + vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); + + // linear interpolate to get the final normal color + float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; + vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); + + // calculate normal vector + vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); + + // calculate the fresnel term to blend reflection and refraction maps + float theta = max( dot( toEye, normal ), 0.0 ); + float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); + + // calculate final uv coords + vec3 coord = vCoord.xyz / vCoord.w; + vec2 uv = coord.xy + coord.z * normal.xz * 0.05; + + vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); + vec4 refractColor = texture2D( tRefractionMap, uv ); + + // multiply water color with the mix of both textures + gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); + + #include + #include + #include + + }` + + } + +} ); + export { Water }; diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index 8bb0f36951a3a4..4e77c6ede68bc6 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -11,9 +11,6 @@ import { ConvolutionShader } from '../shaders/ConvolutionShader.js'; class BloomPass extends Pass { - static blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); - static blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); - constructor( strength = 1, kernelSize = 25, sigma = 4 ) { super(); @@ -130,6 +127,13 @@ class BloomPass extends Pass { } +/* @__PURE__ */ Object.assign( BloomPass, { + + blurX: /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ), + blurY: /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ) + +} ); + const CombineShader = { name: 'CombineShader', diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index 14a2e14c635887..db050b8a951bb3 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -18,9 +18,6 @@ import { CopyShader } from '../shaders/CopyShader.js'; class OutlinePass extends Pass { - static BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); - static BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); - constructor( resolution, scene, camera, selectedObjects ) { super(); @@ -651,4 +648,11 @@ class OutlinePass extends Pass { } +/* @__PURE__ */ Object.assign( OutlinePass, { + + BlurDirectionX: /* @__PURE__ */ new Vector2( 1.0, 0.0 ), + BlurDirectionY: /* @__PURE__ */ new Vector2( 0.0, 1.0 ) + +} ); + export { OutlinePass }; diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index 99517cd2f64090..cf9a5f4784f7af 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -29,12 +29,6 @@ import { CopyShader } from '../shaders/CopyShader.js'; class SAOPass extends Pass { - static OUTPUT = { - 'Default': 0, - 'SAO': 1, - 'Normal': 2 - }; - constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { super(); @@ -333,4 +327,14 @@ class SAOPass extends Pass { } +/* @__PURE__ */ Object.assign( SAOPass, { + + OUTPUT: { + Default: 0, + SAO: 1, + Normal: 2 + } + +} ); + export { SAOPass }; diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index a5ae94dc29a690..e41850a91a1705 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -32,14 +32,6 @@ import { CopyShader } from '../shaders/CopyShader.js'; class SSAOPass extends Pass { - static OUTPUT = { - 'Default': 0, - 'SSAO': 1, - 'Blur': 2, - 'Depth': 3, - 'Normal': 4 - }; - constructor( scene, camera, width, height, kernelSize = 32 ) { super(); @@ -417,4 +409,16 @@ class SSAOPass extends Pass { } +/* @__PURE__ */ Object.assign( SSAOPass, { + + OUTPUT: { + Default: 0, + SSAO: 1, + Blur: 2, + Depth: 3, + Normal: 4 + } + +} ); + export { SSAOPass }; diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index f762b0e9aaa660..d0fe646471dfeb 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -23,15 +23,6 @@ import { CopyShader } from '../shaders/CopyShader.js'; class SSRPass extends Pass { - static OUTPUT = { - 'Default': 0, - 'SSR': 1, - 'Beauty': 3, - 'Depth': 4, - 'Normal': 5, - 'Metalness': 7, - }; - constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { super(); @@ -638,4 +629,17 @@ class SSRPass extends Pass { } +/* @__PURE__ */ Object.assign( SSRPass, { + + OUTPUT: { + Default: 0, + SSR: 1, + Beauty: 3, + Depth: 4, + Normal: 5, + Metalness: 7, + } + +} ); + export { SSRPass }; diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index 8e3f1bdc1662b6..d7bfa46ff66ba9 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -24,9 +24,6 @@ import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js */ class UnrealBloomPass extends Pass { - static BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); - static BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); - constructor( resolution, strength, radius, threshold ) { super(); @@ -412,4 +409,11 @@ class UnrealBloomPass extends Pass { } +/* @__PURE__ */ Object.assign( UnrealBloomPass, { + + BlurDirectionX: /* @__PURE__ */ new Vector2( 1.0, 0.0 ), + BlurDirectionY: /* @__PURE__ */ new Vector2( 0.0, 1.0 ) + +} ); + export { UnrealBloomPass }; diff --git a/examples/jsm/utils/LDrawUtils.js b/examples/jsm/utils/LDrawUtils.js index 64bb11c226ff4a..cd940bc1e2d74f 100644 --- a/examples/jsm/utils/LDrawUtils.js +++ b/examples/jsm/utils/LDrawUtils.js @@ -9,9 +9,9 @@ import { import { mergeGeometries } from './BufferGeometryUtils.js'; -class LDrawUtils { +const LDrawUtils = { - static mergeObject( object ) { + mergeObject( object ) { // Merges geometries in object by materials and returns new object. Use on not indexed geometries. // The object buffers reference the old object ones. @@ -197,6 +197,6 @@ class LDrawUtils { } -} +}; export { LDrawUtils }; diff --git a/examples/jsm/webxr/ARButton.js b/examples/jsm/webxr/ARButton.js index cc2b0173109074..bbb56c7254ee26 100644 --- a/examples/jsm/webxr/ARButton.js +++ b/examples/jsm/webxr/ARButton.js @@ -1,6 +1,6 @@ -class ARButton { +const ARButton = { - static createButton( renderer, sessionInit = {} ) { + createButton( renderer, sessionInit = {} ) { const button = document.createElement( 'button' ); @@ -203,6 +203,6 @@ class ARButton { } -} +}; export { ARButton }; diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index b9d116c93ab95c..a3a604d43f36d5 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -1,6 +1,6 @@ -class VRButton { +const VRButton = { - static createButton( renderer ) { + createButton( renderer ) { const button = document.createElement( 'button' ); @@ -172,11 +172,11 @@ class VRButton { } - } + }, - static xrSessionIsGranted = false; + xrSessionIsGranted: false, - static registerSessionGrantedListener() { + registerSessionGrantedListener() { if ( 'xr' in navigator ) { @@ -194,7 +194,7 @@ class VRButton { } -} +}; /* @__PURE__ */ VRButton.registerSessionGrantedListener(); diff --git a/examples/jsm/webxr/XRButton.js b/examples/jsm/webxr/XRButton.js index f33cbbea25111b..a2ce66fab64153 100644 --- a/examples/jsm/webxr/XRButton.js +++ b/examples/jsm/webxr/XRButton.js @@ -1,6 +1,6 @@ -class XRButton { +const XRButton = { - static createButton( renderer ) { + createButton( renderer ) { const button = document.createElement( 'button' ); @@ -193,6 +193,6 @@ class XRButton { } -} +}; export { XRButton }; From 4387cdeb4bcc36a01b8a5cacae82c92c0c16ae77 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:09:21 -0500 Subject: [PATCH 07/45] Addons: cleanup shader chunks, math constants --- examples/jsm/controls/OrbitControls.js | 3 +- examples/jsm/csm/CSMShader.js | 6 +- examples/jsm/materials/MeshGouraudMaterial.js | 4 +- examples/jsm/math/ImprovedNoise.js | 23 +-- examples/jsm/shaders/MMDToonShader.js | 4 +- examples/jsm/shaders/OutputShader.js | 7 +- .../jsm/shaders/SubsurfaceScatteringShader.js | 133 ++++++++++-------- examples/jsm/shaders/VelocityShader.js | 4 +- 8 files changed, 88 insertions(+), 96 deletions(-) diff --git a/examples/jsm/controls/OrbitControls.js b/examples/jsm/controls/OrbitControls.js index c007569dc05df7..b40c85d7aa6545 100644 --- a/examples/jsm/controls/OrbitControls.js +++ b/examples/jsm/controls/OrbitControls.js @@ -8,7 +8,6 @@ import { Vector3, Plane, Ray, - MathUtils } from 'three'; // OrbitControls performs orbiting, dollying (zooming), and panning. @@ -23,7 +22,7 @@ const _startEvent = { type: 'start' }; const _endEvent = { type: 'end' }; const _ray = /* @__PURE__ */ new Ray(); const _plane = /* @__PURE__ */ new Plane(); -const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); +const TILT_LIMIT = Math.cos( 70 * ( Math.PI / 180 ) ); class OrbitControls extends EventDispatcher { diff --git a/examples/jsm/csm/CSMShader.js b/examples/jsm/csm/CSMShader.js index a1611f2885d960..b6b279313f5824 100644 --- a/examples/jsm/csm/CSMShader.js +++ b/examples/jsm/csm/CSMShader.js @@ -1,5 +1,3 @@ -import { ShaderChunk } from 'three'; - const CSMShader = { lights_fragment_begin: /* glsl */` vec3 geometryPosition = - vViewPosition; @@ -284,7 +282,9 @@ uniform vec2 CSM_cascades[CSM_CASCADES]; uniform float cameraNear; uniform float shadowFar; #endif - ` + ShaderChunk.lights_pars_begin + +#include + ` }; export { CSMShader }; diff --git a/examples/jsm/materials/MeshGouraudMaterial.js b/examples/jsm/materials/MeshGouraudMaterial.js index 1c046ba50736a8..b3edfba1fa3685 100644 --- a/examples/jsm/materials/MeshGouraudMaterial.js +++ b/examples/jsm/materials/MeshGouraudMaterial.js @@ -9,7 +9,7 @@ import { UniformsUtils, UniformsLib, ShaderMaterial, Color, MultiplyOperation } const GouraudShader = { - uniforms: /* @__PURE__ */ UniformsUtils.merge( [ + uniforms: /* @__PURE__ */ ( () => UniformsUtils.merge( [ UniformsLib.common, UniformsLib.specularmap, UniformsLib.envmap, @@ -21,7 +21,7 @@ const GouraudShader = { { emissive: { value: /* @__PURE__ */ new Color( 0x000000 ) } } - ] ), + ] ) )(), vertexShader: /* glsl */` diff --git a/examples/jsm/math/ImprovedNoise.js b/examples/jsm/math/ImprovedNoise.js index 5647d1b208d9e4..8509cd8785fad0 100644 --- a/examples/jsm/math/ImprovedNoise.js +++ b/examples/jsm/math/ImprovedNoise.js @@ -1,21 +1,8 @@ // https://cs.nyu.edu/~perlin/noise/ -const _p = [ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, - 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, - 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, - 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, - 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, - 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, - 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, - 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, - 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, - 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 ]; - -for ( let i = 0; i < 256; i ++ ) { - - _p[ 256 + i ] = _p[ i ]; - -} +const _p = [ + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 +]; function fade( t ) { @@ -37,7 +24,7 @@ function grad( hash, x, y, z ) { } -class ImprovedNoise { +const ImprovedNoise = { noise( x, y, z ) { @@ -66,6 +53,6 @@ class ImprovedNoise { } -} +}; export { ImprovedNoise }; diff --git a/examples/jsm/shaders/MMDToonShader.js b/examples/jsm/shaders/MMDToonShader.js index 0b9517f01b1a3e..9cfbab05499df8 100644 --- a/examples/jsm/shaders/MMDToonShader.js +++ b/examples/jsm/shaders/MMDToonShader.js @@ -69,7 +69,7 @@ const mmd_toon_matcap_fragment = /* glsl */` #endif `; -const MMDToonShader = { +const MMDToonShader = /* @__PURE__ */ ( () => ( { defines: { TOON: true, @@ -127,6 +127,6 @@ const MMDToonShader = { ` ) -}; +} ) )(); export { MMDToonShader }; diff --git a/examples/jsm/shaders/OutputShader.js b/examples/jsm/shaders/OutputShader.js index cf7a18ae7f9de8..03c5479bf54a7e 100644 --- a/examples/jsm/shaders/OutputShader.js +++ b/examples/jsm/shaders/OutputShader.js @@ -1,7 +1,3 @@ -import { - ShaderChunk -} from 'three'; - const OutputShader = { uniforms: { @@ -35,7 +31,8 @@ const OutputShader = { uniform sampler2D tDiffuse; - ` + ShaderChunk[ 'tonemapping_pars_fragment' ] + ShaderChunk[ 'colorspace_pars_fragment' ] + ` + #include + #include varying vec2 vUv; diff --git a/examples/jsm/shaders/SubsurfaceScatteringShader.js b/examples/jsm/shaders/SubsurfaceScatteringShader.js index b9749196a70095..2d13b934ebe5ad 100644 --- a/examples/jsm/shaders/SubsurfaceScatteringShader.js +++ b/examples/jsm/shaders/SubsurfaceScatteringShader.js @@ -19,70 +19,79 @@ function replaceAll( string, find, replace ) { } -const meshphong_frag_head = ShaderChunk[ 'meshphong_frag' ].slice( 0, ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); -const meshphong_frag_body = ShaderChunk[ 'meshphong_frag' ].slice( ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); - -const SubsurfaceScatteringShader = { - - uniforms: /* @__PURE__ */ UniformsUtils.merge( [ - ShaderLib[ 'phong' ].uniforms, - { - 'thicknessMap': { value: null }, - 'thicknessColor': { value: /* @__PURE__ */ new Color( 0xffffff ) }, - 'thicknessDistortion': { value: 0.1 }, - 'thicknessAmbient': { value: 0.0 }, - 'thicknessAttenuation': { value: 0.1 }, - 'thicknessPower': { value: 2.0 }, - 'thicknessScale': { value: 10.0 } - } - - ] ), - - vertexShader: [ - '#define USE_UV', - ShaderChunk[ 'meshphong_vert' ], - ].join( '\n' ), - - fragmentShader: [ - '#define USE_UV', - '#define SUBSURFACE', - - meshphong_frag_head, - - 'uniform sampler2D thicknessMap;', - 'uniform float thicknessPower;', - 'uniform float thicknessScale;', - 'uniform float thicknessDistortion;', - 'uniform float thicknessAmbient;', - 'uniform float thicknessAttenuation;', - 'uniform vec3 thicknessColor;', - - 'void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {', - ' vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r;', - ' vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));', - ' float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;', - ' vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness;', - ' reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;', - '}', - - meshphong_frag_body.replace( '#include ', - - /* @__PURE__ */ replaceAll( - ShaderChunk[ 'lights_fragment_begin' ], - 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', - [ - 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', - - '#if defined( SUBSURFACE ) && defined( USE_UV )', - ' RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);', - '#endif', - ].join( '\n' ) - ), - +const SubsurfaceScatteringShader = /* @__PURE__ */ ( () => { + + const meshphong_frag_head = ShaderChunk[ 'meshphong_frag' ].slice( 0, ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); + const meshphong_frag_body = ShaderChunk[ 'meshphong_frag' ].slice( ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); + + const SubsurfaceScatteringShader = { + + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ + ShaderLib[ 'phong' ].uniforms, + { + 'thicknessMap': { value: null }, + 'thicknessColor': { value: /* @__PURE__ */ new Color( 0xffffff ) }, + 'thicknessDistortion': { value: 0.1 }, + 'thicknessAmbient': { value: 0.0 }, + 'thicknessAttenuation': { value: 0.1 }, + 'thicknessPower': { value: 2.0 }, + 'thicknessScale': { value: 10.0 } + } + + ] ), + + vertexShader: /* glsl */ ` + #define USE_UV + ${ShaderChunk[ 'meshphong_vert' ]} + `, + + fragmentShader: /* glsl */ ` + #define USE_UV + #define SUBSURFACE + + ${meshphong_frag_head} + + uniform sampler2D thicknessMap; + uniform float thicknessPower; + uniform float thicknessScale; + uniform float thicknessDistortion; + uniform float thicknessAmbient; + uniform float thicknessAttenuation; + uniform vec3 thicknessColor; + + void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) { + vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r; + vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion)); + float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale; + vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness; + reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color; + } + + ${meshphong_frag_body.replace( + + '#include ', + + replaceAll( + ShaderChunk[ 'lights_fragment_begin' ], + 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', + /* glsl */ ` + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); + + #if defined( SUBSURFACE ) && defined( USE_UV ) + RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight); + #endif + ` ), - ].join( '\n' ), + )} + + `, + + }; + + return SubsurfaceScatteringShader; + +} )(); -}; export { SubsurfaceScatteringShader }; diff --git a/examples/jsm/shaders/VelocityShader.js b/examples/jsm/shaders/VelocityShader.js index 161955ff6db8d2..288c208fe7ee16 100644 --- a/examples/jsm/shaders/VelocityShader.js +++ b/examples/jsm/shaders/VelocityShader.js @@ -10,7 +10,7 @@ import { const VelocityShader = { - uniforms: /* @__PURE__ */ UniformsUtils.merge( [ + uniforms: /* @__PURE__ */ ( () => UniformsUtils.merge( [ UniformsLib.common, UniformsLib.displacementmap, { @@ -18,7 +18,7 @@ const VelocityShader = { currentProjectionViewMatrix: { value: /* @__PURE__ */ new Matrix4() }, previousProjectionViewMatrix: { value: /* @__PURE__ */ new Matrix4() } } - ] ), + ] ) )(), vertexShader: /* glsl */` #define NORMAL From 9c3e2857f81368f384e3c519226d3ed9ace995b8 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:18:05 -0500 Subject: [PATCH 08/45] ImprovedNoise: don't premultiply kernel --- examples/jsm/math/ImprovedNoise.js | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/examples/jsm/math/ImprovedNoise.js b/examples/jsm/math/ImprovedNoise.js index 8509cd8785fad0..c0e1b14f2529fe 100644 --- a/examples/jsm/math/ImprovedNoise.js +++ b/examples/jsm/math/ImprovedNoise.js @@ -1,8 +1,27 @@ // https://cs.nyu.edu/~perlin/noise/ -const _p = [ - 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 -]; +const _p = /* @__PURE__ */ ( () => { + + const _p = [ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, + 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, + 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, + 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, + 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, + 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, + 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, + 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, + 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 ]; + + for ( let i = 0; i < 256; i ++ ) { + + _p[ 256 + i ] = _p[ i ]; + + } + + return _p; + +} )(); function fade( t ) { @@ -24,7 +43,7 @@ function grad( hash, x, y, z ) { } -const ImprovedNoise = { +class ImprovedNoise { noise( x, y, z ) { @@ -53,6 +72,6 @@ const ImprovedNoise = { } -}; +} export { ImprovedNoise }; From c8bbc34bc563b6b165d01a3804d427b68f58711c Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:34:51 -0500 Subject: [PATCH 09/45] fflate: tree-shake as IIFE --- examples/jsm/libs/fflate.module.js | 4667 ++++++++++++++-------------- 1 file changed, 2408 insertions(+), 2259 deletions(-) diff --git a/examples/jsm/libs/fflate.module.js b/examples/jsm/libs/fflate.module.js index 808000a503cf63..6235f8f489b532 100644 --- a/examples/jsm/libs/fflate.module.js +++ b/examples/jsm/libs/fflate.module.js @@ -14,2461 +14,2610 @@ version 0.6.9 // However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size. // Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint // is better for memory in most engines (I *think*). -var ch2 = {}; -var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; -var cwk = function (u) { return new Worker(u); }; -try { - URL.revokeObjectURL(durl('')); -} -catch (e) { - // We're in Deno or a very old browser - durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; - // If Deno, this is necessary; if not, this changes nothing - cwk = function (u) { return new Worker(u, { type: 'module' }); }; -} -var wk = (function (c, id, msg, transfer, cb) { - var w = cwk(ch2[id] || (ch2[id] = durl(c))); - w.onerror = function (e) { return cb(e.error, null); }; - w.onmessage = function (e) { return cb(null, e.data); }; - w.postMessage(msg, transfer); - return w; -}); -// aliases for shorter compressed code (most minifers don't do this) -var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; -// fixed length extra bits -var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); -// fixed distance extra bits -// see fleb note -var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); -// code length index map -var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); -// get base, reverse index map from extra bits -var freb = function (eb, start) { - var b = new u16(31); - for (var i = 0; i < 31; ++i) { - b[i] = start += 1 << eb[i - 1]; - } - // numbers here are at max 18 bits - var r = new u32(b[30]); - for (var i = 1; i < 30; ++i) { - for (var j = b[i]; j < b[i + 1]; ++j) { - r[j] = ((j - b[i]) << 5) | i; +const { + + Deflate, + AsyncDeflate, + deflate, + deflateSync, + Inflate, + AsyncInflate, + inflate, + inflateSync, + Gzip, + AsyncGzip, + gzip, + gzipSync, + Gunzip, + AsyncGunzip, + gunzip, + gunzipSync, + Zlib, + AsyncZlib, + zlib, + zlibSync, + Unzlib, + AsyncUnzlib, + unzlib, + unzlibSync, + Decompress, + AsyncDecompress, + decompress, + decompressSync, + DecodeUTF8, + EncodeUTF8, + strToU8, + strFromU8, + ZipPassThrough, + ZipDeflate, + AsyncZipDeflate, + Zip, + zip, + zipSync, + UnzipPassThrough, + UnzipInflate, + AsyncUnzipInflate, + Unzip, + unzip, + unzipSync, + +} = /* @__PURE__ */ ( () => { + + var ch2 = {}; + var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; + var cwk = function (u) { return new Worker(u); }; + try { + URL.revokeObjectURL(durl('')); + } + catch (e) { + // We're in Deno or a very old browser + durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; + // If Deno, this is necessary; if not, this changes nothing + cwk = function (u) { return new Worker(u, { type: 'module' }); }; + } + var wk = (function (c, id, msg, transfer, cb) { + var w = cwk(ch2[id] || (ch2[id] = durl(c))); + w.onerror = function (e) { return cb(e.error, null); }; + w.onmessage = function (e) { return cb(null, e.data); }; + w.postMessage(msg, transfer); + return w; + }); + + // aliases for shorter compressed code (most minifers don't do this) + var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; + // fixed length extra bits + var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); + // fixed distance extra bits + // see fleb note + var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); + // code length index map + var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); + // get base, reverse index map from extra bits + var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; } - } - return [b, r]; -}; -var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; -// we can ignore the fact that the other numbers are wrong; they never happen anyway -fl[28] = 258, revfl[258] = 28; -var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; -// map of value to reverse (assuming 16 bits) -var rev = new u16(32768); -for (var i = 0; i < 32768; ++i) { - // reverse table algorithm from SO - var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); - x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); - x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); - rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; -} -// create huffman tree from u8 "map": index -> code length for code index -// mb (max bits) must be at most 15 -// TODO: optimize/split up? -var hMap = (function (cd, mb, r) { - var s = cd.length; - // index - var i = 0; - // u16 "map": index -> # of codes with bit length = index - var l = new u16(mb); - // length of cd must be 288 (total # of codes) - for (; i < s; ++i) - ++l[cd[i] - 1]; - // u16 "map": index -> minimum code for bit length = index - var le = new u16(mb); - for (i = 0; i < mb; ++i) { - le[i] = (le[i - 1] + l[i - 1]) << 1; - } - var co; - if (r) { - // u16 "map": index -> number of actual bits, symbol for code - co = new u16(1 << mb); - // bits to remove for reverser - var rvb = 15 - mb; - for (i = 0; i < s; ++i) { - // ignore 0 lengths - if (cd[i]) { - // num encoding both symbol and bits read - var sv = (i << 4) | cd[i]; - // free bits - var r_1 = mb - cd[i]; - // start value - var v = le[cd[i] - 1]++ << r_1; - // m is end value - for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { - // every 16 bit value starting with the code yields the same result - co[rev[v] >>> rvb] = sv; + // numbers here are at max 18 bits + var r = new u32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; + } + } + return [b, r]; + }; + var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; + // we can ignore the fact that the other numbers are wrong; they never happen anyway + fl[28] = 258, revfl[258] = 28; + var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; + // map of value to reverse (assuming 16 bits) + var rev = new u16(32768); + for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; + } + // create huffman tree from u8 "map": index -> code length for code index + // mb (max bits) must be at most 15 + // TODO: optimize/split up? + var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) + ++l[cd[i] - 1]; + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 0; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + if (r) { + // u16 "map": index -> number of actual bits, symbol for code + co = new u16(1 << mb); + // bits to remove for reverser + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + // ignore 0 lengths + if (cd[i]) { + // num encoding both symbol and bits read + var sv = (i << 4) | cd[i]; + // free bits + var r_1 = mb - cd[i]; + // start value + var v = le[cd[i] - 1]++ << r_1; + // m is end value + for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { + // every 16 bit value starting with the code yields the same result + co[rev[v] >>> rvb] = sv; + } } } } - } - else { - co = new u16(s); - for (i = 0; i < s; ++i) { - if (cd[i]) { - co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); + } } } - } - return co; -}); -// fixed length tree -var flt = new u8(288); -for (var i = 0; i < 144; ++i) - flt[i] = 8; -for (var i = 144; i < 256; ++i) - flt[i] = 9; -for (var i = 256; i < 280; ++i) - flt[i] = 7; -for (var i = 280; i < 288; ++i) - flt[i] = 8; -// fixed distance tree -var fdt = new u8(32); -for (var i = 0; i < 32; ++i) - fdt[i] = 5; -// fixed length map -var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); -// fixed distance map -var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); -// find max of array -var max = function (a) { - var m = a[0]; - for (var i = 1; i < a.length; ++i) { - if (a[i] > m) - m = a[i]; - } - return m; -}; -// read d, starting at bit p and mask with m -var bits = function (d, p, m) { - var o = (p / 8) | 0; - return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; -}; -// read d, starting at bit p continuing for at least 16 bits -var bits16 = function (d, p) { - var o = (p / 8) | 0; - return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); -}; -// get end of byte -var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; -// typed array slice - allows garbage collector to free original reference, -// while being more compatible than .slice -var slc = function (v, s, e) { - if (s == null || s < 0) - s = 0; - if (e == null || e > v.length) - e = v.length; - // can't use .constructor in case user-supplied - var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); - n.set(v.subarray(s, e)); - return n; -}; -// expands raw DEFLATE data -var inflt = function (dat, buf, st) { - // source length - var sl = dat.length; - if (!sl || (st && !st.l && sl < 5)) - return buf || new u8(0); - // have to estimate size - var noBuf = !buf || st; - // no state - var noSt = !st || st.i; - if (!st) - st = {}; - // Assumes roughly 33% compression ratio average - if (!buf) - buf = new u8(sl * 3); - // ensure buffer can fit at least l elements - var cbuf = function (l) { - var bl = buf.length; - // need to increase size to fit - if (l > bl) { - // Double or set to necessary, whichever is greater - var nbuf = new u8(Math.max(bl * 2, l)); - nbuf.set(buf); - buf = nbuf; + return co; + }); + // fixed length tree + var flt = new u8(288); + for (var i = 0; i < 144; ++i) + flt[i] = 8; + for (var i = 144; i < 256; ++i) + flt[i] = 9; + for (var i = 256; i < 280; ++i) + flt[i] = 7; + for (var i = 280; i < 288; ++i) + flt[i] = 8; + // fixed distance tree + var fdt = new u8(32); + for (var i = 0; i < 32; ++i) + fdt[i] = 5; + // fixed length map + var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); + // fixed distance map + var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); + // find max of array + var max = function (a) { + var m = a[0]; + for (var i = 1; i < a.length; ++i) { + if (a[i] > m) + m = a[i]; } + return m; }; - // last chunk bitpos bytes - var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; - // total bits - var tbts = sl * 8; - do { - if (!lm) { - // BFINAL - this is only 1 when last chunk is next - st.f = final = bits(dat, pos, 1); - // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman - var type = bits(dat, pos + 1, 3); - pos += 3; - if (!type) { - // go to end of byte boundary - var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; - if (t > sl) { - if (noSt) - throw 'unexpected EOF'; - break; - } - // ensure size - if (noBuf) - cbuf(bt + l); - // Copy over uncompressed data - buf.set(dat.subarray(s, t), bt); - // Get new bitpos, update byte count - st.b = bt += l, st.p = pos = t * 8; - continue; + // read d, starting at bit p and mask with m + var bits = function (d, p, m) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; + }; + // read d, starting at bit p continuing for at least 16 bits + var bits16 = function (d, p) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); + }; + // get end of byte + var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; + // typed array slice - allows garbage collector to free original reference, + // while being more compatible than .slice + var slc = function (v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); + n.set(v.subarray(s, e)); + return n; + }; + // expands raw DEFLATE data + var inflt = function (dat, buf, st) { + // source length + var sl = dat.length; + if (!sl || (st && !st.l && sl < 5)) + return buf || new u8(0); + // have to estimate size + var noBuf = !buf || st; + // no state + var noSt = !st || st.i; + if (!st) + st = {}; + // Assumes roughly 33% compression ratio average + if (!buf) + buf = new u8(sl * 3); + // ensure buffer can fit at least l elements + var cbuf = function (l) { + var bl = buf.length; + // need to increase size to fit + if (l > bl) { + // Double or set to necessary, whichever is greater + var nbuf = new u8(Math.max(bl * 2, l)); + nbuf.set(buf); + buf = nbuf; } - else if (type == 1) - lm = flrm, dm = fdrm, lbt = 9, dbt = 5; - else if (type == 2) { - // literal lengths - var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; - var tl = hLit + bits(dat, pos + 5, 31) + 1; - pos += 14; - // length+distance tree - var ldt = new u8(tl); - // code length tree - var clt = new u8(19); - for (var i = 0; i < hcLen; ++i) { - // use index map to get real code - clt[clim[i]] = bits(dat, pos + i * 3, 7); + }; + // last chunk bitpos bytes + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + // total bits + var tbts = sl * 8; + do { + if (!lm) { + // BFINAL - this is only 1 when last chunk is next + st.f = final = bits(dat, pos, 1); + // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + // go to end of byte boundary + var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; + if (t > sl) { + if (noSt) + throw 'unexpected EOF'; + break; + } + // ensure size + if (noBuf) + cbuf(bt + l); + // Copy over uncompressed data + buf.set(dat.subarray(s, t), bt); + // Get new bitpos, update byte count + st.b = bt += l, st.p = pos = t * 8; + continue; } - pos += hcLen * 3; - // code lengths bits - var clb = max(clt), clbmsk = (1 << clb) - 1; - // code lengths map - var clm = hMap(clt, clb, 1); - for (var i = 0; i < tl;) { - var r = clm[bits(dat, pos, clbmsk)]; - // bits read - pos += r & 15; - // symbol - var s = r >>> 4; - // code length to copy - if (s < 16) { - ldt[i++] = s; + else if (type == 1) + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + else if (type == 2) { + // literal lengths + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + // length+distance tree + var ldt = new u8(tl); + // code length tree + var clt = new u8(19); + for (var i = 0; i < hcLen; ++i) { + // use index map to get real code + clt[clim[i]] = bits(dat, pos + i * 3, 7); } - else { - // copy count - var c = 0, n = 0; - if (s == 16) - n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; - else if (s == 17) - n = 3 + bits(dat, pos, 7), pos += 3; - else if (s == 18) - n = 11 + bits(dat, pos, 127), pos += 7; - while (n--) - ldt[i++] = c; + pos += hcLen * 3; + // code lengths bits + var clb = max(clt), clbmsk = (1 << clb) - 1; + // code lengths map + var clm = hMap(clt, clb, 1); + for (var i = 0; i < tl;) { + var r = clm[bits(dat, pos, clbmsk)]; + // bits read + pos += r & 15; + // symbol + var s = r >>> 4; + // code length to copy + if (s < 16) { + ldt[i++] = s; + } + else { + // copy count + var c = 0, n = 0; + if (s == 16) + n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; + else if (s == 17) + n = 3 + bits(dat, pos, 7), pos += 3; + else if (s == 18) + n = 11 + bits(dat, pos, 127), pos += 7; + while (n--) + ldt[i++] = c; + } } + // length tree distance tree + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + // max length bits + lbt = max(lt); + // max dist bits + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); } - // length tree distance tree - var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); - // max length bits - lbt = max(lt); - // max dist bits - dbt = max(dt); - lm = hMap(lt, lbt, 1); - dm = hMap(dt, dbt, 1); - } - else - throw 'invalid block type'; - if (pos > tbts) { - if (noSt) - throw 'unexpected EOF'; - break; - } - } - // Make sure the buffer can hold this + the largest possible addition - // Maximum chunk size (practically, theoretically infinite) is 2^17; - if (noBuf) - cbuf(bt + 131072); - var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; - var lpos = pos; - for (;; lpos = pos) { - // bits read, code - var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; - pos += c & 15; - if (pos > tbts) { - if (noSt) - throw 'unexpected EOF'; - break; - } - if (!c) - throw 'invalid length/literal'; - if (sym < 256) - buf[bt++] = sym; - else if (sym == 256) { - lpos = pos, lm = null; - break; - } - else { - var add = sym - 254; - // no extra bits needed if less - if (sym > 264) { - // index - var i = sym - 257, b = fleb[i]; - add = bits(dat, pos, (1 << b) - 1) + fl[i]; - pos += b; - } - // dist - var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; - if (!d) - throw 'invalid distance'; - pos += d & 15; - var dt = fd[dsym]; - if (dsym > 3) { - var b = fdeb[dsym]; - dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; + else + throw 'invalid block type'; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; } + } + // Make sure the buffer can hold this + the largest possible addition + // Maximum chunk size (practically, theoretically infinite) is 2^17; + if (noBuf) + cbuf(bt + 131072); + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (;; lpos = pos) { + // bits read, code + var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; + pos += c & 15; if (pos > tbts) { if (noSt) throw 'unexpected EOF'; break; } - if (noBuf) - cbuf(bt + 131072); - var end = bt + add; - for (; bt < end; bt += 4) { - buf[bt] = buf[bt - dt]; - buf[bt + 1] = buf[bt + 1 - dt]; - buf[bt + 2] = buf[bt + 2 - dt]; - buf[bt + 3] = buf[bt + 3 - dt]; + if (!c) + throw 'invalid length/literal'; + if (sym < 256) + buf[bt++] = sym; + else if (sym == 256) { + lpos = pos, lm = null; + break; + } + else { + var add = sym - 254; + // no extra bits needed if less + if (sym > 264) { + // index + var i = sym - 257, b = fleb[i]; + add = bits(dat, pos, (1 << b) - 1) + fl[i]; + pos += b; + } + // dist + var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; + if (!d) + throw 'invalid distance'; + pos += d & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; + } + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + if (noBuf) + cbuf(bt + 131072); + var end = bt + add; + for (; bt < end; bt += 4) { + buf[bt] = buf[bt - dt]; + buf[bt + 1] = buf[bt + 1 - dt]; + buf[bt + 2] = buf[bt + 2 - dt]; + buf[bt + 3] = buf[bt + 3 - dt]; + } + bt = end; } - bt = end; } + st.l = lm, st.p = lpos, st.b = bt; + if (lm) + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } while (!final); + return bt == buf.length ? buf : slc(buf, 0, bt); + }; + // starting at p, write the minimum number of bits that can hold v to d + var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; + }; + // starting at p, write the minimum number of bits (>8) that can hold v to d + var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; + d[o + 2] |= v >>> 16; + }; + // creates code lengths from a frequency table + var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); } - st.l = lm, st.p = lpos, st.b = bt; - if (lm) - final = 1, st.m = lbt, st.d = dm, st.n = dbt; - } while (!final); - return bt == buf.length ? buf : slc(buf, 0, bt); -}; -// starting at p, write the minimum number of bits that can hold v to d -var wbits = function (d, p, v) { - v <<= p & 7; - var o = (p / 8) | 0; - d[o] |= v; - d[o + 1] |= v >>> 8; -}; -// starting at p, write the minimum number of bits (>8) that can hold v to d -var wbits16 = function (d, p, v) { - v <<= p & 7; - var o = (p / 8) | 0; - d[o] |= v; - d[o + 1] |= v >>> 8; - d[o + 2] |= v >>> 16; -}; -// creates code lengths from a frequency table -var hTree = function (d, mb) { - // Need extra info to make a tree - var t = []; - for (var i = 0; i < d.length; ++i) { - if (d[i]) - t.push({ s: i, f: d[i] }); - } - var s = t.length; - var t2 = t.slice(); - if (!s) - return [et, 0]; - if (s == 1) { - var v = new u8(t[0].s + 1); - v[t[0].s] = 1; - return [v, 1]; - } - t.sort(function (a, b) { return a.f - b.f; }); - // after i2 reaches last ind, will be stopped - // freq must be greater than largest possible number of symbols - t.push({ s: -1, f: 25001 }); - var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; - t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; - // efficient algorithm from UZIP.js - // i0 is lookbehind, i2 is lookahead - after processing two low-freq - // symbols that combined have high freq, will start processing i2 (high-freq, - // non-composite) symbols instead - // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ - while (i1 != s - 1) { - l = t[t[i0].f < t[i2].f ? i0++ : i2++]; - r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; - t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; - } - var maxSym = t2[0].s; - for (var i = 1; i < s; ++i) { - if (t2[i].s > maxSym) - maxSym = t2[i].s; - } - // code lengths - var tr = new u16(maxSym + 1); - // max bits in tree - var mbt = ln(t[i1 - 1], tr, 0); - if (mbt > mb) { - // more algorithms from UZIP.js - // TODO: find out how this code works (debt) - // ind debt - var i = 0, dt = 0; - // left cost - var lft = mbt - mb, cst = 1 << lft; - t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); - for (; i < s; ++i) { - var i2_1 = t2[i].s; - if (tr[i2_1] > mb) { - dt += cst - (1 << (mbt - tr[i2_1])); - tr[i2_1] = mb; - } - else - break; + var s = t.length; + var t2 = t.slice(); + if (!s) + return [et, 0]; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return [v, 1]; } - dt >>>= lft; - while (dt > 0) { - var i2_2 = t2[i].s; - if (tr[i2_2] < mb) - dt -= 1 << (mb - tr[i2_2]++ - 1); - else - ++i; + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; } - for (; i >= 0 && dt; --i) { - var i2_3 = t2[i].s; - if (tr[i2_3] == mb) { - --tr[i2_3]; - ++dt; - } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; } - mbt = mb; - } - return [new u8(tr), mbt]; -}; -// get the max length and assign length codes -var ln = function (n, l, d) { - return n.s == -1 - ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) - : (l[n.s] = d); -}; -// length codes generation -var lc = function (c) { - var s = c.length; - // Note that the semicolon was intentional - while (s && !c[--s]) - ; - var cl = new u16(++s); - // ind num streak - var cli = 0, cln = c[0], cls = 1; - var w = function (v) { cl[cli++] = v; }; - for (var i = 1; i <= s; ++i) { - if (c[i] == cln && i != s) - ++cls; - else { - if (!cln && cls > 2) { - for (; cls > 138; cls -= 138) - w(32754); - if (cls > 2) { - w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); - cls = 0; + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; } + else + break; + } + dt >>>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; } - else if (cls > 3) { - w(cln), --cls; - for (; cls > 6; cls -= 6) - w(8304); - if (cls > 2) - w(((cls - 3) << 5) | 8208), cls = 0; + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } } - while (cls--) - w(cln); - cls = 1; - cln = c[i]; + mbt = mb; } - } - return [cl.subarray(0, cli), s]; -}; -// calculate the length of output from tree, code lengths -var clen = function (cf, cl) { - var l = 0; - for (var i = 0; i < cl.length; ++i) - l += cf[i] * cl[i]; - return l; -}; -// writes a fixed block -// returns the new bit pos -var wfblk = function (out, pos, dat) { - // no need to write 00 as type: TypedArray defaults to 0 - var s = dat.length; - var o = shft(pos + 2); - out[o] = s & 255; - out[o + 1] = s >>> 8; - out[o + 2] = out[o] ^ 255; - out[o + 3] = out[o + 1] ^ 255; - for (var i = 0; i < s; ++i) - out[o + i + 4] = dat[i]; - return (o + 4 + s) * 8; -}; -// writes a block -var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { - wbits(out, p++, final); - ++lf[256]; - var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; - var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; - var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; - var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; - var lcfreq = new u16(19); - for (var i = 0; i < lclt.length; ++i) - lcfreq[lclt[i] & 31]++; - for (var i = 0; i < lcdt.length; ++i) - lcfreq[lcdt[i] & 31]++; - var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; - var nlcc = 19; - for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) - ; - var flen = (bl + 5) << 3; - var ftlen = clen(lf, flt) + clen(df, fdt) + eb; - var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); - if (flen <= ftlen && flen <= dtlen) - return wfblk(out, p, dat.subarray(bs, bs + bl)); - var lm, ll, dm, dl; - wbits(out, p, 1 + (dtlen < ftlen)), p += 2; - if (dtlen < ftlen) { - lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; - var llm = hMap(lct, mlcb, 0); - wbits(out, p, nlc - 257); - wbits(out, p + 5, ndc - 1); - wbits(out, p + 10, nlcc - 4); - p += 14; - for (var i = 0; i < nlcc; ++i) - wbits(out, p + 3 * i, lct[clim[i]]); - p += 3 * nlcc; - var lcts = [lclt, lcdt]; - for (var it = 0; it < 2; ++it) { - var clct = lcts[it]; - for (var i = 0; i < clct.length; ++i) { - var len = clct[i] & 31; - wbits(out, p, llm[len]), p += lct[len]; - if (len > 15) - wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; + return [new u8(tr), mbt]; + }; + // get the max length and assign length codes + var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); + }; + // length codes generation + var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; + } + } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; + } + while (cls--) + w(cln); + cls = 1; + cln = c[i]; } } - } - else { - lm = flm, ll = flt, dm = fdm, dl = fdt; - } - for (var i = 0; i < li; ++i) { - if (syms[i] > 255) { - var len = (syms[i] >>> 18) & 31; - wbits16(out, p, lm[len + 257]), p += ll[len + 257]; - if (len > 7) - wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; - var dst = syms[i] & 31; - wbits16(out, p, dm[dst]), p += dl[dst]; - if (dst > 3) - wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; + return [cl.subarray(0, cli), s]; + }; + // calculate the length of output from tree, code lengths + var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; + }; + // writes a fixed block + // returns the new bit pos + var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >>> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; + }; + // writes a block + var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; + var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; + var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; + var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + lcfreq[lclt[i] & 31]++; + for (var i = 0; i < lcdt.length; ++i) + lcfreq[lcdt[i] & 31]++; + var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); + if (flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; + var llm = hMap(lct, mlcb, 0); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; + } + } } else { - wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; + lm = flm, ll = flt, dm = fdm, dl = fdt; } - } - wbits16(out, p, lm[256]); - return p + ll[256]; -}; -// deflate options (nice << 13) | chain -var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); -// empty -var et = /*#__PURE__*/ new u8(0); -// compresses data into a raw DEFLATE buffer -var dflt = function (dat, lvl, plvl, pre, post, lst) { - var s = dat.length; - var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); - // writing to this writes to the output buffer - var w = o.subarray(pre, o.length - post); - var pos = 0; - if (!lvl || s < 8) { - for (var i = 0; i <= s; i += 65535) { - // end - var e = i + 65535; - if (e < s) { - // write full block - pos = wfblk(w, pos, dat.subarray(i, e)); + for (var i = 0; i < li; ++i) { + if (syms[i] > 255) { + var len = (syms[i] >>> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; + var dst = syms[i] & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; } else { - // write final block - w[i] = lst; - pos = wfblk(w, pos, dat.subarray(i, s)); + wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; } } - } - else { - var opt = deo[lvl - 1]; - var n = opt >>> 13, c = opt & 8191; - var msk_1 = (1 << plvl) - 1; - // prev 2-byte val map curr 2-byte val map - var prev = new u16(32768), head = new u16(msk_1 + 1); - var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; - var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; - // 24576 is an arbitrary number of maximum symbols per block - // 424 buffer for last block - var syms = new u32(25000); - // length/literal freq distance freq - var lf = new u16(288), df = new u16(32); - // l/lcnt exbits index l/lind waitdx bitpos - var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; - for (; i < s; ++i) { - // hash value - // deopt when i > s - 3 - at end, deopt acceptable - var hv = hsh(i); - // index mod 32768 previous index mod - var imod = i & 32767, pimod = head[hv]; - prev[imod] = pimod; - head[hv] = imod; - // We always should modify head and prev, but only add symbols if - // this data is not yet processed ("wait" for wait index) - if (wi <= i) { - // bytes remaining - var rem = s - i; - if ((lc_1 > 7000 || li > 24576) && rem > 423) { - pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); - li = lc_1 = eb = 0, bs = i; - for (var j = 0; j < 286; ++j) - lf[j] = 0; - for (var j = 0; j < 30; ++j) - df[j] = 0; + wbits16(out, p, lm[256]); + return p + ll[256]; + }; + // deflate options (nice << 13) | chain + var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); + // empty + var et = /*#__PURE__*/ new u8(0); + // compresses data into a raw DEFLATE buffer + var dflt = function (dat, lvl, plvl, pre, post, lst) { + var s = dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var pos = 0; + if (!lvl || s < 8) { + for (var i = 0; i <= s; i += 65535) { + // end + var e = i + 65535; + if (e < s) { + // write full block + pos = wfblk(w, pos, dat.subarray(i, e)); } - // len dist chain - var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; - if (rem > 2 && hv == hsh(i - dif)) { - var maxn = Math.min(n, rem) - 1; - var maxd = Math.min(32767, i); - // max possible length - // not capped at dif because decompressors implement "rolling" index population - var ml = Math.min(258, rem); - while (dif <= maxd && --ch_1 && imod != pimod) { - if (dat[i + l] == dat[i + l - dif]) { - var nl = 0; - for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) - ; - if (nl > l) { - l = nl, d = dif; - // break out early when we reach "nice" (we are satisfied enough) - if (nl > maxn) - break; - // now, find the rarest 2-byte sequence within this - // length of literals and search for that instead. - // Much faster than just using the start - var mmd = Math.min(dif, nl - 2); - var md = 0; - for (var j = 0; j < mmd; ++j) { - var ti = (i - dif + j + 32768) & 32767; - var pti = prev[ti]; - var cd = (ti - pti + 32768) & 32767; - if (cd > md) - md = cd, pimod = ti; + else { + // write final block + w[i] = lst; + pos = wfblk(w, pos, dat.subarray(i, s)); + } + } + } + else { + var opt = deo[lvl - 1]; + var n = opt >>> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = new u16(32768), head = new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new u32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx bitpos + var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; + for (; i < s; ++i) { + // hash value + // deopt when i > s - 3 - at end, deopt acceptable + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && rem > 423) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; + } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = (i - dif + j + 32768) & 32767; + var pti = prev[ti]; + var cd = (ti - pti + 32768) & 32767; + if (cd > md) + md = cd, pimod = ti; + } } } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += (imod - pimod + 32768) & 32767; } - // check the previous match - imod = pimod, pimod = prev[imod]; - dif += (imod - pimod + 32768) & 32767; } - } - // d will be nonzero only when a match was found - if (d) { - // store both dist and len data in one Uint32 - // Make sure this is recognized as a len/dist with 28th bit (2^28) - syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; - var lin = revfl[l] & 31, din = revfd[d] & 31; - eb += fleb[lin] + fdeb[din]; - ++lf[257 + lin]; - ++df[din]; - wi = i + l; - ++lc_1; - } - else { - syms[li++] = dat[i]; - ++lf[dat[i]]; + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one Uint32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } } } + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + // this is the easiest way to avoid needing to maintain state + if (!lst && pos & 7) + pos = wfblk(w, pos + 1, et); } - pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); - // this is the easiest way to avoid needing to maintain state - if (!lst && pos & 7) - pos = wfblk(w, pos + 1, et); - } - return slc(o, 0, pre + shft(pos) + post); -}; -// CRC32 table -var crct = /*#__PURE__*/ (function () { - var t = new u32(256); - for (var i = 0; i < 256; ++i) { - var c = i, k = 9; - while (--k) - c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); - t[i] = c; - } - return t; -})(); -// CRC32 -var crc = function () { - var c = -1; - return { - p: function (d) { - // closures have awful performance - var cr = c; - for (var i = 0; i < d.length; ++i) - cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); - c = cr; - }, - d: function () { return ~c; } + return slc(o, 0, pre + shft(pos) + post); }; -}; -// Alder32 -var adler = function () { - var a = 1, b = 0; - return { - p: function (d) { - // closures have awful performance - var n = a, m = b; - var l = d.length; - for (var i = 0; i != l;) { - var e = Math.min(i + 2655, l); - for (; i < e; ++i) - m += n += d[i]; - n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); - } - a = n, b = m; - }, - d: function () { - a %= 65521, b %= 65521; - return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); + // CRC32 table + var crct = /*#__PURE__*/ (function () { + var t = new u32(256); + for (var i = 0; i < 256; ++i) { + var c = i, k = 9; + while (--k) + c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); + t[i] = c; } + return t; + })(); + // CRC32 + var crc = function () { + var c = -1; + return { + p: function (d) { + // closures have awful performance + var cr = c; + for (var i = 0; i < d.length; ++i) + cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); + c = cr; + }, + d: function () { return ~c; } + }; }; -}; -; -// deflate with opts -var dopt = function (dat, opt, pre, post, st) { - return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); -}; -// Walmart object spread -var mrg = function (a, b) { - var o = {}; - for (var k in a) - o[k] = a[k]; - for (var k in b) - o[k] = b[k]; - return o; -}; -// worker clone -// This is possibly the craziest part of the entire codebase, despite how simple it may seem. -// The only parameter to this function is a closure that returns an array of variables outside of the function scope. -// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization. -// We will return an object mapping of true variable name to value (basically, the current scope as a JS object). -// The reason we can't just use the original variable names is minifiers mangling the toplevel scope. -// This took me three weeks to figure out how to do. -var wcln = function (fn, fnStr, td) { - var dt = fn(); - var st = fn.toString(); - var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); - for (var i = 0; i < dt.length; ++i) { - var v = dt[i], k = ks[i]; - if (typeof v == 'function') { - fnStr += ';' + k + '='; - var st_1 = v.toString(); - if (v.prototype) { - // for global objects - if (st_1.indexOf('[native code]') != -1) { - var spInd = st_1.indexOf(' ', 8) + 1; - fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd)); + // Alder32 + var adler = function () { + var a = 1, b = 0; + return { + p: function (d) { + // closures have awful performance + var n = a, m = b; + var l = d.length; + for (var i = 0; i != l;) { + var e = Math.min(i + 2655, l); + for (; i < e; ++i) + m += n += d[i]; + n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); } - else { - fnStr += st_1; - for (var t in v.prototype) - fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString(); + a = n, b = m; + }, + d: function () { + a %= 65521, b %= 65521; + return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); + } + }; + }; + ; + // deflate with opts + var dopt = function (dat, opt, pre, post, st) { + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); + }; + // Walmart object spread + var mrg = function (a, b) { + var o = {}; + for (var k in a) + o[k] = a[k]; + for (var k in b) + o[k] = b[k]; + return o; + }; + // worker clone + // This is possibly the craziest part of the entire codebase, despite how simple it may seem. + // The only parameter to this function is a closure that returns an array of variables outside of the function scope. + // We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization. + // We will return an object mapping of true variable name to value (basically, the current scope as a JS object). + // The reason we can't just use the original variable names is minifiers mangling the toplevel scope. + // This took me three weeks to figure out how to do. + var wcln = function (fn, fnStr, td) { + var dt = fn(); + var st = fn.toString(); + var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); + for (var i = 0; i < dt.length; ++i) { + var v = dt[i], k = ks[i]; + if (typeof v == 'function') { + fnStr += ';' + k + '='; + var st_1 = v.toString(); + if (v.prototype) { + // for global objects + if (st_1.indexOf('[native code]') != -1) { + var spInd = st_1.indexOf(' ', 8) + 1; + fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd)); + } + else { + fnStr += st_1; + for (var t in v.prototype) + fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString(); + } } + else + fnStr += st_1; } else - fnStr += st_1; + td[k] = v; } - else - td[k] = v; - } - return [fnStr, td]; -}; -var ch = []; -// clone bufs -var cbfs = function (v) { - var tl = []; - for (var k in v) { - if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) - tl.push((v[k] = new v[k].constructor(v[k])).buffer); - } - return tl; -}; -// use a worker to execute code -var wrkr = function (fns, init, id, cb) { - var _a; - if (!ch[id]) { - var fnStr = '', td_1 = {}, m = fns.length - 1; - for (var i = 0; i < m; ++i) - _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; - ch[id] = wcln(fns[m], fnStr, td_1); - } - var td = mrg({}, ch[id][1]); - return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); -}; -// base async inflate fn -var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; -var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; -// gzip extra -var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; -// gunzip extra -var guze = function () { return [gzs, gzl]; }; -// zlib extra -var zle = function () { return [zlh, wbytes, adler]; }; -// unzlib extra -var zule = function () { return [zlv]; }; -// post buf -var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; -// get u8 -var gu8 = function (o) { return o && o.size && new u8(o.size); }; -// async helper -var cbify = function (dat, opts, fns, init, id, cb) { - var w = wrkr(fns, init, id, function (err, dat) { - w.terminate(); - cb(err, dat); - }); - w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []); - return function () { w.terminate(); }; -}; -// auto stream -var astrm = function (strm) { - strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; - return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; -}; -// async stream attach -var astrmify = function (fns, strm, opts, init, id) { - var t; - var w = wrkr(fns, init, id, function (err, dat) { - if (err) - w.terminate(), strm.ondata.call(strm, err); - else { - if (dat[1]) - w.terminate(); - strm.ondata.call(strm, err, dat[0], dat[1]); + return [fnStr, td]; + }; + var ch = []; + // clone bufs + var cbfs = function (v) { + var tl = []; + for (var k in v) { + if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) + tl.push((v[k] = new v[k].constructor(v[k])).buffer); } - }); - w.postMessage(opts); - strm.push = function (d, f) { - if (t) - throw 'stream finished'; - if (!strm.ondata) - throw 'no stream handler'; - w.postMessage([d, t = f], [d.buffer]); + return tl; }; - strm.terminate = function () { w.terminate(); }; -}; -// read 2 bytes -var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; -// read 4 bytes -var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; -var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; -// write bytes -var wbytes = function (d, b, v) { - for (; v; ++b) - d[b] = v, v >>>= 8; -}; -// gzip header -var gzh = function (c, o) { - var fn = o.filename; - c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix - if (o.mtime != 0) - wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000)); - if (fn) { - c[3] = 8; - for (var i = 0; i <= fn.length; ++i) - c[i + 10] = fn.charCodeAt(i); - } -}; -// gzip footer: -8 to -4 = CRC, -4 to -0 is length -// gzip start -var gzs = function (d) { - if (d[0] != 31 || d[1] != 139 || d[2] != 8) - throw 'invalid gzip data'; - var flg = d[3]; - var st = 10; - if (flg & 4) - st += d[10] | (d[11] << 8) + 2; - for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) - ; - return st + (flg & 2); -}; -// gzip length -var gzl = function (d) { - var l = d.length; - return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; -}; -// gzip header length -var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; -// zlib header -var zlh = function (c, o) { - var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; - c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); -}; -// zlib valid -var zlv = function (d) { - if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) - throw 'invalid zlib data'; - if (d[1] & 32) - throw 'invalid zlib data: preset dictionaries not supported'; -}; -function AsyncCmpStrm(opts, cb) { - if (!cb && typeof opts == 'function') - cb = opts, opts = {}; - this.ondata = cb; - return opts; -} -// zlib footer: -4 to -0 is Adler32 -/** - * Streaming DEFLATE compression - */ -var Deflate = /*#__PURE__*/ (function () { - function Deflate(opts, cb) { + // use a worker to execute code + var wrkr = function (fns, init, id, cb) { + var _a; + if (!ch[id]) { + var fnStr = '', td_1 = {}, m = fns.length - 1; + for (var i = 0; i < m; ++i) + _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; + ch[id] = wcln(fns[m], fnStr, td_1); + } + var td = mrg({}, ch[id][1]); + return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); + }; + // base async inflate fn + var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; + var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; + // gzip extra + var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; + // gunzip extra + var guze = function () { return [gzs, gzl]; }; + // zlib extra + var zle = function () { return [zlh, wbytes, adler]; }; + // unzlib extra + var zule = function () { return [zlv]; }; + // post buf + var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; + // get u8 + var gu8 = function (o) { return o && o.size && new u8(o.size); }; + // async helper + var cbify = function (dat, opts, fns, init, id, cb) { + var w = wrkr(fns, init, id, function (err, dat) { + w.terminate(); + cb(err, dat); + }); + w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []); + return function () { w.terminate(); }; + }; + // auto stream + var astrm = function (strm) { + strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; + return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; + }; + // async stream attach + var astrmify = function (fns, strm, opts, init, id) { + var t; + var w = wrkr(fns, init, id, function (err, dat) { + if (err) + w.terminate(), strm.ondata.call(strm, err); + else { + if (dat[1]) + w.terminate(); + strm.ondata.call(strm, err, dat[0], dat[1]); + } + }); + w.postMessage(opts); + strm.push = function (d, f) { + if (t) + throw 'stream finished'; + if (!strm.ondata) + throw 'no stream handler'; + w.postMessage([d, t = f], [d.buffer]); + }; + strm.terminate = function () { w.terminate(); }; + }; + // read 2 bytes + var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; + // read 4 bytes + var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; + var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; + // write bytes + var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; + }; + // gzip header + var gzh = function (c, o) { + var fn = o.filename; + c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix + if (o.mtime != 0) + wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000)); + if (fn) { + c[3] = 8; + for (var i = 0; i <= fn.length; ++i) + c[i + 10] = fn.charCodeAt(i); + } + }; + // gzip footer: -8 to -4 = CRC, -4 to -0 is length + // gzip start + var gzs = function (d) { + if (d[0] != 31 || d[1] != 139 || d[2] != 8) + throw 'invalid gzip data'; + var flg = d[3]; + var st = 10; + if (flg & 4) + st += d[10] | (d[11] << 8) + 2; + for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) + ; + return st + (flg & 2); + }; + // gzip length + var gzl = function (d) { + var l = d.length; + return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; + }; + // gzip header length + var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; + // zlib header + var zlh = function (c, o) { + var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; + c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); + }; + // zlib valid + var zlv = function (d) { + if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) + throw 'invalid zlib data'; + if (d[1] & 32) + throw 'invalid zlib data: preset dictionaries not supported'; + }; + function AsyncCmpStrm(opts, cb) { if (!cb && typeof opts == 'function') cb = opts, opts = {}; this.ondata = cb; - this.o = opts || {}; + return opts; } - Deflate.prototype.p = function (c, f) { - this.ondata(dopt(c, this.o, 0, 0, !f), f); - }; + // zlib footer: -4 to -0 is Adler32 /** - * Pushes a chunk to be deflated - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming DEFLATE compression */ - Deflate.prototype.push = function (chunk, final) { - if (this.d) - throw 'stream finished'; - if (!this.ondata) - throw 'no stream handler'; - this.d = final; - this.p(chunk, final || false); - }; - return Deflate; -}()); -export { Deflate }; -/** - * Asynchronous streaming DEFLATE compression - */ -var AsyncDeflate = /*#__PURE__*/ (function () { - function AsyncDeflate(opts, cb) { - astrmify([ + var Deflate = /*#__PURE__*/ (function () { + function Deflate(opts, cb) { + if (!cb && typeof opts == 'function') + cb = opts, opts = {}; + this.ondata = cb; + this.o = opts || {}; + } + Deflate.prototype.p = function (c, f) { + this.ondata(dopt(c, this.o, 0, 0, !f), f); + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Deflate.prototype.push = function (chunk, final) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + this.d = final; + this.p(chunk, final || false); + }; + return Deflate; + }()); + + /** + * Asynchronous streaming DEFLATE compression + */ + var AsyncDeflate = /*#__PURE__*/ (function () { + function AsyncDeflate(opts, cb) { + astrmify([ + bDflt, + function () { return [astrm, Deflate]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Deflate(ev.data); + onmessage = astrm(strm); + }, 6); + } + return AsyncDeflate; + }()); + + function deflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ bDflt, - function () { return [astrm, Deflate]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { - var strm = new Deflate(ev.data); - onmessage = astrm(strm); - }, 6); + ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); } - return AsyncDeflate; -}()); -export { AsyncDeflate }; -export function deflate(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bDflt, - ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); -} -/** - * Compresses data with DEFLATE without any wrapper - * @param data The data to compress - * @param opts The compression options - * @returns The deflated version of the data - */ -export function deflateSync(data, opts) { - return dopt(data, opts || {}, 0, 0); -} -/** - * Streaming DEFLATE decompression - */ -var Inflate = /*#__PURE__*/ (function () { /** - * Creates an inflation stream - * @param cb The callback to call whenever data is inflated + * Compresses data with DEFLATE without any wrapper + * @param data The data to compress + * @param opts The compression options + * @returns The deflated version of the data */ - function Inflate(cb) { - this.s = {}; - this.p = new u8(0); - this.ondata = cb; + function deflateSync(data, opts) { + return dopt(data, opts || {}, 0, 0); } - Inflate.prototype.e = function (c) { - if (this.d) - throw 'stream finished'; - if (!this.ondata) - throw 'no stream handler'; - var l = this.p.length; - var n = new u8(l + c.length); - n.set(this.p), n.set(c, l), this.p = n; - }; - Inflate.prototype.c = function (final) { - this.d = this.s.i = final || false; - var bts = this.s.b; - var dt = inflt(this.p, this.o, this.s); - this.ondata(slc(dt, bts, this.s.b), this.d); - this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; - this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; - }; /** - * Pushes a chunk to be inflated - * @param chunk The chunk to push - * @param final Whether this is the final chunk + * Streaming DEFLATE decompression */ - Inflate.prototype.push = function (chunk, final) { - this.e(chunk), this.c(final); - }; - return Inflate; -}()); -export { Inflate }; -/** - * Asynchronous streaming DEFLATE decompression - */ -var AsyncInflate = /*#__PURE__*/ (function () { + var Inflate = /*#__PURE__*/ (function () { + /** + * Creates an inflation stream + * @param cb The callback to call whenever data is inflated + */ + function Inflate(cb) { + this.s = {}; + this.p = new u8(0); + this.ondata = cb; + } + Inflate.prototype.e = function (c) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + var l = this.p.length; + var n = new u8(l + c.length); + n.set(this.p), n.set(c, l), this.p = n; + }; + Inflate.prototype.c = function (final) { + this.d = this.s.i = final || false; + var bts = this.s.b; + var dt = inflt(this.p, this.o, this.s); + this.ondata(slc(dt, bts, this.s.b), this.d); + this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; + this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; + }; + /** + * Pushes a chunk to be inflated + * @param chunk The chunk to push + * @param final Whether this is the final chunk + */ + Inflate.prototype.push = function (chunk, final) { + this.e(chunk), this.c(final); + }; + return Inflate; + }()); + /** - * Creates an asynchronous inflation stream - * @param cb The callback to call whenever data is deflated + * Asynchronous streaming DEFLATE decompression */ - function AsyncInflate(cb) { - this.ondata = cb; - astrmify([ - bInflt, - function () { return [astrm, Inflate]; } - ], this, 0, function () { - var strm = new Inflate(); - onmessage = astrm(strm); - }, 7); + var AsyncInflate = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous inflation stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncInflate(cb) { + this.ondata = cb; + astrmify([ + bInflt, + function () { return [astrm, Inflate]; } + ], this, 0, function () { + var strm = new Inflate(); + onmessage = astrm(strm); + }, 7); + } + return AsyncInflate; + }()); + + function inflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt + ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); } - return AsyncInflate; -}()); -export { AsyncInflate }; -export function inflate(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bInflt - ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); -} -/** - * Expands DEFLATE data with no wrapper - * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. - * @returns The decompressed version of the data - */ -export function inflateSync(data, out) { - return inflt(data, out); -} -// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. -/** - * Streaming GZIP compression - */ -var Gzip = /*#__PURE__*/ (function () { - function Gzip(opts, cb) { - this.c = crc(); - this.l = 0; - this.v = 1; - Deflate.call(this, opts, cb); + /** + * Expands DEFLATE data with no wrapper + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ + function inflateSync(data, out) { + return inflt(data, out); } + // before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. /** - * Pushes a chunk to be GZIPped - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming GZIP compression */ - Gzip.prototype.push = function (chunk, final) { - Deflate.prototype.push.call(this, chunk, final); - }; - Gzip.prototype.p = function (c, f) { - this.c.p(c); - this.l += c.length; - var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); - if (this.v) - gzh(raw, this.o), this.v = 0; - if (f) - wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); - this.ondata(raw, f); - }; - return Gzip; -}()); -export { Gzip }; -/** - * Asynchronous streaming GZIP compression - */ -var AsyncGzip = /*#__PURE__*/ (function () { - function AsyncGzip(opts, cb) { - astrmify([ + var Gzip = /*#__PURE__*/ (function () { + function Gzip(opts, cb) { + this.c = crc(); + this.l = 0; + this.v = 1; + Deflate.call(this, opts, cb); + } + /** + * Pushes a chunk to be GZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Gzip.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Gzip.prototype.p = function (c, f) { + this.c.p(c); + this.l += c.length; + var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); + if (this.v) + gzh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); + this.ondata(raw, f); + }; + return Gzip; + }()); + + /** + * Asynchronous streaming GZIP compression + */ + var AsyncGzip = /*#__PURE__*/ (function () { + function AsyncGzip(opts, cb) { + astrmify([ + bDflt, + gze, + function () { return [astrm, Deflate, Gzip]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Gzip(ev.data); + onmessage = astrm(strm); + }, 8); + } + return AsyncGzip; + }()); + + function gzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ bDflt, gze, - function () { return [astrm, Deflate, Gzip]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { - var strm = new Gzip(ev.data); - onmessage = astrm(strm); - }, 8); + function () { return [gzipSync]; } + ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb); } - return AsyncGzip; -}()); -export { AsyncGzip }; -export function gzip(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bDflt, - gze, - function () { return [gzipSync]; } - ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb); -} -/** - * Compresses data with GZIP - * @param data The data to compress - * @param opts The compression options - * @returns The gzipped version of the data - */ -export function gzipSync(data, opts) { - if (!opts) - opts = {}; - var c = crc(), l = data.length; - c.p(data); - var d = dopt(data, opts, gzhl(opts), 8), s = d.length; - return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; -} -/** - * Streaming GZIP decompression - */ -var Gunzip = /*#__PURE__*/ (function () { /** - * Creates a GUNZIP stream - * @param cb The callback to call whenever data is inflated + * Compresses data with GZIP + * @param data The data to compress + * @param opts The compression options + * @returns The gzipped version of the data */ - function Gunzip(cb) { - this.v = 1; - Inflate.call(this, cb); + function gzipSync(data, opts) { + if (!opts) + opts = {}; + var c = crc(), l = data.length; + c.p(data); + var d = dopt(data, opts, gzhl(opts), 8), s = d.length; + return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; } /** - * Pushes a chunk to be GUNZIPped - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming GZIP decompression */ - Gunzip.prototype.push = function (chunk, final) { - Inflate.prototype.e.call(this, chunk); - if (this.v) { - var s = this.p.length > 3 ? gzs(this.p) : 4; - if (s >= this.p.length && !final) - return; - this.p = this.p.subarray(s), this.v = 0; + var Gunzip = /*#__PURE__*/ (function () { + /** + * Creates a GUNZIP stream + * @param cb The callback to call whenever data is inflated + */ + function Gunzip(cb) { + this.v = 1; + Inflate.call(this, cb); } - if (final) { - if (this.p.length < 8) - throw 'invalid gzip stream'; - this.p = this.p.subarray(0, -8); - } - // necessary to prevent TS from using the closure value - // This allows for workerization to function correctly - Inflate.prototype.c.call(this, final); - }; - return Gunzip; -}()); -export { Gunzip }; -/** - * Asynchronous streaming GZIP decompression - */ -var AsyncGunzip = /*#__PURE__*/ (function () { + /** + * Pushes a chunk to be GUNZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Gunzip.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + var s = this.p.length > 3 ? gzs(this.p) : 4; + if (s >= this.p.length && !final) + return; + this.p = this.p.subarray(s), this.v = 0; + } + if (final) { + if (this.p.length < 8) + throw 'invalid gzip stream'; + this.p = this.p.subarray(0, -8); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Gunzip; + }()); + /** - * Creates an asynchronous GUNZIP stream - * @param cb The callback to call whenever data is deflated + * Asynchronous streaming GZIP decompression */ - function AsyncGunzip(cb) { - this.ondata = cb; - astrmify([ + var AsyncGunzip = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous GUNZIP stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncGunzip(cb) { + this.ondata = cb; + astrmify([ + bInflt, + guze, + function () { return [astrm, Inflate, Gunzip]; } + ], this, 0, function () { + var strm = new Gunzip(); + onmessage = astrm(strm); + }, 9); + } + return AsyncGunzip; + }()); + + function gunzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ bInflt, guze, - function () { return [astrm, Inflate, Gunzip]; } - ], this, 0, function () { - var strm = new Gunzip(); - onmessage = astrm(strm); - }, 9); + function () { return [gunzipSync]; } + ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); } - return AsyncGunzip; -}()); -export { AsyncGunzip }; -export function gunzip(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bInflt, - guze, - function () { return [gunzipSync]; } - ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); -} -/** - * Expands GZIP data - * @param data The data to decompress - * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. - * @returns The decompressed version of the data - */ -export function gunzipSync(data, out) { - return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); -} -/** - * Streaming Zlib compression - */ -var Zlib = /*#__PURE__*/ (function () { - function Zlib(opts, cb) { - this.c = adler(); - this.v = 1; - Deflate.call(this, opts, cb); + /** + * Expands GZIP data + * @param data The data to decompress + * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. + * @returns The decompressed version of the data + */ + function gunzipSync(data, out) { + return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); } /** - * Pushes a chunk to be zlibbed - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming Zlib compression */ - Zlib.prototype.push = function (chunk, final) { - Deflate.prototype.push.call(this, chunk, final); - }; - Zlib.prototype.p = function (c, f) { - this.c.p(c); - var raw = dopt(c, this.o, this.v && 2, f && 4, !f); - if (this.v) - zlh(raw, this.o), this.v = 0; - if (f) - wbytes(raw, raw.length - 4, this.c.d()); - this.ondata(raw, f); - }; - return Zlib; -}()); -export { Zlib }; -/** - * Asynchronous streaming Zlib compression - */ -var AsyncZlib = /*#__PURE__*/ (function () { - function AsyncZlib(opts, cb) { - astrmify([ + var Zlib = /*#__PURE__*/ (function () { + function Zlib(opts, cb) { + this.c = adler(); + this.v = 1; + Deflate.call(this, opts, cb); + } + /** + * Pushes a chunk to be zlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Zlib.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Zlib.prototype.p = function (c, f) { + this.c.p(c); + var raw = dopt(c, this.o, this.v && 2, f && 4, !f); + if (this.v) + zlh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 4, this.c.d()); + this.ondata(raw, f); + }; + return Zlib; + }()); + + /** + * Asynchronous streaming Zlib compression + */ + var AsyncZlib = /*#__PURE__*/ (function () { + function AsyncZlib(opts, cb) { + astrmify([ + bDflt, + zle, + function () { return [astrm, Deflate, Zlib]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Zlib(ev.data); + onmessage = astrm(strm); + }, 10); + } + return AsyncZlib; + }()); + + function zlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ bDflt, zle, - function () { return [astrm, Deflate, Zlib]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { - var strm = new Zlib(ev.data); - onmessage = astrm(strm); - }, 10); + function () { return [zlibSync]; } + ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb); } - return AsyncZlib; -}()); -export { AsyncZlib }; -export function zlib(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bDflt, - zle, - function () { return [zlibSync]; } - ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb); -} -/** - * Compress data with Zlib - * @param data The data to compress - * @param opts The compression options - * @returns The zlib-compressed version of the data - */ -export function zlibSync(data, opts) { - if (!opts) - opts = {}; - var a = adler(); - a.p(data); - var d = dopt(data, opts, 2, 4); - return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; -} -/** - * Streaming Zlib decompression - */ -var Unzlib = /*#__PURE__*/ (function () { /** - * Creates a Zlib decompression stream - * @param cb The callback to call whenever data is inflated + * Compress data with Zlib + * @param data The data to compress + * @param opts The compression options + * @returns The zlib-compressed version of the data */ - function Unzlib(cb) { - this.v = 1; - Inflate.call(this, cb); + function zlibSync(data, opts) { + if (!opts) + opts = {}; + var a = adler(); + a.p(data); + var d = dopt(data, opts, 2, 4); + return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; } /** - * Pushes a chunk to be unzlibbed - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming Zlib decompression */ - Unzlib.prototype.push = function (chunk, final) { - Inflate.prototype.e.call(this, chunk); - if (this.v) { - if (this.p.length < 2 && !final) - return; - this.p = this.p.subarray(2), this.v = 0; + var Unzlib = /*#__PURE__*/ (function () { + /** + * Creates a Zlib decompression stream + * @param cb The callback to call whenever data is inflated + */ + function Unzlib(cb) { + this.v = 1; + Inflate.call(this, cb); } - if (final) { - if (this.p.length < 4) - throw 'invalid zlib stream'; - this.p = this.p.subarray(0, -4); - } - // necessary to prevent TS from using the closure value - // This allows for workerization to function correctly - Inflate.prototype.c.call(this, final); - }; - return Unzlib; -}()); -export { Unzlib }; -/** - * Asynchronous streaming Zlib decompression - */ -var AsyncUnzlib = /*#__PURE__*/ (function () { + /** + * Pushes a chunk to be unzlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Unzlib.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + if (this.p.length < 2 && !final) + return; + this.p = this.p.subarray(2), this.v = 0; + } + if (final) { + if (this.p.length < 4) + throw 'invalid zlib stream'; + this.p = this.p.subarray(0, -4); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Unzlib; + }()); + /** - * Creates an asynchronous Zlib decompression stream - * @param cb The callback to call whenever data is deflated + * Asynchronous streaming Zlib decompression */ - function AsyncUnzlib(cb) { - this.ondata = cb; - astrmify([ + var AsyncUnzlib = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous Zlib decompression stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncUnzlib(cb) { + this.ondata = cb; + astrmify([ + bInflt, + zule, + function () { return [astrm, Inflate, Unzlib]; } + ], this, 0, function () { + var strm = new Unzlib(); + onmessage = astrm(strm); + }, 11); + } + return AsyncUnzlib; + }()); + + function unzlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ bInflt, zule, - function () { return [astrm, Inflate, Unzlib]; } - ], this, 0, function () { - var strm = new Unzlib(); - onmessage = astrm(strm); - }, 11); + function () { return [unzlibSync]; } + ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); } - return AsyncUnzlib; -}()); -export { AsyncUnzlib }; -export function unzlib(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bInflt, - zule, - function () { return [unzlibSync]; } - ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); -} -/** - * Expands Zlib data - * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. - * @returns The decompressed version of the data - */ -export function unzlibSync(data, out) { - return inflt((zlv(data), data.subarray(2, -4)), out); -} -// Default algorithm for compression (used because having a known output size allows faster decompression) -export { gzip as compress, AsyncGzip as AsyncCompress }; -// Default algorithm for compression (used because having a known output size allows faster decompression) -export { gzipSync as compressSync, Gzip as Compress }; -/** - * Streaming GZIP, Zlib, or raw DEFLATE decompression - */ -var Decompress = /*#__PURE__*/ (function () { /** - * Creates a decompression stream - * @param cb The callback to call whenever data is decompressed + * Expands Zlib data + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data */ - function Decompress(cb) { - this.G = Gunzip; - this.I = Inflate; - this.Z = Unzlib; - this.ondata = cb; + function unzlibSync(data, out) { + return inflt((zlv(data), data.subarray(2, -4)), out); } + /** - * Pushes a chunk to be decompressed - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming GZIP, Zlib, or raw DEFLATE decompression */ - Decompress.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no stream handler'; - if (!this.s) { - if (this.p && this.p.length) { - var n = new u8(this.p.length + chunk.length); - n.set(this.p), n.set(chunk, this.p.length); + var Decompress = /*#__PURE__*/ (function () { + /** + * Creates a decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function Decompress(cb) { + this.G = Gunzip; + this.I = Inflate; + this.Z = Unzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Decompress.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no stream handler'; + if (!this.s) { + if (this.p && this.p.length) { + var n = new u8(this.p.length + chunk.length); + n.set(this.p), n.set(chunk, this.p.length); + } + else + this.p = chunk; + if (this.p.length > 2) { + var _this_1 = this; + var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; + this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) + ? new this.G(cb) + : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) + ? new this.I(cb) + : new this.Z(cb); + this.s.push(this.p, final); + this.p = null; + } } else - this.p = chunk; - if (this.p.length > 2) { - var _this_1 = this; - var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; - this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) - ? new this.G(cb) - : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) - ? new this.I(cb) - : new this.Z(cb); - this.s.push(this.p, final); - this.p = null; - } - } - else - this.s.push(chunk, final); - }; - return Decompress; -}()); -export { Decompress }; -/** - * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression - */ -var AsyncDecompress = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous decompression stream - * @param cb The callback to call whenever data is decompressed - */ - function AsyncDecompress(cb) { - this.G = AsyncGunzip; - this.I = AsyncInflate; - this.Z = AsyncUnzlib; - this.ondata = cb; - } + this.s.push(chunk, final); + }; + return Decompress; + }()); + /** - * Pushes a chunk to be decompressed - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression */ - AsyncDecompress.prototype.push = function (chunk, final) { - Decompress.prototype.push.call(this, chunk, final); - }; - return AsyncDecompress; -}()); -export { AsyncDecompress }; -export function decompress(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return (data[0] == 31 && data[1] == 139 && data[2] == 8) - ? gunzip(data, opts, cb) - : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) - ? inflate(data, opts, cb) - : unzlib(data, opts, cb); -} -/** - * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format - * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. - * @returns The decompressed version of the data - */ -export function decompressSync(data, out) { - return (data[0] == 31 && data[1] == 139 && data[2] == 8) - ? gunzipSync(data, out) - : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) - ? inflateSync(data, out) - : unzlibSync(data, out); -} -// flatten a directory structure -var fltn = function (d, p, t, o) { - for (var k in d) { - var val = d[k], n = p + k; - if (val instanceof u8) - t[n] = [val, o]; - else if (Array.isArray(val)) - t[n] = [val[0], mrg(o, val[1])]; - else - fltn(val, n + '/', t, o); - } -}; -// text encoder -var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); -// text decoder -var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); -// text decoder stream -var tds = 0; -try { - td.decode(et, { stream: true }); - tds = 1; -} -catch (e) { } -// decode UTF8 -var dutf8 = function (d) { - for (var r = '', i = 0;;) { - var c = d[i++]; - var eb = (c > 127) + (c > 223) + (c > 239); - if (i + eb > d.length) - return [r, slc(d, i - 1)]; - if (!eb) - r += String.fromCharCode(c); - else if (eb == 3) { - c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, - r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); - } - else if (eb & 1) - r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); - else - r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); + var AsyncDecompress = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function AsyncDecompress(cb) { + this.G = AsyncGunzip; + this.I = AsyncInflate; + this.Z = AsyncUnzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + AsyncDecompress.prototype.push = function (chunk, final) { + Decompress.prototype.push.call(this, chunk, final); + }; + return AsyncDecompress; + }()); + + function decompress(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzip(data, opts, cb) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflate(data, opts, cb) + : unzlib(data, opts, cb); } -}; -/** - * Streaming UTF-8 decoding - */ -var DecodeUTF8 = /*#__PURE__*/ (function () { /** - * Creates a UTF-8 decoding stream - * @param cb The callback to call whenever data is decoded + * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data */ - function DecodeUTF8(cb) { - this.ondata = cb; - if (tds) - this.t = new TextDecoder(); - else - this.p = et; - } + function decompressSync(data, out) { + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzipSync(data, out) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflateSync(data, out) + : unzlibSync(data, out); + } + // flatten a directory structure + var fltn = function (d, p, t, o) { + for (var k in d) { + var val = d[k], n = p + k; + if (val instanceof u8) + t[n] = [val, o]; + else if (Array.isArray(val)) + t[n] = [val[0], mrg(o, val[1])]; + else + fltn(val, n + '/', t, o); + } + }; + // text encoder + var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); + // text decoder + var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); + // text decoder stream + var tds = 0; + try { + td.decode(et, { stream: true }); + tds = 1; + } + catch (e) { } + // decode UTF8 + var dutf8 = function (d) { + for (var r = '', i = 0;;) { + var c = d[i++]; + var eb = (c > 127) + (c > 223) + (c > 239); + if (i + eb > d.length) + return [r, slc(d, i - 1)]; + if (!eb) + r += String.fromCharCode(c); + else if (eb == 3) { + c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, + r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); + } + else if (eb & 1) + r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); + else + r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); + } + }; /** - * Pushes a chunk to be decoded from UTF-8 binary - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * Streaming UTF-8 decoding */ - DecodeUTF8.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no callback'; - final = !!final; - if (this.t) { - this.ondata(this.t.decode(chunk, { stream: true }), final); + var DecodeUTF8 = /*#__PURE__*/ (function () { + /** + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is decoded + */ + function DecodeUTF8(cb) { + this.ondata = cb; + if (tds) + this.t = new TextDecoder(); + else + this.p = et; + } + /** + * Pushes a chunk to be decoded from UTF-8 binary + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + DecodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + final = !!final; + if (this.t) { + this.ondata(this.t.decode(chunk, { stream: true }), final); + if (final) { + if (this.t.decode().length) + throw 'invalid utf-8 data'; + this.t = null; + } + return; + } + if (!this.p) + throw 'stream finished'; + var dat = new u8(this.p.length + chunk.length); + dat.set(this.p); + dat.set(chunk, this.p.length); + var _a = dutf8(dat), ch = _a[0], np = _a[1]; if (final) { - if (this.t.decode().length) + if (np.length) throw 'invalid utf-8 data'; - this.t = null; + this.p = null; } - return; - } - if (!this.p) - throw 'stream finished'; - var dat = new u8(this.p.length + chunk.length); - dat.set(this.p); - dat.set(chunk, this.p.length); - var _a = dutf8(dat), ch = _a[0], np = _a[1]; - if (final) { - if (np.length) - throw 'invalid utf-8 data'; - this.p = null; - } - else - this.p = np; - this.ondata(ch, final); - }; - return DecodeUTF8; -}()); -export { DecodeUTF8 }; -/** - * Streaming UTF-8 encoding - */ -var EncodeUTF8 = /*#__PURE__*/ (function () { + else + this.p = np; + this.ondata(ch, final); + }; + return DecodeUTF8; + }()); + /** - * Creates a UTF-8 decoding stream - * @param cb The callback to call whenever data is encoded + * Streaming UTF-8 encoding */ - function EncodeUTF8(cb) { - this.ondata = cb; - } + var EncodeUTF8 = /*#__PURE__*/ (function () { + /** + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is encoded + */ + function EncodeUTF8(cb) { + this.ondata = cb; + } + /** + * Pushes a chunk to be encoded to UTF-8 + * @param chunk The string data to push + * @param final Whether this is the last chunk + */ + EncodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + if (this.d) + throw 'stream finished'; + this.ondata(strToU8(chunk), this.d = final || false); + }; + return EncodeUTF8; + }()); + /** - * Pushes a chunk to be encoded to UTF-8 - * @param chunk The string data to push - * @param final Whether this is the last chunk + * Converts a string into a Uint8Array for use with compression/decompression methods + * @param str The string to encode + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless decoding a binary string. + * @returns The string encoded in UTF-8/Latin-1 binary */ - EncodeUTF8.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no callback'; - if (this.d) - throw 'stream finished'; - this.ondata(strToU8(chunk), this.d = final || false); - }; - return EncodeUTF8; -}()); -export { EncodeUTF8 }; -/** - * Converts a string into a Uint8Array for use with compression/decompression methods - * @param str The string to encode - * @param latin1 Whether or not to interpret the data as Latin-1. This should - * not need to be true unless decoding a binary string. - * @returns The string encoded in UTF-8/Latin-1 binary - */ -export function strToU8(str, latin1) { - if (latin1) { - var ar_1 = new u8(str.length); - for (var i = 0; i < str.length; ++i) - ar_1[i] = str.charCodeAt(i); - return ar_1; - } - if (te) - return te.encode(str); - var l = str.length; - var ar = new u8(str.length + (str.length >> 1)); - var ai = 0; - var w = function (v) { ar[ai++] = v; }; - for (var i = 0; i < l; ++i) { - if (ai + 5 > ar.length) { - var n = new u8(ai + 8 + ((l - i) << 1)); - n.set(ar); - ar = n; - } - var c = str.charCodeAt(i); - if (c < 128 || latin1) - w(c); - else if (c < 2048) - w(192 | (c >> 6)), w(128 | (c & 63)); - else if (c > 55295 && c < 57344) - c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), - w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); - else - w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); - } - return slc(ar, 0, ai); -} -/** - * Converts a Uint8Array to a string - * @param dat The data to decode to string - * @param latin1 Whether or not to interpret the data as Latin-1. This should - * not need to be true unless encoding to binary string. - * @returns The original UTF-8/Latin-1 string - */ -export function strFromU8(dat, latin1) { - if (latin1) { - var r = ''; - for (var i = 0; i < dat.length; i += 16384) - r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); - return r; - } - else if (td) - return td.decode(dat); - else { - var _a = dutf8(dat), out = _a[0], ext = _a[1]; - if (ext.length) - throw 'invalid utf-8 data'; - return out; - } -} -; -// deflate bit flag -var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; -// skip local zip header -var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; -// read zip header -var zh = function (d, b, z) { - var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); - var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; - return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; -}; -// read zip64 extra field -var z64e = function (d, b) { - for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) - ; - return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; -}; -// extra field length -var exfl = function (ex) { - var le = 0; - if (ex) { - for (var k in ex) { - var l = ex[k].length; - if (l > 65535) - throw 'extra field too long'; - le += l + 4; + function strToU8(str, latin1) { + if (latin1) { + var ar_1 = new u8(str.length); + for (var i = 0; i < str.length; ++i) + ar_1[i] = str.charCodeAt(i); + return ar_1; } - } - return le; -}; -// write zip header -var wzh = function (d, b, f, fn, u, c, ce, co) { - var fl = fn.length, ex = f.extra, col = co && co.length; - var exl = exfl(ex); - wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; - if (ce != null) - d[b++] = 20, d[b++] = f.os; - d[b] = 20, b += 2; // spec compliance? what's that? - d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; - d[b++] = f.compression & 255, d[b++] = f.compression >> 8; - var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; - if (y < 0 || y > 119) - throw 'date not in range 1980-2099'; - wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; - if (c != null) { - wbytes(d, b, f.crc); - wbytes(d, b + 4, c); - wbytes(d, b + 8, f.size); - } - wbytes(d, b + 12, fl); - wbytes(d, b + 14, exl), b += 16; - if (ce != null) { - wbytes(d, b, col); - wbytes(d, b + 6, f.attrs); - wbytes(d, b + 10, ce), b += 14; - } - d.set(fn, b); - b += fl; - if (exl) { - for (var k in ex) { - var exf = ex[k], l = exf.length; - wbytes(d, b, +k); - wbytes(d, b + 2, l); - d.set(exf, b + 4), b += 4 + l; + if (te) + return te.encode(str); + var l = str.length; + var ar = new u8(str.length + (str.length >> 1)); + var ai = 0; + var w = function (v) { ar[ai++] = v; }; + for (var i = 0; i < l; ++i) { + if (ai + 5 > ar.length) { + var n = new u8(ai + 8 + ((l - i) << 1)); + n.set(ar); + ar = n; + } + var c = str.charCodeAt(i); + if (c < 128 || latin1) + w(c); + else if (c < 2048) + w(192 | (c >> 6)), w(128 | (c & 63)); + else if (c > 55295 && c < 57344) + c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), + w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + else + w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); } + return slc(ar, 0, ai); } - if (col) - d.set(co, b), b += col; - return b; -}; -// write zip footer (end of central directory) -var wzf = function (o, b, c, d, e) { - wbytes(o, b, 0x6054B50); // skip disk - wbytes(o, b + 8, c); - wbytes(o, b + 10, c); - wbytes(o, b + 12, d); - wbytes(o, b + 16, e); -}; -/** - * A pass-through stream to keep data uncompressed in a ZIP archive. - */ -var ZipPassThrough = /*#__PURE__*/ (function () { /** - * Creates a pass-through stream that can be added to ZIP archives - * @param filename The filename to associate with this data stream + * Converts a Uint8Array to a string + * @param dat The data to decode to string + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless encoding to binary string. + * @returns The original UTF-8/Latin-1 string */ - function ZipPassThrough(filename) { - this.filename = filename; - this.c = crc(); - this.size = 0; - this.compression = 0; + function strFromU8(dat, latin1) { + if (latin1) { + var r = ''; + for (var i = 0; i < dat.length; i += 16384) + r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); + return r; + } + else if (td) + return td.decode(dat); + else { + var _a = dutf8(dat), out = _a[0], ext = _a[1]; + if (ext.length) + throw 'invalid utf-8 data'; + return out; + } } - /** - * Processes a chunk and pushes to the output stream. You can override this - * method in a subclass for custom behavior, but by default this passes - * the data through. You must call this.ondata(err, chunk, final) at some - * point in this method. - * @param chunk The chunk to process - * @param final Whether this is the last chunk - */ - ZipPassThrough.prototype.process = function (chunk, final) { - this.ondata(null, chunk, final); + ; + // deflate bit flag + var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; + // skip local zip header + var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; + // read zip header + var zh = function (d, b, z) { + var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); + var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; + return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; }; - /** - * Pushes a chunk to be added. If you are subclassing this with a custom - * compression algorithm, note that you must push data from the source - * file only, pre-compression. - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - ZipPassThrough.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no callback - add to ZIP archive before pushing'; - this.c.p(chunk); - this.size += chunk.length; - if (final) - this.crc = this.c.d(); - this.process(chunk, final || false); + // read zip64 extra field + var z64e = function (d, b) { + for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) + ; + return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; }; - return ZipPassThrough; -}()); -export { ZipPassThrough }; -// I don't extend because TypeScript extension adds 1kB of runtime bloat -/** - * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate - * for better performance - */ -var ZipDeflate = /*#__PURE__*/ (function () { - /** - * Creates a DEFLATE stream that can be added to ZIP archives - * @param filename The filename to associate with this data stream - * @param opts The compression options - */ - function ZipDeflate(filename, opts) { - var _this_1 = this; - if (!opts) - opts = {}; - ZipPassThrough.call(this, filename); - this.d = new Deflate(opts, function (dat, final) { - _this_1.ondata(null, dat, final); - }); - this.compression = 8; - this.flag = dbf(opts.level); - } - ZipDeflate.prototype.process = function (chunk, final) { - try { - this.d.push(chunk, final); - } - catch (e) { - this.ondata(e, null, final); + // extra field length + var exfl = function (ex) { + var le = 0; + if (ex) { + for (var k in ex) { + var l = ex[k].length; + if (l > 65535) + throw 'extra field too long'; + le += l + 4; + } } + return le; }; - /** - * Pushes a chunk to be deflated - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - ZipDeflate.prototype.push = function (chunk, final) { - ZipPassThrough.prototype.push.call(this, chunk, final); - }; - return ZipDeflate; -}()); -export { ZipDeflate }; -/** - * Asynchronous streaming DEFLATE compression for ZIP archives - */ -var AsyncZipDeflate = /*#__PURE__*/ (function () { - /** - * Creates a DEFLATE stream that can be added to ZIP archives - * @param filename The filename to associate with this data stream - * @param opts The compression options - */ - function AsyncZipDeflate(filename, opts) { - var _this_1 = this; - if (!opts) - opts = {}; - ZipPassThrough.call(this, filename); - this.d = new AsyncDeflate(opts, function (err, dat, final) { - _this_1.ondata(err, dat, final); - }); - this.compression = 8; - this.flag = dbf(opts.level); - this.terminate = this.d.terminate; - } - AsyncZipDeflate.prototype.process = function (chunk, final) { - this.d.push(chunk, final); + // write zip header + var wzh = function (d, b, f, fn, u, c, ce, co) { + var fl = fn.length, ex = f.extra, col = co && co.length; + var exl = exfl(ex); + wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; + if (ce != null) + d[b++] = 20, d[b++] = f.os; + d[b] = 20, b += 2; // spec compliance? what's that? + d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; + d[b++] = f.compression & 255, d[b++] = f.compression >> 8; + var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; + if (y < 0 || y > 119) + throw 'date not in range 1980-2099'; + wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; + if (c != null) { + wbytes(d, b, f.crc); + wbytes(d, b + 4, c); + wbytes(d, b + 8, f.size); + } + wbytes(d, b + 12, fl); + wbytes(d, b + 14, exl), b += 16; + if (ce != null) { + wbytes(d, b, col); + wbytes(d, b + 6, f.attrs); + wbytes(d, b + 10, ce), b += 14; + } + d.set(fn, b); + b += fl; + if (exl) { + for (var k in ex) { + var exf = ex[k], l = exf.length; + wbytes(d, b, +k); + wbytes(d, b + 2, l); + d.set(exf, b + 4), b += 4 + l; + } + } + if (col) + d.set(co, b), b += col; + return b; }; - /** - * Pushes a chunk to be deflated - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - AsyncZipDeflate.prototype.push = function (chunk, final) { - ZipPassThrough.prototype.push.call(this, chunk, final); + // write zip footer (end of central directory) + var wzf = function (o, b, c, d, e) { + wbytes(o, b, 0x6054B50); // skip disk + wbytes(o, b + 8, c); + wbytes(o, b + 10, c); + wbytes(o, b + 12, d); + wbytes(o, b + 16, e); }; - return AsyncZipDeflate; -}()); -export { AsyncZipDeflate }; -// TODO: Better tree shaking -/** - * A zippable archive to which files can incrementally be added - */ -var Zip = /*#__PURE__*/ (function () { /** - * Creates an empty ZIP archive to which files can be added - * @param cb The callback to call whenever data for the generated ZIP archive - * is available + * A pass-through stream to keep data uncompressed in a ZIP archive. */ - function Zip(cb) { - this.ondata = cb; - this.u = []; - this.d = 1; - } + var ZipPassThrough = /*#__PURE__*/ (function () { + /** + * Creates a pass-through stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + */ + function ZipPassThrough(filename) { + this.filename = filename; + this.c = crc(); + this.size = 0; + this.compression = 0; + } + /** + * Processes a chunk and pushes to the output stream. You can override this + * method in a subclass for custom behavior, but by default this passes + * the data through. You must call this.ondata(err, chunk, final) at some + * point in this method. + * @param chunk The chunk to process + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.process = function (chunk, final) { + this.ondata(null, chunk, final); + }; + /** + * Pushes a chunk to be added. If you are subclassing this with a custom + * compression algorithm, note that you must push data from the source + * file only, pre-compression. + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback - add to ZIP archive before pushing'; + this.c.p(chunk); + this.size += chunk.length; + if (final) + this.crc = this.c.d(); + this.process(chunk, final || false); + }; + return ZipPassThrough; + }()); + + // I don't extend because TypeScript extension adds 1kB of runtime bloat /** - * Adds a file to the ZIP archive - * @param file The file stream to add + * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate + * for better performance */ - Zip.prototype.add = function (file) { - var _this_1 = this; - if (this.d & 2) - throw 'stream finished'; - var f = strToU8(file.filename), fl = f.length; - var com = file.comment, o = com && strToU8(com); - var u = fl != file.filename.length || (o && (com.length != o.length)); - var hl = fl + exfl(file.extra) + 30; - if (fl > 65535) - throw 'filename too long'; - var header = new u8(hl); - wzh(header, 0, file, f, u); - var chks = [header]; - var pAll = function () { - for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { - var chk = chks_1[_i]; - _this_1.ondata(null, chk, false); - } - chks = []; - }; - var tr = this.d; - this.d = 0; - var ind = this.u.length; - var uf = mrg(file, { - f: f, - u: u, - o: o, - t: function () { - if (file.terminate) - file.terminate(); - }, - r: function () { - pAll(); - if (tr) { - var nxt = _this_1.u[ind + 1]; - if (nxt) - nxt.r(); - else - _this_1.d = 1; - } - tr = 1; - } - }); - var cl = 0; - file.ondata = function (err, dat, final) { - if (err) { - _this_1.ondata(err, dat, final); - _this_1.terminate(); + var ZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function ZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new Deflate(opts, function (dat, final) { + _this_1.ondata(null, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + } + ZipDeflate.prototype.process = function (chunk, final) { + try { + this.d.push(chunk, final); } - else { - cl += dat.length; - chks.push(dat); - if (final) { - var dd = new u8(16); - wbytes(dd, 0, 0x8074B50); - wbytes(dd, 4, file.crc); - wbytes(dd, 8, cl); - wbytes(dd, 12, file.size); - chks.push(dd); - uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; - if (tr) - uf.r(); - tr = 1; - } - else if (tr) - pAll(); + catch (e) { + this.ondata(e, null, final); } }; - this.u.push(uf); - }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return ZipDeflate; + }()); + /** - * Ends the process of adding files and prepares to emit the final chunks. - * This *must* be called after adding all desired files for the resulting - * ZIP file to work properly. + * Asynchronous streaming DEFLATE compression for ZIP archives */ - Zip.prototype.end = function () { - var _this_1 = this; - if (this.d & 2) { - if (this.d & 1) - throw 'stream finishing'; - throw 'stream finished'; - } - if (this.d) - this.e(); - else - this.u.push({ - r: function () { - if (!(_this_1.d & 1)) - return; - _this_1.u.splice(-1, 1); - _this_1.e(); - }, - t: function () { } + var AsyncZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function AsyncZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new AsyncDeflate(opts, function (err, dat, final) { + _this_1.ondata(err, dat, final); }); - this.d = 3; - }; - Zip.prototype.e = function () { - var bt = 0, l = 0, tl = 0; - for (var _i = 0, _a = this.u; _i < _a.length; _i++) { - var f = _a[_i]; - tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); - } - var out = new u8(tl + 22); - for (var _b = 0, _c = this.u; _b < _c.length; _b++) { - var f = _c[_b]; - wzh(out, bt, f, f.f, f.u, f.c, l, f.o); - bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; - } - wzf(out, bt, this.u.length, tl, l); - this.ondata(null, out, true); - this.d = 2; - }; + this.compression = 8; + this.flag = dbf(opts.level); + this.terminate = this.d.terminate; + } + AsyncZipDeflate.prototype.process = function (chunk, final) { + this.d.push(chunk, final); + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + AsyncZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return AsyncZipDeflate; + }()); + + // TODO: Better tree shaking /** - * A method to terminate any internal workers used by the stream. Subsequent - * calls to add() will fail. + * A zippable archive to which files can incrementally be added */ - Zip.prototype.terminate = function () { - for (var _i = 0, _a = this.u; _i < _a.length; _i++) { - var f = _a[_i]; - f.t(); + var Zip = /*#__PURE__*/ (function () { + /** + * Creates an empty ZIP archive to which files can be added + * @param cb The callback to call whenever data for the generated ZIP archive + * is available + */ + function Zip(cb) { + this.ondata = cb; + this.u = []; + this.d = 1; } - this.d = 2; - }; - return Zip; -}()); -export { Zip }; -export function zip(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - var r = {}; - fltn(data, '', r, opts); - var k = Object.keys(r); - var lft = k.length, o = 0, tot = 0; - var slft = lft, files = new Array(lft); - var term = []; - var tAll = function () { - for (var i = 0; i < term.length; ++i) - term[i](); - }; - var cbf = function () { - var out = new u8(tot + 22), oe = o, cdl = tot - o; - tot = 0; - for (var i = 0; i < slft; ++i) { - var f = files[i]; - try { - var l = f.c.length; - wzh(out, tot, f, f.f, f.u, l); - var badd = 30 + f.f.length + exfl(f.extra); - var loc = tot + badd; - out.set(f.c, loc); - wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; + /** + * Adds a file to the ZIP archive + * @param file The file stream to add + */ + Zip.prototype.add = function (file) { + var _this_1 = this; + if (this.d & 2) + throw 'stream finished'; + var f = strToU8(file.filename), fl = f.length; + var com = file.comment, o = com && strToU8(com); + var u = fl != file.filename.length || (o && (com.length != o.length)); + var hl = fl + exfl(file.extra) + 30; + if (fl > 65535) + throw 'filename too long'; + var header = new u8(hl); + wzh(header, 0, file, f, u); + var chks = [header]; + var pAll = function () { + for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { + var chk = chks_1[_i]; + _this_1.ondata(null, chk, false); + } + chks = []; + }; + var tr = this.d; + this.d = 0; + var ind = this.u.length; + var uf = mrg(file, { + f: f, + u: u, + o: o, + t: function () { + if (file.terminate) + file.terminate(); + }, + r: function () { + pAll(); + if (tr) { + var nxt = _this_1.u[ind + 1]; + if (nxt) + nxt.r(); + else + _this_1.d = 1; + } + tr = 1; + } + }); + var cl = 0; + file.ondata = function (err, dat, final) { + if (err) { + _this_1.ondata(err, dat, final); + _this_1.terminate(); + } + else { + cl += dat.length; + chks.push(dat); + if (final) { + var dd = new u8(16); + wbytes(dd, 0, 0x8074B50); + wbytes(dd, 4, file.crc); + wbytes(dd, 8, cl); + wbytes(dd, 12, file.size); + chks.push(dd); + uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; + if (tr) + uf.r(); + tr = 1; + } + else if (tr) + pAll(); + } + }; + this.u.push(uf); + }; + /** + * Ends the process of adding files and prepares to emit the final chunks. + * This *must* be called after adding all desired files for the resulting + * ZIP file to work properly. + */ + Zip.prototype.end = function () { + var _this_1 = this; + if (this.d & 2) { + if (this.d & 1) + throw 'stream finishing'; + throw 'stream finished'; } - catch (e) { - return cb(e, null); + if (this.d) + this.e(); + else + this.u.push({ + r: function () { + if (!(_this_1.d & 1)) + return; + _this_1.u.splice(-1, 1); + _this_1.e(); + }, + t: function () { } + }); + this.d = 3; + }; + Zip.prototype.e = function () { + var bt = 0, l = 0, tl = 0; + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); } - } - wzf(out, o, files.length, cdl, oe); - cb(null, out); - }; - if (!lft) - cbf(); - var _loop_1 = function (i) { - var fn = k[i]; - var _a = r[fn], file = _a[0], p = _a[1]; - var c = crc(), size = file.length; - c.p(file); - var f = strToU8(fn), s = f.length; - var com = p.comment, m = com && strToU8(com), ms = m && m.length; - var exl = exfl(p.extra); - var compression = p.level == 0 ? 0 : 8; - var cbl = function (e, d) { - if (e) { - tAll(); - cb(e, null); + var out = new u8(tl + 22); + for (var _b = 0, _c = this.u; _b < _c.length; _b++) { + var f = _c[_b]; + wzh(out, bt, f, f.f, f.u, f.c, l, f.o); + bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; } - else { - var l = d.length; - files[i] = mrg(p, { - size: size, - crc: c.d(), - c: d, - f: f, - m: m, - u: s != fn.length || (m && (com.length != ms)), - compression: compression - }); - o += 30 + s + exl + l; - tot += 76 + 2 * (s + exl) + (ms || 0) + l; - if (!--lft) - cbf(); + wzf(out, bt, this.u.length, tl, l); + this.ondata(null, out, true); + this.d = 2; + }; + /** + * A method to terminate any internal workers used by the stream. Subsequent + * calls to add() will fail. + */ + Zip.prototype.terminate = function () { + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + f.t(); } + this.d = 2; }; - if (s > 65535) - cbl('filename too long', null); - if (!compression) - cbl(null, file); - else if (size < 160000) { - try { - cbl(null, deflateSync(file, p)); + return Zip; + }()); + + function zip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + var r = {}; + fltn(data, '', r, opts); + var k = Object.keys(r); + var lft = k.length, o = 0, tot = 0; + var slft = lft, files = new Array(lft); + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var cbf = function () { + var out = new u8(tot + 22), oe = o, cdl = tot - o; + tot = 0; + for (var i = 0; i < slft; ++i) { + var f = files[i]; + try { + var l = f.c.length; + wzh(out, tot, f, f.f, f.u, l); + var badd = 30 + f.f.length + exfl(f.extra); + var loc = tot + badd; + out.set(f.c, loc); + wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; + } + catch (e) { + return cb(e, null); + } } - catch (e) { - cbl(e, null); + wzf(out, o, files.length, cdl, oe); + cb(null, out); + }; + if (!lft) + cbf(); + var _loop_1 = function (i) { + var fn = k[i]; + var _a = r[fn], file = _a[0], p = _a[1]; + var c = crc(), size = file.length; + c.p(file); + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + var compression = p.level == 0 ? 0 : 8; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); + } + else { + var l = d.length; + files[i] = mrg(p, { + size: size, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + compression: compression + }); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + if (!--lft) + cbf(); + } + }; + if (s > 65535) + cbl('filename too long', null); + if (!compression) + cbl(null, file); + else if (size < 160000) { + try { + cbl(null, deflateSync(file, p)); + } + catch (e) { + cbl(e, null); + } } + else + term.push(deflate(file, p, cbl)); + }; + // Cannot use lft because it can decrease + for (var i = 0; i < slft; ++i) { + _loop_1(i); } - else - term.push(deflate(file, p, cbl)); - }; - // Cannot use lft because it can decrease - for (var i = 0; i < slft; ++i) { - _loop_1(i); - } - return tAll; -} -/** - * Synchronously creates a ZIP file. Prefer using `zip` for better performance - * with more than one file. - * @param data The directory structure for the ZIP archive - * @param opts The main options, merged with per-file options - * @returns The generated ZIP archive - */ -export function zipSync(data, opts) { - if (!opts) - opts = {}; - var r = {}; - var files = []; - fltn(data, '', r, opts); - var o = 0; - var tot = 0; - for (var fn in r) { - var _a = r[fn], file = _a[0], p = _a[1]; - var compression = p.level == 0 ? 0 : 8; - var f = strToU8(fn), s = f.length; - var com = p.comment, m = com && strToU8(com), ms = m && m.length; - var exl = exfl(p.extra); - if (s > 65535) - throw 'filename too long'; - var d = compression ? deflateSync(file, p) : file, l = d.length; - var c = crc(); - c.p(file); - files.push(mrg(p, { - size: file.length, - crc: c.d(), - c: d, - f: f, - m: m, - u: s != fn.length || (m && (com.length != ms)), - o: o, - compression: compression - })); - o += 30 + s + exl + l; - tot += 76 + 2 * (s + exl) + (ms || 0) + l; + return tAll; } - var out = new u8(tot + 22), oe = o, cdl = tot - o; - for (var i = 0; i < files.length; ++i) { - var f = files[i]; - wzh(out, f.o, f, f.f, f.u, f.c.length); - var badd = 30 + f.f.length + exfl(f.extra); - out.set(f.c, f.o + badd); - wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); - } - wzf(out, o, files.length, cdl, oe); - return out; -} -/** - * Streaming pass-through decompression for ZIP archives - */ -var UnzipPassThrough = /*#__PURE__*/ (function () { - function UnzipPassThrough() { - } - UnzipPassThrough.prototype.push = function (data, final) { - this.ondata(null, data, final); - }; - UnzipPassThrough.compression = 0; - return UnzipPassThrough; -}()); -export { UnzipPassThrough }; -/** - * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for - * better performance. - */ -var UnzipInflate = /*#__PURE__*/ (function () { /** - * Creates a DEFLATE decompression that can be used in ZIP archives + * Synchronously creates a ZIP file. Prefer using `zip` for better performance + * with more than one file. + * @param data The directory structure for the ZIP archive + * @param opts The main options, merged with per-file options + * @returns The generated ZIP archive */ - function UnzipInflate() { - var _this_1 = this; - this.i = new Inflate(function (dat, final) { - _this_1.ondata(null, dat, final); - }); - } - UnzipInflate.prototype.push = function (data, final) { - try { - this.i.push(data, final); + function zipSync(data, opts) { + if (!opts) + opts = {}; + var r = {}; + var files = []; + fltn(data, '', r, opts); + var o = 0; + var tot = 0; + for (var fn in r) { + var _a = r[fn], file = _a[0], p = _a[1]; + var compression = p.level == 0 ? 0 : 8; + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + if (s > 65535) + throw 'filename too long'; + var d = compression ? deflateSync(file, p) : file, l = d.length; + var c = crc(); + c.p(file); + files.push(mrg(p, { + size: file.length, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + o: o, + compression: compression + })); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; } - catch (e) { - this.ondata(e, data, final); + var out = new u8(tot + 22), oe = o, cdl = tot - o; + for (var i = 0; i < files.length; ++i) { + var f = files[i]; + wzh(out, f.o, f, f.f, f.u, f.c.length); + var badd = 30 + f.f.length + exfl(f.extra); + out.set(f.c, f.o + badd); + wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); } - }; - UnzipInflate.compression = 8; - return UnzipInflate; -}()); -export { UnzipInflate }; -/** - * Asynchronous streaming DEFLATE decompression for ZIP archives - */ -var AsyncUnzipInflate = /*#__PURE__*/ (function () { + wzf(out, o, files.length, cdl, oe); + return out; + } + /** + * Streaming pass-through decompression for ZIP archives + */ + var UnzipPassThrough = /*#__PURE__*/ (function () { + function UnzipPassThrough() { + } + UnzipPassThrough.prototype.push = function (data, final) { + this.ondata(null, data, final); + }; + UnzipPassThrough.compression = 0; + return UnzipPassThrough; + }()); + /** - * Creates a DEFLATE decompression that can be used in ZIP archives + * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for + * better performance. */ - function AsyncUnzipInflate(_, sz) { - var _this_1 = this; - if (sz < 320000) { + var UnzipInflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE decompression that can be used in ZIP archives + */ + function UnzipInflate() { + var _this_1 = this; this.i = new Inflate(function (dat, final) { _this_1.ondata(null, dat, final); }); } - else { - this.i = new AsyncInflate(function (err, dat, final) { - _this_1.ondata(err, dat, final); - }); - this.terminate = this.i.terminate; - } - } - AsyncUnzipInflate.prototype.push = function (data, final) { - if (this.i.terminate) - data = slc(data, 0); - this.i.push(data, final); - }; - AsyncUnzipInflate.compression = 8; - return AsyncUnzipInflate; -}()); -export { AsyncUnzipInflate }; -/** - * A ZIP archive decompression stream that emits files as they are discovered - */ -var Unzip = /*#__PURE__*/ (function () { + UnzipInflate.prototype.push = function (data, final) { + try { + this.i.push(data, final); + } + catch (e) { + this.ondata(e, data, final); + } + }; + UnzipInflate.compression = 8; + return UnzipInflate; + }()); + /** - * Creates a ZIP decompression stream - * @param cb The callback to call whenever a file in the ZIP archive is found + * Asynchronous streaming DEFLATE decompression for ZIP archives */ - function Unzip(cb) { - this.onfile = cb; - this.k = []; - this.o = { - 0: UnzipPassThrough + var AsyncUnzipInflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE decompression that can be used in ZIP archives + */ + function AsyncUnzipInflate(_, sz) { + var _this_1 = this; + if (sz < 320000) { + this.i = new Inflate(function (dat, final) { + _this_1.ondata(null, dat, final); + }); + } + else { + this.i = new AsyncInflate(function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.terminate = this.i.terminate; + } + } + AsyncUnzipInflate.prototype.push = function (data, final) { + if (this.i.terminate) + data = slc(data, 0); + this.i.push(data, final); }; - this.p = et; - } + AsyncUnzipInflate.compression = 8; + return AsyncUnzipInflate; + }()); + /** - * Pushes a chunk to be unzipped - * @param chunk The chunk to push - * @param final Whether this is the last chunk + * A ZIP archive decompression stream that emits files as they are discovered */ - Unzip.prototype.push = function (chunk, final) { - var _this_1 = this; - if (!this.onfile) - throw 'no callback'; - if (!this.p) - throw 'stream finished'; - if (this.c > 0) { - var len = Math.min(this.c, chunk.length); - var toAdd = chunk.subarray(0, len); - this.c -= len; - if (this.d) - this.d.push(toAdd, !this.c); - else - this.k[0].push(toAdd); - chunk = chunk.subarray(len); - if (chunk.length) - return this.push(chunk, final); + var Unzip = /*#__PURE__*/ (function () { + /** + * Creates a ZIP decompression stream + * @param cb The callback to call whenever a file in the ZIP archive is found + */ + function Unzip(cb) { + this.onfile = cb; + this.k = []; + this.o = { + 0: UnzipPassThrough + }; + this.p = et; } - else { - var f = 0, i = 0, is = void 0, buf = void 0; - if (!this.p.length) - buf = chunk; - else if (!chunk.length) - buf = this.p; - else { - buf = new u8(this.p.length + chunk.length); - buf.set(this.p), buf.set(chunk, this.p.length); + /** + * Pushes a chunk to be unzipped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Unzip.prototype.push = function (chunk, final) { + var _this_1 = this; + if (!this.onfile) + throw 'no callback'; + if (!this.p) + throw 'stream finished'; + if (this.c > 0) { + var len = Math.min(this.c, chunk.length); + var toAdd = chunk.subarray(0, len); + this.c -= len; + if (this.d) + this.d.push(toAdd, !this.c); + else + this.k[0].push(toAdd); + chunk = chunk.subarray(len); + if (chunk.length) + return this.push(chunk, final); } - var l = buf.length, oc = this.c, add = oc && this.d; - var _loop_2 = function () { - var _a; - var sig = b4(buf, i); - if (sig == 0x4034B50) { - f = 1, is = i; - this_1.d = null; - this_1.c = 0; - var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); - if (l > i + 30 + fnl + es) { - var chks_2 = []; - this_1.k.unshift(chks_2); - f = 2; - var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); - var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); - if (sc_1 == 4294967295) { - _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1]; - } - else if (dd) - sc_1 = -1; - i += es; - this_1.c = sc_1; - var d_1; - var file_1 = { - name: fn_1, - compression: cmp_1, - start: function () { - if (!file_1.ondata) - throw 'no callback'; - if (!sc_1) - file_1.ondata(null, et, true); - else { - var ctr = _this_1.o[cmp_1]; - if (!ctr) - throw 'unknown compression type ' + cmp_1; - d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); - d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; - for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { - var dat = chks_3[_i]; - d_1.push(dat, false); + else { + var f = 0, i = 0, is = void 0, buf = void 0; + if (!this.p.length) + buf = chunk; + else if (!chunk.length) + buf = this.p; + else { + buf = new u8(this.p.length + chunk.length); + buf.set(this.p), buf.set(chunk, this.p.length); + } + var l = buf.length, oc = this.c, add = oc && this.d; + var _loop_2 = function () { + var _a; + var sig = b4(buf, i); + if (sig == 0x4034B50) { + f = 1, is = i; + this_1.d = null; + this_1.c = 0; + var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); + if (l > i + 30 + fnl + es) { + var chks_2 = []; + this_1.k.unshift(chks_2); + f = 2; + var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); + var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); + if (sc_1 == 4294967295) { + _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1]; + } + else if (dd) + sc_1 = -1; + i += es; + this_1.c = sc_1; + var d_1; + var file_1 = { + name: fn_1, + compression: cmp_1, + start: function () { + if (!file_1.ondata) + throw 'no callback'; + if (!sc_1) + file_1.ondata(null, et, true); + else { + var ctr = _this_1.o[cmp_1]; + if (!ctr) + throw 'unknown compression type ' + cmp_1; + d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); + d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; + for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { + var dat = chks_3[_i]; + d_1.push(dat, false); + } + if (_this_1.k[0] == chks_2 && _this_1.c) + _this_1.d = d_1; + else + d_1.push(et, true); } - if (_this_1.k[0] == chks_2 && _this_1.c) - _this_1.d = d_1; - else - d_1.push(et, true); + }, + terminate: function () { + if (d_1 && d_1.terminate) + d_1.terminate(); } - }, - terminate: function () { - if (d_1 && d_1.terminate) - d_1.terminate(); - } - }; - if (sc_1 >= 0) - file_1.size = sc_1, file_1.originalSize = su_1; - this_1.onfile(file_1); - } - return "break"; - } - else if (oc) { - if (sig == 0x8074B50) { - is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0; + }; + if (sc_1 >= 0) + file_1.size = sc_1, file_1.originalSize = su_1; + this_1.onfile(file_1); + } return "break"; } - else if (sig == 0x2014B50) { - is = i -= 4, f = 3, this_1.c = 0; - return "break"; + else if (oc) { + if (sig == 0x8074B50) { + is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0; + return "break"; + } + else if (sig == 0x2014B50) { + is = i -= 4, f = 3, this_1.c = 0; + return "break"; + } } + }; + var this_1 = this; + for (; i < l - 4; ++i) { + var state_1 = _loop_2(); + if (state_1 === "break") + break; } - }; - var this_1 = this; - for (; i < l - 4; ++i) { - var state_1 = _loop_2(); - if (state_1 === "break") - break; + this.p = et; + if (oc < 0) { + var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i); + if (add) + add.push(dat, !!f); + else + this.k[+(f == 2)].push(dat); + } + if (f & 2) + return this.push(buf.subarray(i), final); + this.p = buf.subarray(i); } - this.p = et; - if (oc < 0) { - var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i); - if (add) - add.push(dat, !!f); - else - this.k[+(f == 2)].push(dat); + if (final) { + if (this.c) + throw 'invalid zip file'; + this.p = null; } - if (f & 2) - return this.push(buf.subarray(i), final); - this.p = buf.subarray(i); - } - if (final) { - if (this.c) - throw 'invalid zip file'; - this.p = null; - } - }; + }; + /** + * Registers a decoder with the stream, allowing for files compressed with + * the compression type provided to be expanded correctly + * @param decoder The decoder constructor + */ + Unzip.prototype.register = function (decoder) { + this.o[decoder.compression] = decoder; + }; + return Unzip; + }()); + /** - * Registers a decoder with the stream, allowing for files compressed with - * the compression type provided to be expanded correctly - * @param decoder The decoder constructor + * Asynchronously decompresses a ZIP archive + * @param data The raw compressed ZIP file + * @param cb The callback to call with the decompressed files + * @returns A function that can be used to immediately terminate the unzipping */ - Unzip.prototype.register = function (decoder) { - this.o[decoder.compression] = decoder; - }; - return Unzip; -}()); -export { Unzip }; -/** - * Asynchronously decompresses a ZIP archive - * @param data The raw compressed ZIP file - * @param cb The callback to call with the decompressed files - * @returns A function that can be used to immediately terminate the unzipping - */ -export function unzip(data, cb) { - if (typeof cb != 'function') - throw 'no callback'; - var term = []; - var tAll = function () { - for (var i = 0; i < term.length; ++i) - term[i](); - }; - var files = {}; - var e = data.length - 22; - for (; b4(data, e) != 0x6054B50; --e) { - if (!e || data.length - e > 65558) { - cb('invalid zip file', null); - return; - } - } - ; - var lft = b2(data, e + 8); - if (!lft) - cb(null, {}); - var c = lft; - var o = b4(data, e + 16); - var z = o == 4294967295; - if (z) { - e = b4(data, e - 12); - if (b4(data, e) != 0x6064B50) { - cb('invalid zip file', null); - return; - } - c = lft = b4(data, e + 32); - o = b4(data, e + 48); - } - var _loop_3 = function (i) { - var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); - o = no; - var cbl = function (e, d) { - if (e) { - tAll(); - cb(e, null); + function unzip(data, cb) { + if (typeof cb != 'function') + throw 'no callback'; + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) { + cb('invalid zip file', null); + return; } - else { - files[fn] = d; - if (!--lft) - cb(null, files); + } + ; + var lft = b2(data, e + 8); + if (!lft) + cb(null, {}); + var c = lft; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) { + cb('invalid zip file', null); + return; } - }; - if (!c_1) - cbl(null, slc(data, b, b + sc)); - else if (c_1 == 8) { - var infl = data.subarray(b, b + sc); - if (sc < 320000) { - try { - cbl(null, inflateSync(infl, new u8(su))); + c = lft = b4(data, e + 32); + o = b4(data, e + 48); + } + var _loop_3 = function (i) { + var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); } - catch (e) { - cbl(e, null); + else { + files[fn] = d; + if (!--lft) + cb(null, files); + } + }; + if (!c_1) + cbl(null, slc(data, b, b + sc)); + else if (c_1 == 8) { + var infl = data.subarray(b, b + sc); + if (sc < 320000) { + try { + cbl(null, inflateSync(infl, new u8(su))); + } + catch (e) { + cbl(e, null); + } } + else + term.push(inflate(infl, { size: su }, cbl)); } else - term.push(inflate(infl, { size: su }, cbl)); + cbl('unknown compression type ' + c_1, null); + }; + for (var i = 0; i < c; ++i) { + _loop_3(i); } - else - cbl('unknown compression type ' + c_1, null); - }; - for (var i = 0; i < c; ++i) { - _loop_3(i); + return tAll; } - return tAll; -} -/** - * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better - * performance with more than one file. - * @param data The raw compressed ZIP file - * @returns The decompressed files - */ -export function unzipSync(data) { - var files = {}; - var e = data.length - 22; - for (; b4(data, e) != 0x6054B50; --e) { - if (!e || data.length - e > 65558) - throw 'invalid zip file'; - } - ; - var c = b2(data, e + 8); - if (!c) - return {}; - var o = b4(data, e + 16); - var z = o == 4294967295; - if (z) { - e = b4(data, e - 12); - if (b4(data, e) != 0x6064B50) - throw 'invalid zip file'; - c = b4(data, e + 32); - o = b4(data, e + 48); - } - for (var i = 0; i < c; ++i) { - var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); - o = no; - if (!c_2) - files[fn] = slc(data, b, b + sc); - else if (c_2 == 8) - files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); - else - throw 'unknown compression type ' + c_2; + /** + * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better + * performance with more than one file. + * @param data The raw compressed ZIP file + * @returns The decompressed files + */ + function unzipSync(data) { + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) + throw 'invalid zip file'; + } + ; + var c = b2(data, e + 8); + if (!c) + return {}; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) + throw 'invalid zip file'; + c = b4(data, e + 32); + o = b4(data, e + 48); + } + for (var i = 0; i < c; ++i) { + var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + if (!c_2) + files[fn] = slc(data, b, b + sc); + else if (c_2 == 8) + files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); + else + throw 'unknown compression type ' + c_2; + } + return files; } - return files; + + return { + + Deflate, + AsyncDeflate, + deflate, + deflateSync, + Inflate, + AsyncInflate, + inflate, + inflateSync, + Gzip, + AsyncGzip, + gzip, + gzipSync, + Gunzip, + AsyncGunzip, + gunzip, + gunzipSync, + Zlib, + AsyncZlib, + zlib, + zlibSync, + Unzlib, + AsyncUnzlib, + unzlib, + unzlibSync, + Decompress, + AsyncDecompress, + decompress, + decompressSync, + DecodeUTF8, + EncodeUTF8, + strToU8, + strFromU8, + ZipPassThrough, + ZipDeflate, + AsyncZipDeflate, + Zip, + zip, + zipSync, + UnzipPassThrough, + UnzipInflate, + AsyncUnzipInflate, + Unzip, + unzip, + unzipSync, + + }; + +} )(); + +export { + Deflate, + AsyncDeflate, + deflate, + deflateSync, + Inflate, + AsyncInflate, + inflate, + inflateSync, + Gzip, + AsyncGzip, + gzip, + gzipSync, + Gunzip, + AsyncGunzip, + gunzip, + gunzipSync, + Zlib, + AsyncZlib, + zlib, + zlibSync, + Unzlib, + AsyncUnzlib, + unzlib, + unzlibSync, + gzip as compress, + AsyncGzip as AsyncCompress, + gzipSync as compressSync, + Gzip as Compress, + Decompress, + AsyncDecompress, + decompress, + decompressSync, + DecodeUTF8, + EncodeUTF8, + strToU8, + strFromU8, + ZipPassThrough, + ZipDeflate, + AsyncZipDeflate, + Zip, + zip, + zipSync, + UnzipPassThrough, + UnzipInflate, + AsyncUnzipInflate, + Unzip, + unzip, + unzipSync, } From e5b469477fdbc8a0645d5daf55802ff169923f81 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:35:03 -0500 Subject: [PATCH 10/45] EXRExporter: mark textEncoder as pure --- examples/jsm/exporters/EXRExporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/exporters/EXRExporter.js b/examples/jsm/exporters/EXRExporter.js index bd1d8202d2d83d..246f7b881bcbd9 100644 --- a/examples/jsm/exporters/EXRExporter.js +++ b/examples/jsm/exporters/EXRExporter.js @@ -13,7 +13,7 @@ import { } from 'three'; import * as fflate from '../libs/fflate.module.js'; -const textEncoder = new TextEncoder(); +const textEncoder = /* @__PURE__ */ new TextEncoder(); const NO_COMPRESSION = 0; const ZIPS_COMPRESSION = 2; From 3a7b8ab319e4efc237ab18ef70790d12be6038c5 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:52:00 -0500 Subject: [PATCH 11/45] lottie_canvas: tree-shake as IIFE --- examples/jsm/libs/lottie_canvas.module.js | 28262 ++++++++++---------- 1 file changed, 14134 insertions(+), 14128 deletions(-) diff --git a/examples/jsm/libs/lottie_canvas.module.js b/examples/jsm/libs/lottie_canvas.module.js index cbe3a964e5c90d..1a537d5500bc99 100644 --- a/examples/jsm/libs/lottie_canvas.module.js +++ b/examples/jsm/libs/lottie_canvas.module.js @@ -1,14844 +1,14850 @@ -const svgNS = 'http://www.w3.org/2000/svg'; +const lottie = /* @__PURE */ ( () => { -let locationHref = ''; -let _useWebWorker = false; + const svgNS = 'http://www.w3.org/2000/svg'; -const initialDefaultFrame = -999999; + let locationHref = ''; + let _useWebWorker = false; -const setWebWorker = (flag) => { _useWebWorker = !!flag; }; -const getWebWorker = () => _useWebWorker; + const initialDefaultFrame = -999999; -const setLocationHref = (value) => { locationHref = value; }; -const getLocationHref = () => locationHref; + const setWebWorker = (flag) => { _useWebWorker = !!flag; }; + const getWebWorker = () => _useWebWorker; -function createTag(type) { - // return {appendChild:function(){},setAttribute:function(){},style:{}} - return document.createElement(type); -} + const setLocationHref = (value) => { locationHref = value; }; + const getLocationHref = () => locationHref; -function extendPrototype(sources, destination) { - var i; - var len = sources.length; - var sourcePrototype; - for (i = 0; i < len; i += 1) { - sourcePrototype = sources[i].prototype; - for (var attr in sourcePrototype) { - if (Object.prototype.hasOwnProperty.call(sourcePrototype, attr)) destination.prototype[attr] = sourcePrototype[attr]; + function createTag(type) { + // return {appendChild:function(){},setAttribute:function(){},style:{}} + return document.createElement(type); } - } -} - -function getDescriptor(object, prop) { - return Object.getOwnPropertyDescriptor(object, prop); -} - -function createProxyFunction(prototype) { - function ProxyFunction() {} - ProxyFunction.prototype = prototype; - return ProxyFunction; -} - -// import Howl from '../../3rd_party/howler'; - -const audioControllerFactory = (function () { - function AudioController(audioFactory) { - this.audios = []; - this.audioFactory = audioFactory; - this._volume = 1; - this._isMuted = false; - } - - AudioController.prototype = { - addAudio: function (audio) { - this.audios.push(audio); - }, - pause: function () { - var i; - var len = this.audios.length; - for (i = 0; i < len; i += 1) { - this.audios[i].pause(); - } - }, - resume: function () { + + function extendPrototype(sources, destination) { var i; - var len = this.audios.length; + var len = sources.length; + var sourcePrototype; for (i = 0; i < len; i += 1) { - this.audios[i].resume(); + sourcePrototype = sources[i].prototype; + for (var attr in sourcePrototype) { + if (Object.prototype.hasOwnProperty.call(sourcePrototype, attr)) destination.prototype[attr] = sourcePrototype[attr]; + } } - }, - setRate: function (rateValue) { - var i; - var len = this.audios.length; - for (i = 0; i < len; i += 1) { - this.audios[i].setRate(rateValue); - } - }, - createAudio: function (assetPath) { - if (this.audioFactory) { - return this.audioFactory(assetPath); - } if (window.Howl) { - return new window.Howl({ - src: [assetPath], - }); + } + + function getDescriptor(object, prop) { + return Object.getOwnPropertyDescriptor(object, prop); + } + + function createProxyFunction(prototype) { + function ProxyFunction() {} + ProxyFunction.prototype = prototype; + return ProxyFunction; + } + + // import Howl from '../../3rd_party/howler'; + + const audioControllerFactory = (function () { + function AudioController(audioFactory) { + this.audios = []; + this.audioFactory = audioFactory; + this._volume = 1; + this._isMuted = false; } - return { - isPlaying: false, - play: function () { this.isPlaying = true; }, - seek: function () { this.isPlaying = false; }, - playing: function () {}, - rate: function () {}, - setVolume: function () {}, + + AudioController.prototype = { + addAudio: function (audio) { + this.audios.push(audio); + }, + pause: function () { + var i; + var len = this.audios.length; + for (i = 0; i < len; i += 1) { + this.audios[i].pause(); + } + }, + resume: function () { + var i; + var len = this.audios.length; + for (i = 0; i < len; i += 1) { + this.audios[i].resume(); + } + }, + setRate: function (rateValue) { + var i; + var len = this.audios.length; + for (i = 0; i < len; i += 1) { + this.audios[i].setRate(rateValue); + } + }, + createAudio: function (assetPath) { + if (this.audioFactory) { + return this.audioFactory(assetPath); + } if (window.Howl) { + return new window.Howl({ + src: [assetPath], + }); + } + return { + isPlaying: false, + play: function () { this.isPlaying = true; }, + seek: function () { this.isPlaying = false; }, + playing: function () {}, + rate: function () {}, + setVolume: function () {}, + }; + }, + setAudioFactory: function (audioFactory) { + this.audioFactory = audioFactory; + }, + setVolume: function (value) { + this._volume = value; + this._updateVolume(); + }, + mute: function () { + this._isMuted = true; + this._updateVolume(); + }, + unmute: function () { + this._isMuted = false; + this._updateVolume(); + }, + getVolume: function () { + return this._volume; + }, + _updateVolume: function () { + var i; + var len = this.audios.length; + for (i = 0; i < len; i += 1) { + this.audios[i].volume(this._volume * (this._isMuted ? 0 : 1)); + } + }, + }; + + return function () { + return new AudioController(); }; - }, - setAudioFactory: function (audioFactory) { - this.audioFactory = audioFactory; - }, - setVolume: function (value) { - this._volume = value; - this._updateVolume(); - }, - mute: function () { - this._isMuted = true; - this._updateVolume(); - }, - unmute: function () { - this._isMuted = false; - this._updateVolume(); - }, - getVolume: function () { - return this._volume; - }, - _updateVolume: function () { + }()); + + const createTypedArray = (function () { + function createRegularArray(type, len) { + var i = 0; + var arr = []; + var value; + switch (type) { + case 'int16': + case 'uint8c': + value = 1; + break; + default: + value = 1.1; + break; + } + for (i = 0; i < len; i += 1) { + arr.push(value); + } + return arr; + } + function createTypedArrayFactory(type, len) { + if (type === 'float32') { + return new Float32Array(len); + } if (type === 'int16') { + return new Int16Array(len); + } if (type === 'uint8c') { + return new Uint8ClampedArray(len); + } + return createRegularArray(type, len); + } + if (typeof Uint8ClampedArray === 'function' && typeof Float32Array === 'function') { + return createTypedArrayFactory; + } + return createRegularArray; + }()); + + function createSizedArray(len) { + return Array.apply(null, { length: len }); + } + + let subframeEnabled = true; + let expressionsPlugin = null; + let idPrefix$1 = ''; + const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + let _shouldRoundValues = false; + const bmPow = Math.pow; + const bmSqrt = Math.sqrt; + const bmFloor = Math.floor; + const bmMax = Math.max; + const bmMin = Math.min; + + const BMMath = {}; + (function () { + var propertyNames = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'atan2', 'ceil', 'cbrt', 'expm1', 'clz32', 'cos', 'cosh', 'exp', 'floor', 'fround', 'hypot', 'imul', 'log', 'log1p', 'log2', 'log10', 'max', 'min', 'pow', 'random', 'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', 'E', 'LN10', 'LN2', 'LOG10E', 'LOG2E', 'PI', 'SQRT1_2', 'SQRT2']; var i; - var len = this.audios.length; + var len = propertyNames.length; for (i = 0; i < len; i += 1) { - this.audios[i].volume(this._volume * (this._isMuted ? 0 : 1)); - } - }, - }; - - return function () { - return new AudioController(); - }; -}()); - -const createTypedArray = (function () { - function createRegularArray(type, len) { - var i = 0; - var arr = []; - var value; - switch (type) { - case 'int16': - case 'uint8c': - value = 1; - break; - default: - value = 1.1; - break; + BMMath[propertyNames[i]] = Math[propertyNames[i]]; + } + }()); + + function ProjectInterface$1() { return {}; } + BMMath.random = Math.random; + BMMath.abs = function (val) { + var tOfVal = typeof val; + if (tOfVal === 'object' && val.length) { + var absArr = createSizedArray(val.length); + var i; + var len = val.length; + for (i = 0; i < len; i += 1) { + absArr[i] = Math.abs(val[i]); + } + return absArr; + } + return Math.abs(val); + }; + let defaultCurveSegments = 150; + const degToRads = Math.PI / 180; + const roundCorner = 0.5519; + + function roundValues(flag) { + _shouldRoundValues = !!flag; + } + + function bmRnd(value) { + if (_shouldRoundValues) { + return Math.round(value); + } + return value; } - for (i = 0; i < len; i += 1) { - arr.push(value); + + function styleDiv(element) { + element.style.position = 'absolute'; + element.style.top = 0; + element.style.left = 0; + element.style.display = 'block'; + element.style.transformOrigin = '0 0'; + element.style.webkitTransformOrigin = '0 0'; + element.style.backfaceVisibility = 'visible'; + element.style.webkitBackfaceVisibility = 'visible'; + element.style.transformStyle = 'preserve-3d'; + element.style.webkitTransformStyle = 'preserve-3d'; + element.style.mozTransformStyle = 'preserve-3d'; } - return arr; - } - function createTypedArrayFactory(type, len) { - if (type === 'float32') { - return new Float32Array(len); - } if (type === 'int16') { - return new Int16Array(len); - } if (type === 'uint8c') { - return new Uint8ClampedArray(len); + + function BMEnterFrameEvent(type, currentTime, totalTime, frameMultiplier) { + this.type = type; + this.currentTime = currentTime; + this.totalTime = totalTime; + this.direction = frameMultiplier < 0 ? -1 : 1; } - return createRegularArray(type, len); - } - if (typeof Uint8ClampedArray === 'function' && typeof Float32Array === 'function') { - return createTypedArrayFactory; - } - return createRegularArray; -}()); - -function createSizedArray(len) { - return Array.apply(null, { length: len }); -} - -let subframeEnabled = true; -let expressionsPlugin = null; -let idPrefix$1 = ''; -const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); -let _shouldRoundValues = false; -const bmPow = Math.pow; -const bmSqrt = Math.sqrt; -const bmFloor = Math.floor; -const bmMax = Math.max; -const bmMin = Math.min; - -const BMMath = {}; -(function () { - var propertyNames = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'atan2', 'ceil', 'cbrt', 'expm1', 'clz32', 'cos', 'cosh', 'exp', 'floor', 'fround', 'hypot', 'imul', 'log', 'log1p', 'log2', 'log10', 'max', 'min', 'pow', 'random', 'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', 'E', 'LN10', 'LN2', 'LOG10E', 'LOG2E', 'PI', 'SQRT1_2', 'SQRT2']; - var i; - var len = propertyNames.length; - for (i = 0; i < len; i += 1) { - BMMath[propertyNames[i]] = Math[propertyNames[i]]; - } -}()); - -function ProjectInterface$1() { return {}; } -BMMath.random = Math.random; -BMMath.abs = function (val) { - var tOfVal = typeof val; - if (tOfVal === 'object' && val.length) { - var absArr = createSizedArray(val.length); - var i; - var len = val.length; - for (i = 0; i < len; i += 1) { - absArr[i] = Math.abs(val[i]); + + function BMCompleteEvent(type, frameMultiplier) { + this.type = type; + this.direction = frameMultiplier < 0 ? -1 : 1; } - return absArr; - } - return Math.abs(val); -}; -let defaultCurveSegments = 150; -const degToRads = Math.PI / 180; -const roundCorner = 0.5519; - -function roundValues(flag) { - _shouldRoundValues = !!flag; -} - -function bmRnd(value) { - if (_shouldRoundValues) { - return Math.round(value); - } - return value; -} - -function styleDiv(element) { - element.style.position = 'absolute'; - element.style.top = 0; - element.style.left = 0; - element.style.display = 'block'; - element.style.transformOrigin = '0 0'; - element.style.webkitTransformOrigin = '0 0'; - element.style.backfaceVisibility = 'visible'; - element.style.webkitBackfaceVisibility = 'visible'; - element.style.transformStyle = 'preserve-3d'; - element.style.webkitTransformStyle = 'preserve-3d'; - element.style.mozTransformStyle = 'preserve-3d'; -} - -function BMEnterFrameEvent(type, currentTime, totalTime, frameMultiplier) { - this.type = type; - this.currentTime = currentTime; - this.totalTime = totalTime; - this.direction = frameMultiplier < 0 ? -1 : 1; -} - -function BMCompleteEvent(type, frameMultiplier) { - this.type = type; - this.direction = frameMultiplier < 0 ? -1 : 1; -} - -function BMCompleteLoopEvent(type, totalLoops, currentLoop, frameMultiplier) { - this.type = type; - this.currentLoop = currentLoop; - this.totalLoops = totalLoops; - this.direction = frameMultiplier < 0 ? -1 : 1; -} - -function BMSegmentStartEvent(type, firstFrame, totalFrames) { - this.type = type; - this.firstFrame = firstFrame; - this.totalFrames = totalFrames; -} - -function BMDestroyEvent(type, target) { - this.type = type; - this.target = target; -} - -function BMRenderFrameErrorEvent(nativeError, currentTime) { - this.type = 'renderFrameError'; - this.nativeError = nativeError; - this.currentTime = currentTime; -} - -function BMConfigErrorEvent(nativeError) { - this.type = 'configError'; - this.nativeError = nativeError; -} - -function BMAnimationConfigErrorEvent(type, nativeError) { - this.type = type; - this.nativeError = nativeError; -} - -const createElementID = (function () { - var _count = 0; - return function createID() { - _count += 1; - return idPrefix$1 + '__lottie_element_' + _count; - }; -}()); - -function HSVtoRGB(h, s, v) { - var r; - var g; - var b; - var i; - var f; - var p; - var q; - var t; - i = Math.floor(h * 6); - f = h * 6 - i; - p = v * (1 - s); - q = v * (1 - f * s); - t = v * (1 - (1 - f) * s); - switch (i % 6) { - case 0: r = v; g = t; b = p; break; - case 1: r = q; g = v; b = p; break; - case 2: r = p; g = v; b = t; break; - case 3: r = p; g = q; b = v; break; - case 4: r = t; g = p; b = v; break; - case 5: r = v; g = p; b = q; break; - default: break; - } - return [r, - g, - b]; -} - -function RGBtoHSV(r, g, b) { - var max = Math.max(r, g, b); - var min = Math.min(r, g, b); - var d = max - min; - var h; - var s = (max === 0 ? 0 : d / max); - var v = max / 255; - - switch (max) { - case min: h = 0; break; - case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break; - case g: h = (b - r) + d * 2; h /= 6 * d; break; - case b: h = (r - g) + d * 4; h /= 6 * d; break; - default: break; - } - - return [ - h, - s, - v, - ]; -} - -function addSaturationToRGB(color, offset) { - var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); - hsv[1] += offset; - if (hsv[1] > 1) { - hsv[1] = 1; - } else if (hsv[1] <= 0) { - hsv[1] = 0; - } - return HSVtoRGB(hsv[0], hsv[1], hsv[2]); -} - -function addBrightnessToRGB(color, offset) { - var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); - hsv[2] += offset; - if (hsv[2] > 1) { - hsv[2] = 1; - } else if (hsv[2] < 0) { - hsv[2] = 0; - } - return HSVtoRGB(hsv[0], hsv[1], hsv[2]); -} - -function addHueToRGB(color, offset) { - var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); - hsv[0] += offset / 360; - if (hsv[0] > 1) { - hsv[0] -= 1; - } else if (hsv[0] < 0) { - hsv[0] += 1; - } - return HSVtoRGB(hsv[0], hsv[1], hsv[2]); -} - -const rgbToHex = (function () { - var colorMap = []; - var i; - var hex; - for (i = 0; i < 256; i += 1) { - hex = i.toString(16); - colorMap[i] = hex.length === 1 ? '0' + hex : hex; - } - - return function (r, g, b) { - if (r < 0) { - r = 0; + + function BMCompleteLoopEvent(type, totalLoops, currentLoop, frameMultiplier) { + this.type = type; + this.currentLoop = currentLoop; + this.totalLoops = totalLoops; + this.direction = frameMultiplier < 0 ? -1 : 1; } - if (g < 0) { - g = 0; + + function BMSegmentStartEvent(type, firstFrame, totalFrames) { + this.type = type; + this.firstFrame = firstFrame; + this.totalFrames = totalFrames; } - if (b < 0) { - b = 0; + + function BMDestroyEvent(type, target) { + this.type = type; + this.target = target; } - return '#' + colorMap[r] + colorMap[g] + colorMap[b]; - }; -}()); - -const setSubframeEnabled = (flag) => { subframeEnabled = !!flag; }; -const getSubframeEnabled = () => subframeEnabled; -const setExpressionsPlugin = (value) => { expressionsPlugin = value; }; -const getExpressionsPlugin = () => expressionsPlugin; -const setDefaultCurveSegments = (value) => { defaultCurveSegments = value; }; -const getDefaultCurveSegments = () => defaultCurveSegments; -const setIdPrefix = (value) => { idPrefix$1 = value; }; -const getIdPrefix = () => idPrefix$1; - -function createNS(type) { - // return {appendChild:function(){},setAttribute:function(){},style:{}} - return document.createElementNS(svgNS, type); -} - -const dataManager = (function () { - var _counterId = 1; - var processes = []; - var workerFn; - var workerInstance; - var workerProxy = { - onmessage: function () { - - }, - postMessage: function (path) { - workerFn({ - data: path, - }); - }, - }; - var _workerSelf = { - postMessage: function (data) { - workerProxy.onmessage({ - data: data, - }); - }, - }; - function createWorker(fn) { - if (window.Worker && window.Blob && getWebWorker()) { - var blob = new Blob(['var _workerSelf = self; self.onmessage = ', fn.toString()], { type: 'text/javascript' }); - // var blob = new Blob(['self.onmessage = ', fn.toString()], { type: 'text/javascript' }); - var url = URL.createObjectURL(blob); - return new Worker(url); + + function BMRenderFrameErrorEvent(nativeError, currentTime) { + this.type = 'renderFrameError'; + this.nativeError = nativeError; + this.currentTime = currentTime; } - workerFn = fn; - return workerProxy; - } - - function setupWorker() { - if (!workerInstance) { - workerInstance = createWorker(function workerStart(e) { - function dataFunctionManager() { - function completeLayers(layers, comps) { - var layerData; - var i; - var len = layers.length; - var j; - var jLen; - var k; - var kLen; - for (i = 0; i < len; i += 1) { - layerData = layers[i]; - if (('ks' in layerData) && !layerData.completed) { - layerData.completed = true; - if (layerData.tt) { - layers[i - 1].td = layerData.tt; - } - if (layerData.hasMask) { - var maskProps = layerData.masksProperties; - jLen = maskProps.length; - for (j = 0; j < jLen; j += 1) { - if (maskProps[j].pt.k.i) { - convertPathsToAbsoluteValues(maskProps[j].pt.k); - } else { - kLen = maskProps[j].pt.k.length; - for (k = 0; k < kLen; k += 1) { - if (maskProps[j].pt.k[k].s) { - convertPathsToAbsoluteValues(maskProps[j].pt.k[k].s[0]); - } - if (maskProps[j].pt.k[k].e) { - convertPathsToAbsoluteValues(maskProps[j].pt.k[k].e[0]); + + function BMConfigErrorEvent(nativeError) { + this.type = 'configError'; + this.nativeError = nativeError; + } + + function BMAnimationConfigErrorEvent(type, nativeError) { + this.type = type; + this.nativeError = nativeError; + } + + const createElementID = (function () { + var _count = 0; + return function createID() { + _count += 1; + return idPrefix$1 + '__lottie_element_' + _count; + }; + }()); + + function HSVtoRGB(h, s, v) { + var r; + var g; + var b; + var i; + var f; + var p; + var q; + var t; + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + default: break; + } + return [r, + g, + b]; + } + + function RGBtoHSV(r, g, b) { + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var d = max - min; + var h; + var s = (max === 0 ? 0 : d / max); + var v = max / 255; + + switch (max) { + case min: h = 0; break; + case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break; + case g: h = (b - r) + d * 2; h /= 6 * d; break; + case b: h = (r - g) + d * 4; h /= 6 * d; break; + default: break; + } + + return [ + h, + s, + v, + ]; + } + + function addSaturationToRGB(color, offset) { + var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); + hsv[1] += offset; + if (hsv[1] > 1) { + hsv[1] = 1; + } else if (hsv[1] <= 0) { + hsv[1] = 0; + } + return HSVtoRGB(hsv[0], hsv[1], hsv[2]); + } + + function addBrightnessToRGB(color, offset) { + var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); + hsv[2] += offset; + if (hsv[2] > 1) { + hsv[2] = 1; + } else if (hsv[2] < 0) { + hsv[2] = 0; + } + return HSVtoRGB(hsv[0], hsv[1], hsv[2]); + } + + function addHueToRGB(color, offset) { + var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); + hsv[0] += offset / 360; + if (hsv[0] > 1) { + hsv[0] -= 1; + } else if (hsv[0] < 0) { + hsv[0] += 1; + } + return HSVtoRGB(hsv[0], hsv[1], hsv[2]); + } + + const rgbToHex = (function () { + var colorMap = []; + var i; + var hex; + for (i = 0; i < 256; i += 1) { + hex = i.toString(16); + colorMap[i] = hex.length === 1 ? '0' + hex : hex; + } + + return function (r, g, b) { + if (r < 0) { + r = 0; + } + if (g < 0) { + g = 0; + } + if (b < 0) { + b = 0; + } + return '#' + colorMap[r] + colorMap[g] + colorMap[b]; + }; + }()); + + const setSubframeEnabled = (flag) => { subframeEnabled = !!flag; }; + const getSubframeEnabled = () => subframeEnabled; + const setExpressionsPlugin = (value) => { expressionsPlugin = value; }; + const getExpressionsPlugin = () => expressionsPlugin; + const setDefaultCurveSegments = (value) => { defaultCurveSegments = value; }; + const getDefaultCurveSegments = () => defaultCurveSegments; + const setIdPrefix = (value) => { idPrefix$1 = value; }; + const getIdPrefix = () => idPrefix$1; + + function createNS(type) { + // return {appendChild:function(){},setAttribute:function(){},style:{}} + return document.createElementNS(svgNS, type); + } + + const dataManager = (function () { + var _counterId = 1; + var processes = []; + var workerFn; + var workerInstance; + var workerProxy = { + onmessage: function () { + + }, + postMessage: function (path) { + workerFn({ + data: path, + }); + }, + }; + var _workerSelf = { + postMessage: function (data) { + workerProxy.onmessage({ + data: data, + }); + }, + }; + function createWorker(fn) { + if (window.Worker && window.Blob && getWebWorker()) { + var blob = new Blob(['var _workerSelf = self; self.onmessage = ', fn.toString()], { type: 'text/javascript' }); + // var blob = new Blob(['self.onmessage = ', fn.toString()], { type: 'text/javascript' }); + var url = URL.createObjectURL(blob); + return new Worker(url); + } + workerFn = fn; + return workerProxy; + } + + function setupWorker() { + if (!workerInstance) { + workerInstance = createWorker(function workerStart(e) { + function dataFunctionManager() { + function completeLayers(layers, comps) { + var layerData; + var i; + var len = layers.length; + var j; + var jLen; + var k; + var kLen; + for (i = 0; i < len; i += 1) { + layerData = layers[i]; + if (('ks' in layerData) && !layerData.completed) { + layerData.completed = true; + if (layerData.tt) { + layers[i - 1].td = layerData.tt; + } + if (layerData.hasMask) { + var maskProps = layerData.masksProperties; + jLen = maskProps.length; + for (j = 0; j < jLen; j += 1) { + if (maskProps[j].pt.k.i) { + convertPathsToAbsoluteValues(maskProps[j].pt.k); + } else { + kLen = maskProps[j].pt.k.length; + for (k = 0; k < kLen; k += 1) { + if (maskProps[j].pt.k[k].s) { + convertPathsToAbsoluteValues(maskProps[j].pt.k[k].s[0]); + } + if (maskProps[j].pt.k[k].e) { + convertPathsToAbsoluteValues(maskProps[j].pt.k[k].e[0]); + } + } } } } + if (layerData.ty === 0) { + layerData.layers = findCompLayers(layerData.refId, comps); + completeLayers(layerData.layers, comps); + } else if (layerData.ty === 4) { + completeShapes(layerData.shapes); + } else if (layerData.ty === 5) { + completeText(layerData); + } } } - if (layerData.ty === 0) { - layerData.layers = findCompLayers(layerData.refId, comps); - completeLayers(layerData.layers, comps); - } else if (layerData.ty === 4) { - completeShapes(layerData.shapes); - } else if (layerData.ty === 5) { - completeText(layerData); - } } - } - } - function completeChars(chars, assets) { - if (chars) { - var i = 0; - var len = chars.length; - for (i = 0; i < len; i += 1) { - if (chars[i].t === 1) { - // var compData = findComp(chars[i].data.refId, assets); - chars[i].data.layers = findCompLayers(chars[i].data.refId, assets); - // chars[i].data.ip = 0; - // chars[i].data.op = 99999; - // chars[i].data.st = 0; - // chars[i].data.sr = 1; - // chars[i].w = compData.w; - // chars[i].data.ks = { - // a: { k: [0, 0, 0], a: 0 }, - // p: { k: [0, -compData.h, 0], a: 0 }, - // r: { k: 0, a: 0 }, - // s: { k: [100, 100], a: 0 }, - // o: { k: 100, a: 0 }, - // }; - completeLayers(chars[i].data.layers, assets); + function completeChars(chars, assets) { + if (chars) { + var i = 0; + var len = chars.length; + for (i = 0; i < len; i += 1) { + if (chars[i].t === 1) { + // var compData = findComp(chars[i].data.refId, assets); + chars[i].data.layers = findCompLayers(chars[i].data.refId, assets); + // chars[i].data.ip = 0; + // chars[i].data.op = 99999; + // chars[i].data.st = 0; + // chars[i].data.sr = 1; + // chars[i].w = compData.w; + // chars[i].data.ks = { + // a: { k: [0, 0, 0], a: 0 }, + // p: { k: [0, -compData.h, 0], a: 0 }, + // r: { k: 0, a: 0 }, + // s: { k: [100, 100], a: 0 }, + // o: { k: 100, a: 0 }, + // }; + completeLayers(chars[i].data.layers, assets); + } + } } } - } - } - function findComp(id, comps) { - var i = 0; - var len = comps.length; - while (i < len) { - if (comps[i].id === id) { - return comps[i]; + function findComp(id, comps) { + var i = 0; + var len = comps.length; + while (i < len) { + if (comps[i].id === id) { + return comps[i]; + } + i += 1; + } + return null; } - i += 1; - } - return null; - } - function findCompLayers(id, comps) { - var comp = findComp(id, comps); - if (comp) { - if (!comp.layers.__used) { - comp.layers.__used = true; - return comp.layers; + function findCompLayers(id, comps) { + var comp = findComp(id, comps); + if (comp) { + if (!comp.layers.__used) { + comp.layers.__used = true; + return comp.layers; + } + return JSON.parse(JSON.stringify(comp.layers)); + } + return null; } - return JSON.parse(JSON.stringify(comp.layers)); - } - return null; - } - function completeShapes(arr) { - var i; - var len = arr.length; - var j; - var jLen; - for (i = len - 1; i >= 0; i -= 1) { - if (arr[i].ty === 'sh') { - if (arr[i].ks.k.i) { - convertPathsToAbsoluteValues(arr[i].ks.k); - } else { - jLen = arr[i].ks.k.length; - for (j = 0; j < jLen; j += 1) { - if (arr[i].ks.k[j].s) { - convertPathsToAbsoluteValues(arr[i].ks.k[j].s[0]); - } - if (arr[i].ks.k[j].e) { - convertPathsToAbsoluteValues(arr[i].ks.k[j].e[0]); + function completeShapes(arr) { + var i; + var len = arr.length; + var j; + var jLen; + for (i = len - 1; i >= 0; i -= 1) { + if (arr[i].ty === 'sh') { + if (arr[i].ks.k.i) { + convertPathsToAbsoluteValues(arr[i].ks.k); + } else { + jLen = arr[i].ks.k.length; + for (j = 0; j < jLen; j += 1) { + if (arr[i].ks.k[j].s) { + convertPathsToAbsoluteValues(arr[i].ks.k[j].s[0]); + } + if (arr[i].ks.k[j].e) { + convertPathsToAbsoluteValues(arr[i].ks.k[j].e[0]); + } + } } + } else if (arr[i].ty === 'gr') { + completeShapes(arr[i].it); } } - } else if (arr[i].ty === 'gr') { - completeShapes(arr[i].it); } - } - } - - function convertPathsToAbsoluteValues(path) { - var i; - var len = path.i.length; - for (i = 0; i < len; i += 1) { - path.i[i][0] += path.v[i][0]; - path.i[i][1] += path.v[i][1]; - path.o[i][0] += path.v[i][0]; - path.o[i][1] += path.v[i][1]; - } - } - - function checkVersion(minimum, animVersionString) { - var animVersion = animVersionString ? animVersionString.split('.') : [100, 100, 100]; - if (minimum[0] > animVersion[0]) { - return true; - } if (animVersion[0] > minimum[0]) { - return false; - } - if (minimum[1] > animVersion[1]) { - return true; - } if (animVersion[1] > minimum[1]) { - return false; - } - if (minimum[2] > animVersion[2]) { - return true; - } if (animVersion[2] > minimum[2]) { - return false; - } - return null; - } - - var checkText = (function () { - var minimumVersion = [4, 4, 14]; - function updateTextLayer(textLayer) { - var documentData = textLayer.t.d; - textLayer.t.d = { - k: [ - { - s: documentData, - t: 0, - }, - ], - }; - } + function convertPathsToAbsoluteValues(path) { + var i; + var len = path.i.length; + for (i = 0; i < len; i += 1) { + path.i[i][0] += path.v[i][0]; + path.i[i][1] += path.v[i][1]; + path.o[i][0] += path.v[i][0]; + path.o[i][1] += path.v[i][1]; + } + } - function iterateLayers(layers) { - var i; - var len = layers.length; - for (i = 0; i < len; i += 1) { - if (layers[i].ty === 5) { - updateTextLayer(layers[i]); + function checkVersion(minimum, animVersionString) { + var animVersion = animVersionString ? animVersionString.split('.') : [100, 100, 100]; + if (minimum[0] > animVersion[0]) { + return true; + } if (animVersion[0] > minimum[0]) { + return false; } + if (minimum[1] > animVersion[1]) { + return true; + } if (animVersion[1] > minimum[1]) { + return false; + } + if (minimum[2] > animVersion[2]) { + return true; + } if (animVersion[2] > minimum[2]) { + return false; + } + return null; } - } - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { + var checkText = (function () { + var minimumVersion = [4, 4, 14]; + + function updateTextLayer(textLayer) { + var documentData = textLayer.t.d; + textLayer.t.d = { + k: [ + { + s: documentData, + t: 0, + }, + ], + }; + } + + function iterateLayers(layers) { var i; - var len = animationData.assets.length; + var len = layers.length; for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); + if (layers[i].ty === 5) { + updateTextLayer(layers[i]); } } } - } - }; - }()); - var checkChars = (function () { - var minimumVersion = [4, 7, 99]; - return function (animationData) { - if (animationData.chars && !checkVersion(minimumVersion, animationData.v)) { - var i; - var len = animationData.chars.length; - for (i = 0; i < len; i += 1) { - var charData = animationData.chars[i]; - if (charData.data && charData.data.shapes) { - completeShapes(charData.data.shapes); - charData.data.ip = 0; - charData.data.op = 99999; - charData.data.st = 0; - charData.data.sr = 1; - charData.data.ks = { - p: { k: [0, 0], a: 0 }, - s: { k: [100, 100], a: 0 }, - a: { k: [0, 0], a: 0 }, - r: { k: 0, a: 0 }, - o: { k: 100, a: 0 }, - }; - if (!animationData.chars[i].t) { - charData.data.shapes.push( - { - ty: 'no', + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { + var i; + var len = animationData.assets.length; + for (i = 0; i < len; i += 1) { + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); } - ); - charData.data.shapes[0].it.push( - { + } + } + } + }; + }()); + + var checkChars = (function () { + var minimumVersion = [4, 7, 99]; + return function (animationData) { + if (animationData.chars && !checkVersion(minimumVersion, animationData.v)) { + var i; + var len = animationData.chars.length; + for (i = 0; i < len; i += 1) { + var charData = animationData.chars[i]; + if (charData.data && charData.data.shapes) { + completeShapes(charData.data.shapes); + charData.data.ip = 0; + charData.data.op = 99999; + charData.data.st = 0; + charData.data.sr = 1; + charData.data.ks = { p: { k: [0, 0], a: 0 }, s: { k: [100, 100], a: 0 }, a: { k: [0, 0], a: 0 }, r: { k: 0, a: 0 }, o: { k: 100, a: 0 }, - sk: { k: 0, a: 0 }, - sa: { k: 0, a: 0 }, - ty: 'tr', + }; + if (!animationData.chars[i].t) { + charData.data.shapes.push( + { + ty: 'no', + } + ); + charData.data.shapes[0].it.push( + { + p: { k: [0, 0], a: 0 }, + s: { k: [100, 100], a: 0 }, + a: { k: [0, 0], a: 0 }, + r: { k: 0, a: 0 }, + o: { k: 100, a: 0 }, + sk: { k: 0, a: 0 }, + sa: { k: 0, a: 0 }, + ty: 'tr', + } + ); } - ); + } } } - } - } - }; - }()); - - var checkPathProperties = (function () { - var minimumVersion = [5, 7, 15]; - - function updateTextLayer(textLayer) { - var pathData = textLayer.t.p; - if (typeof pathData.a === 'number') { - pathData.a = { - a: 0, - k: pathData.a, }; - } - if (typeof pathData.p === 'number') { - pathData.p = { - a: 0, - k: pathData.p, - }; - } - if (typeof pathData.r === 'number') { - pathData.r = { - a: 0, - k: pathData.r, - }; - } - } + }()); - function iterateLayers(layers) { - var i; - var len = layers.length; - for (i = 0; i < len; i += 1) { - if (layers[i].ty === 5) { - updateTextLayer(layers[i]); + var checkPathProperties = (function () { + var minimumVersion = [5, 7, 15]; + + function updateTextLayer(textLayer) { + var pathData = textLayer.t.p; + if (typeof pathData.a === 'number') { + pathData.a = { + a: 0, + k: pathData.a, + }; + } + if (typeof pathData.p === 'number') { + pathData.p = { + a: 0, + k: pathData.p, + }; + } + if (typeof pathData.r === 'number') { + pathData.r = { + a: 0, + k: pathData.r, + }; + } } - } - } - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { + function iterateLayers(layers) { var i; - var len = animationData.assets.length; + var len = layers.length; for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); + if (layers[i].ty === 5) { + updateTextLayer(layers[i]); } } } - } - }; - }()); - var checkColors = (function () { - var minimumVersion = [4, 1, 9]; - - function iterateShapes(shapes) { - var i; - var len = shapes.length; - var j; - var jLen; - for (i = 0; i < len; i += 1) { - if (shapes[i].ty === 'gr') { - iterateShapes(shapes[i].it); - } else if (shapes[i].ty === 'fl' || shapes[i].ty === 'st') { - if (shapes[i].c.k && shapes[i].c.k[0].i) { - jLen = shapes[i].c.k.length; - for (j = 0; j < jLen; j += 1) { - if (shapes[i].c.k[j].s) { - shapes[i].c.k[j].s[0] /= 255; - shapes[i].c.k[j].s[1] /= 255; - shapes[i].c.k[j].s[2] /= 255; - shapes[i].c.k[j].s[3] /= 255; - } - if (shapes[i].c.k[j].e) { - shapes[i].c.k[j].e[0] /= 255; - shapes[i].c.k[j].e[1] /= 255; - shapes[i].c.k[j].e[2] /= 255; - shapes[i].c.k[j].e[3] /= 255; + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { + var i; + var len = animationData.assets.length; + for (i = 0; i < len; i += 1) { + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); + } } } - } else { - shapes[i].c.k[0] /= 255; - shapes[i].c.k[1] /= 255; - shapes[i].c.k[2] /= 255; - shapes[i].c.k[3] /= 255; } - } - } - } + }; + }()); - function iterateLayers(layers) { - var i; - var len = layers.length; - for (i = 0; i < len; i += 1) { - if (layers[i].ty === 4) { - iterateShapes(layers[i].shapes); - } - } - } + var checkColors = (function () { + var minimumVersion = [4, 1, 9]; - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { + function iterateShapes(shapes) { var i; - var len = animationData.assets.length; + var len = shapes.length; + var j; + var jLen; for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); + if (shapes[i].ty === 'gr') { + iterateShapes(shapes[i].it); + } else if (shapes[i].ty === 'fl' || shapes[i].ty === 'st') { + if (shapes[i].c.k && shapes[i].c.k[0].i) { + jLen = shapes[i].c.k.length; + for (j = 0; j < jLen; j += 1) { + if (shapes[i].c.k[j].s) { + shapes[i].c.k[j].s[0] /= 255; + shapes[i].c.k[j].s[1] /= 255; + shapes[i].c.k[j].s[2] /= 255; + shapes[i].c.k[j].s[3] /= 255; + } + if (shapes[i].c.k[j].e) { + shapes[i].c.k[j].e[0] /= 255; + shapes[i].c.k[j].e[1] /= 255; + shapes[i].c.k[j].e[2] /= 255; + shapes[i].c.k[j].e[3] /= 255; + } + } + } else { + shapes[i].c.k[0] /= 255; + shapes[i].c.k[1] /= 255; + shapes[i].c.k[2] /= 255; + shapes[i].c.k[3] /= 255; + } } } } - } - }; - }()); - - var checkShapes = (function () { - var minimumVersion = [4, 4, 18]; - - function completeClosingShapes(arr) { - var i; - var len = arr.length; - var j; - var jLen; - for (i = len - 1; i >= 0; i -= 1) { - if (arr[i].ty === 'sh') { - if (arr[i].ks.k.i) { - arr[i].ks.k.c = arr[i].closed; - } else { - jLen = arr[i].ks.k.length; - for (j = 0; j < jLen; j += 1) { - if (arr[i].ks.k[j].s) { - arr[i].ks.k[j].s[0].c = arr[i].closed; - } - if (arr[i].ks.k[j].e) { - arr[i].ks.k[j].e[0].c = arr[i].closed; - } + + function iterateLayers(layers) { + var i; + var len = layers.length; + for (i = 0; i < len; i += 1) { + if (layers[i].ty === 4) { + iterateShapes(layers[i].shapes); } } - } else if (arr[i].ty === 'gr') { - completeClosingShapes(arr[i].it); } - } - } - function iterateLayers(layers) { - var layerData; - var i; - var len = layers.length; - var j; - var jLen; - var k; - var kLen; - for (i = 0; i < len; i += 1) { - layerData = layers[i]; - if (layerData.hasMask) { - var maskProps = layerData.masksProperties; - jLen = maskProps.length; - for (j = 0; j < jLen; j += 1) { - if (maskProps[j].pt.k.i) { - maskProps[j].pt.k.c = maskProps[j].cl; - } else { - kLen = maskProps[j].pt.k.length; - for (k = 0; k < kLen; k += 1) { - if (maskProps[j].pt.k[k].s) { - maskProps[j].pt.k[k].s[0].c = maskProps[j].cl; - } - if (maskProps[j].pt.k[k].e) { - maskProps[j].pt.k[k].e[0].c = maskProps[j].cl; + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { + var i; + var len = animationData.assets.length; + for (i = 0; i < len; i += 1) { + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); } } } } - } - if (layerData.ty === 4) { - completeClosingShapes(layerData.shapes); - } - } - } + }; + }()); + + var checkShapes = (function () { + var minimumVersion = [4, 4, 18]; - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { + function completeClosingShapes(arr) { var i; - var len = animationData.assets.length; - for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); + var len = arr.length; + var j; + var jLen; + for (i = len - 1; i >= 0; i -= 1) { + if (arr[i].ty === 'sh') { + if (arr[i].ks.k.i) { + arr[i].ks.k.c = arr[i].closed; + } else { + jLen = arr[i].ks.k.length; + for (j = 0; j < jLen; j += 1) { + if (arr[i].ks.k[j].s) { + arr[i].ks.k[j].s[0].c = arr[i].closed; + } + if (arr[i].ks.k[j].e) { + arr[i].ks.k[j].e[0].c = arr[i].closed; + } + } + } + } else if (arr[i].ty === 'gr') { + completeClosingShapes(arr[i].it); } } } - } - }; - }()); - - function completeData(animationData) { - if (animationData.__complete) { - return; - } - checkColors(animationData); - checkText(animationData); - checkChars(animationData); - checkPathProperties(animationData); - checkShapes(animationData); - completeLayers(animationData.layers, animationData.assets); - completeChars(animationData.chars, animationData.assets); - animationData.__complete = true; - } - - function completeText(data) { - if (data.t.a.length === 0 && !('m' in data.t.p)) { - // data.singleShape = true; - } - } - var moduleOb = {}; - moduleOb.completeData = completeData; - moduleOb.checkColors = checkColors; - moduleOb.checkChars = checkChars; - moduleOb.checkPathProperties = checkPathProperties; - moduleOb.checkShapes = checkShapes; - moduleOb.completeLayers = completeLayers; + function iterateLayers(layers) { + var layerData; + var i; + var len = layers.length; + var j; + var jLen; + var k; + var kLen; + for (i = 0; i < len; i += 1) { + layerData = layers[i]; + if (layerData.hasMask) { + var maskProps = layerData.masksProperties; + jLen = maskProps.length; + for (j = 0; j < jLen; j += 1) { + if (maskProps[j].pt.k.i) { + maskProps[j].pt.k.c = maskProps[j].cl; + } else { + kLen = maskProps[j].pt.k.length; + for (k = 0; k < kLen; k += 1) { + if (maskProps[j].pt.k[k].s) { + maskProps[j].pt.k[k].s[0].c = maskProps[j].cl; + } + if (maskProps[j].pt.k[k].e) { + maskProps[j].pt.k[k].e[0].c = maskProps[j].cl; + } + } + } + } + } + if (layerData.ty === 4) { + completeClosingShapes(layerData.shapes); + } + } + } - return moduleOb; - } - if (!_workerSelf.dataManager) { - _workerSelf.dataManager = dataFunctionManager(); - } + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { + var i; + var len = animationData.assets.length; + for (i = 0; i < len; i += 1) { + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); + } + } + } + } + }; + }()); - if (!_workerSelf.assetLoader) { - _workerSelf.assetLoader = (function () { - function formatResponse(xhr) { - // using typeof doubles the time of execution of this method, - // so if available, it's better to use the header to validate the type - var contentTypeHeader = xhr.getResponseHeader('content-type'); - if (contentTypeHeader && xhr.responseType === 'json' && contentTypeHeader.indexOf('json') !== -1) { - return xhr.response; + function completeData(animationData) { + if (animationData.__complete) { + return; + } + checkColors(animationData); + checkText(animationData); + checkChars(animationData); + checkPathProperties(animationData); + checkShapes(animationData); + completeLayers(animationData.layers, animationData.assets); + completeChars(animationData.chars, animationData.assets); + animationData.__complete = true; } - if (xhr.response && typeof xhr.response === 'object') { - return xhr.response; - } if (xhr.response && typeof xhr.response === 'string') { - return JSON.parse(xhr.response); - } if (xhr.responseText) { - return JSON.parse(xhr.responseText); + + function completeText(data) { + if (data.t.a.length === 0 && !('m' in data.t.p)) { + // data.singleShape = true; + } } - return null; + + var moduleOb = {}; + moduleOb.completeData = completeData; + moduleOb.checkColors = checkColors; + moduleOb.checkChars = checkChars; + moduleOb.checkPathProperties = checkPathProperties; + moduleOb.checkShapes = checkShapes; + moduleOb.completeLayers = completeLayers; + + return moduleOb; + } + if (!_workerSelf.dataManager) { + _workerSelf.dataManager = dataFunctionManager(); } - function loadAsset(path, fullPath, callback, errorCallback) { - var response; - var xhr = new XMLHttpRequest(); - // set responseType after calling open or IE will break. - try { - // This crashes on Android WebView prior to KitKat - xhr.responseType = 'json'; - } catch (err) {} // eslint-disable-line no-empty - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - response = formatResponse(xhr); - callback(response); - } else { - try { - response = formatResponse(xhr); - callback(response); - } catch (err) { - if (errorCallback) { - errorCallback(err); + if (!_workerSelf.assetLoader) { + _workerSelf.assetLoader = (function () { + function formatResponse(xhr) { + // using typeof doubles the time of execution of this method, + // so if available, it's better to use the header to validate the type + var contentTypeHeader = xhr.getResponseHeader('content-type'); + if (contentTypeHeader && xhr.responseType === 'json' && contentTypeHeader.indexOf('json') !== -1) { + return xhr.response; + } + if (xhr.response && typeof xhr.response === 'object') { + return xhr.response; + } if (xhr.response && typeof xhr.response === 'string') { + return JSON.parse(xhr.response); + } if (xhr.responseText) { + return JSON.parse(xhr.responseText); + } + return null; + } + + function loadAsset(path, fullPath, callback, errorCallback) { + var response; + var xhr = new XMLHttpRequest(); + // set responseType after calling open or IE will break. + try { + // This crashes on Android WebView prior to KitKat + xhr.responseType = 'json'; + } catch (err) {} // eslint-disable-line no-empty + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + response = formatResponse(xhr); + callback(response); + } else { + try { + response = formatResponse(xhr); + callback(response); + } catch (err) { + if (errorCallback) { + errorCallback(err); + } + } } } + }; + try { + xhr.open('GET', path, true); + } catch (error) { + xhr.open('GET', fullPath + '/' + path, true); } + xhr.send(); } - }; - try { - xhr.open('GET', path, true); - } catch (error) { - xhr.open('GET', fullPath + '/' + path, true); - } - xhr.send(); + return { + load: loadAsset, + }; + }()); } - return { - load: loadAsset, - }; - }()); - } - if (e.data.type === 'loadAnimation') { - _workerSelf.assetLoader.load( - e.data.path, - e.data.fullPath, - function (data) { - _workerSelf.dataManager.completeData(data); + if (e.data.type === 'loadAnimation') { + _workerSelf.assetLoader.load( + e.data.path, + e.data.fullPath, + function (data) { + _workerSelf.dataManager.completeData(data); + _workerSelf.postMessage({ + id: e.data.id, + payload: data, + status: 'success', + }); + }, + function () { + _workerSelf.postMessage({ + id: e.data.id, + status: 'error', + }); + } + ); + } else if (e.data.type === 'complete') { + var animation = e.data.animation; + _workerSelf.dataManager.completeData(animation); _workerSelf.postMessage({ id: e.data.id, - payload: data, + payload: animation, status: 'success', }); - }, - function () { - _workerSelf.postMessage({ - id: e.data.id, - status: 'error', - }); + } else if (e.data.type === 'loadData') { + _workerSelf.assetLoader.load( + e.data.path, + e.data.fullPath, + function (data) { + _workerSelf.postMessage({ + id: e.data.id, + payload: data, + status: 'success', + }); + }, + function () { + _workerSelf.postMessage({ + id: e.data.id, + status: 'error', + }); + } + ); } - ); - } else if (e.data.type === 'complete') { - var animation = e.data.animation; - _workerSelf.dataManager.completeData(animation); - _workerSelf.postMessage({ - id: e.data.id, - payload: animation, - status: 'success', }); - } else if (e.data.type === 'loadData') { - _workerSelf.assetLoader.load( - e.data.path, - e.data.fullPath, - function (data) { - _workerSelf.postMessage({ - id: e.data.id, - payload: data, - status: 'success', - }); - }, - function () { - _workerSelf.postMessage({ - id: e.data.id, - status: 'error', - }); - } - ); - } - }); - workerInstance.onmessage = function (event) { - var data = event.data; - var id = data.id; - var process = processes[id]; - processes[id] = null; - if (data.status === 'success') { - process.onComplete(data.payload); - } else if (process.onError) { - process.onError(); - } - }; - } - } - - function createProcess(onComplete, onError) { - _counterId += 1; - var id = 'processId_' + _counterId; - processes[id] = { - onComplete: onComplete, - onError: onError, - }; - return id; - } - - function loadAnimation(path, onComplete, onError) { - setupWorker(); - var processId = createProcess(onComplete, onError); - workerInstance.postMessage({ - type: 'loadAnimation', - path: path, - fullPath: window.location.origin + window.location.pathname, - id: processId, - }); - } - - function loadData(path, onComplete, onError) { - setupWorker(); - var processId = createProcess(onComplete, onError); - workerInstance.postMessage({ - type: 'loadData', - path: path, - fullPath: window.location.origin + window.location.pathname, - id: processId, - }); - } - - function completeAnimation(anim, onComplete, onError) { - setupWorker(); - var processId = createProcess(onComplete, onError); - workerInstance.postMessage({ - type: 'complete', - animation: anim, - id: processId, - }); - } - - return { - loadAnimation: loadAnimation, - loadData: loadData, - completeAnimation: completeAnimation, - }; -}()); - -const ImagePreloader = (function () { - var proxyImage = (function () { - var canvas = createTag('canvas'); - canvas.width = 1; - canvas.height = 1; - var ctx = canvas.getContext('2d'); - ctx.fillStyle = 'rgba(0,0,0,0)'; - ctx.fillRect(0, 0, 1, 1); - return canvas; - }()); - - function imageLoaded() { - this.loadedAssets += 1; - if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { - if (this.imagesLoadedCb) { - this.imagesLoadedCb(null); - } - } - } - function footageLoaded() { - this.loadedFootagesCount += 1; - if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { - if (this.imagesLoadedCb) { - this.imagesLoadedCb(null); - } - } - } - - function getAssetsPath(assetData, assetsPath, originalPath) { - var path = ''; - if (assetData.e) { - path = assetData.p; - } else if (assetsPath) { - var imagePath = assetData.p; - if (imagePath.indexOf('images/') !== -1) { - imagePath = imagePath.split('/')[1]; - } - path = assetsPath + imagePath; - } else { - path = originalPath; - path += assetData.u ? assetData.u : ''; - path += assetData.p; - } - return path; - } - - function testImageLoaded(img) { - var _count = 0; - var intervalId = setInterval(function () { - var box = img.getBBox(); - if (box.width || _count > 500) { - this._imageLoaded(); - clearInterval(intervalId); - } - _count += 1; - }.bind(this), 50); - } - - function createImageData(assetData) { - var path = getAssetsPath(assetData, this.assetsPath, this.path); - var img = createNS('image'); - if (isSafari) { - this.testImageLoaded(img); - } else { - img.addEventListener('load', this._imageLoaded, false); - } - img.addEventListener('error', function () { - ob.img = proxyImage; - this._imageLoaded(); - }.bind(this), false); - img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path); - if (this._elementHelper.append) { - this._elementHelper.append(img); - } else { - this._elementHelper.appendChild(img); - } - var ob = { - img: img, - assetData: assetData, - }; - return ob; - } - - function createImgData(assetData) { - var path = getAssetsPath(assetData, this.assetsPath, this.path); - var img = createTag('img'); - img.crossOrigin = 'anonymous'; - img.addEventListener('load', this._imageLoaded, false); - img.addEventListener('error', function () { - ob.img = proxyImage; - this._imageLoaded(); - }.bind(this), false); - img.src = path; - var ob = { - img: img, - assetData: assetData, - }; - return ob; - } - - function createFootageData(data) { - var ob = { - assetData: data, - }; - var path = getAssetsPath(data, this.assetsPath, this.path); - dataManager.loadData(path, function (footageData) { - ob.img = footageData; - this._footageLoaded(); - }.bind(this), function () { - ob.img = {}; - this._footageLoaded(); - }.bind(this)); - return ob; - } - - function loadAssets(assets, cb) { - this.imagesLoadedCb = cb; - var i; - var len = assets.length; - for (i = 0; i < len; i += 1) { - if (!assets[i].layers) { - if (!assets[i].t || assets[i].t === 'seq') { - this.totalImages += 1; - this.images.push(this._createImageData(assets[i])); - } else if (assets[i].t === 3) { - this.totalFootages += 1; - this.images.push(this.createFootageData(assets[i])); + workerInstance.onmessage = function (event) { + var data = event.data; + var id = data.id; + var process = processes[id]; + processes[id] = null; + if (data.status === 'success') { + process.onComplete(data.payload); + } else if (process.onError) { + process.onError(); + } + }; } } - } - } - function setPath(path) { - this.path = path || ''; - } + function createProcess(onComplete, onError) { + _counterId += 1; + var id = 'processId_' + _counterId; + processes[id] = { + onComplete: onComplete, + onError: onError, + }; + return id; + } - function setAssetsPath(path) { - this.assetsPath = path || ''; - } + function loadAnimation(path, onComplete, onError) { + setupWorker(); + var processId = createProcess(onComplete, onError); + workerInstance.postMessage({ + type: 'loadAnimation', + path: path, + fullPath: window.location.origin + window.location.pathname, + id: processId, + }); + } - function getAsset(assetData) { - var i = 0; - var len = this.images.length; - while (i < len) { - if (this.images[i].assetData === assetData) { - return this.images[i].img; + function loadData(path, onComplete, onError) { + setupWorker(); + var processId = createProcess(onComplete, onError); + workerInstance.postMessage({ + type: 'loadData', + path: path, + fullPath: window.location.origin + window.location.pathname, + id: processId, + }); } - i += 1; - } - return null; - } - - function destroy() { - this.imagesLoadedCb = null; - this.images.length = 0; - } - - function loadedImages() { - return this.totalImages === this.loadedAssets; - } - - function loadedFootages() { - return this.totalFootages === this.loadedFootagesCount; - } - - function setCacheType(type, elementHelper) { - if (type === 'svg') { - this._elementHelper = elementHelper; - this._createImageData = this.createImageData.bind(this); - } else { - this._createImageData = this.createImgData.bind(this); - } - } - - function ImagePreloaderFactory() { - this._imageLoaded = imageLoaded.bind(this); - this._footageLoaded = footageLoaded.bind(this); - this.testImageLoaded = testImageLoaded.bind(this); - this.createFootageData = createFootageData.bind(this); - this.assetsPath = ''; - this.path = ''; - this.totalImages = 0; - this.totalFootages = 0; - this.loadedAssets = 0; - this.loadedFootagesCount = 0; - this.imagesLoadedCb = null; - this.images = []; - } - - ImagePreloaderFactory.prototype = { - loadAssets: loadAssets, - setAssetsPath: setAssetsPath, - setPath: setPath, - loadedImages: loadedImages, - loadedFootages: loadedFootages, - destroy: destroy, - getAsset: getAsset, - createImgData: createImgData, - createImageData: createImageData, - imageLoaded: imageLoaded, - footageLoaded: footageLoaded, - setCacheType: setCacheType, - }; - - return ImagePreloaderFactory; -}()); - -function BaseEvent() {} -BaseEvent.prototype = { - triggerEvent: function (eventName, args) { - if (this._cbs[eventName]) { - var callbacks = this._cbs[eventName]; - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i](args); + + function completeAnimation(anim, onComplete, onError) { + setupWorker(); + var processId = createProcess(onComplete, onError); + workerInstance.postMessage({ + type: 'complete', + animation: anim, + id: processId, + }); } - } - }, - addEventListener: function (eventName, callback) { - if (!this._cbs[eventName]) { - this._cbs[eventName] = []; - } - this._cbs[eventName].push(callback); - - return function () { - this.removeEventListener(eventName, callback); - }.bind(this); - }, - removeEventListener: function (eventName, callback) { - if (!callback) { - this._cbs[eventName] = null; - } else if (this._cbs[eventName]) { - var i = 0; - var len = this._cbs[eventName].length; - while (i < len) { - if (this._cbs[eventName][i] === callback) { - this._cbs[eventName].splice(i, 1); - i -= 1; - len -= 1; + + return { + loadAnimation: loadAnimation, + loadData: loadData, + completeAnimation: completeAnimation, + }; + }()); + + const ImagePreloader = (function () { + var proxyImage = (function () { + var canvas = createTag('canvas'); + canvas.width = 1; + canvas.height = 1; + var ctx = canvas.getContext('2d'); + ctx.fillStyle = 'rgba(0,0,0,0)'; + ctx.fillRect(0, 0, 1, 1); + return canvas; + }()); + + function imageLoaded() { + this.loadedAssets += 1; + if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { + if (this.imagesLoadedCb) { + this.imagesLoadedCb(null); + } } - i += 1; } - if (!this._cbs[eventName].length) { - this._cbs[eventName] = null; + function footageLoaded() { + this.loadedFootagesCount += 1; + if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { + if (this.imagesLoadedCb) { + this.imagesLoadedCb(null); + } + } } - } - }, -}; - -const markerParser = ( - function () { - function parsePayloadLines(payload) { - var lines = payload.split('\r\n'); - var keys = {}; - var line; - var keysCount = 0; - for (var i = 0; i < lines.length; i += 1) { - line = lines[i].split(':'); - if (line.length === 2) { - keys[line[0]] = line[1].trim(); - keysCount += 1; + function getAssetsPath(assetData, assetsPath, originalPath) { + var path = ''; + if (assetData.e) { + path = assetData.p; + } else if (assetsPath) { + var imagePath = assetData.p; + if (imagePath.indexOf('images/') !== -1) { + imagePath = imagePath.split('/')[1]; + } + path = assetsPath + imagePath; + } else { + path = originalPath; + path += assetData.u ? assetData.u : ''; + path += assetData.p; } + return path; } - if (keysCount === 0) { - throw new Error(); - } - return keys; - } - return function (_markers) { - var markers = []; - for (var i = 0; i < _markers.length; i += 1) { - var _marker = _markers[i]; - var markerData = { - time: _marker.tm, - duration: _marker.dr, - }; - try { - markerData.payload = JSON.parse(_markers[i].cm); - } catch (_) { - try { - markerData.payload = parsePayloadLines(_markers[i].cm); - } catch (__) { - markerData.payload = { - name: _markers[i].cm, - }; + function testImageLoaded(img) { + var _count = 0; + var intervalId = setInterval(function () { + var box = img.getBBox(); + if (box.width || _count > 500) { + this._imageLoaded(); + clearInterval(intervalId); } - } - markers.push(markerData); + _count += 1; + }.bind(this), 50); } - return markers; - }; - }()); -const ProjectInterface = (function () { - function registerComposition(comp) { - this.compositions.push(comp); - } + function createImageData(assetData) { + var path = getAssetsPath(assetData, this.assetsPath, this.path); + var img = createNS('image'); + if (isSafari) { + this.testImageLoaded(img); + } else { + img.addEventListener('load', this._imageLoaded, false); + } + img.addEventListener('error', function () { + ob.img = proxyImage; + this._imageLoaded(); + }.bind(this), false); + img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path); + if (this._elementHelper.append) { + this._elementHelper.append(img); + } else { + this._elementHelper.appendChild(img); + } + var ob = { + img: img, + assetData: assetData, + }; + return ob; + } + + function createImgData(assetData) { + var path = getAssetsPath(assetData, this.assetsPath, this.path); + var img = createTag('img'); + img.crossOrigin = 'anonymous'; + img.addEventListener('load', this._imageLoaded, false); + img.addEventListener('error', function () { + ob.img = proxyImage; + this._imageLoaded(); + }.bind(this), false); + img.src = path; + var ob = { + img: img, + assetData: assetData, + }; + return ob; + } - return function () { - function _thisProjectFunction(name) { - var i = 0; - var len = this.compositions.length; - while (i < len) { - if (this.compositions[i].data && this.compositions[i].data.nm === name) { - if (this.compositions[i].prepareFrame && this.compositions[i].data.xt) { - this.compositions[i].prepareFrame(this.currentFrame); + function createFootageData(data) { + var ob = { + assetData: data, + }; + var path = getAssetsPath(data, this.assetsPath, this.path); + dataManager.loadData(path, function (footageData) { + ob.img = footageData; + this._footageLoaded(); + }.bind(this), function () { + ob.img = {}; + this._footageLoaded(); + }.bind(this)); + return ob; + } + + function loadAssets(assets, cb) { + this.imagesLoadedCb = cb; + var i; + var len = assets.length; + for (i = 0; i < len; i += 1) { + if (!assets[i].layers) { + if (!assets[i].t || assets[i].t === 'seq') { + this.totalImages += 1; + this.images.push(this._createImageData(assets[i])); + } else if (assets[i].t === 3) { + this.totalFootages += 1; + this.images.push(this.createFootageData(assets[i])); + } } - return this.compositions[i].compInterface; } - i += 1; } - return null; - } - _thisProjectFunction.compositions = []; - _thisProjectFunction.currentFrame = 0; - - _thisProjectFunction.registerComposition = registerComposition; - - return _thisProjectFunction; - }; -}()); - -const renderers = {}; - -const registerRenderer = (key, value) => { - renderers[key] = value; -}; - -function getRenderer(key) { - return renderers[key]; -} - -const AnimationItem = function () { - this._cbs = []; - this.name = ''; - this.path = ''; - this.isLoaded = false; - this.currentFrame = 0; - this.currentRawFrame = 0; - this.firstFrame = 0; - this.totalFrames = 0; - this.frameRate = 0; - this.frameMult = 0; - this.playSpeed = 1; - this.playDirection = 1; - this.playCount = 0; - this.animationData = {}; - this.assets = []; - this.isPaused = true; - this.autoplay = false; - this.loop = true; - this.renderer = null; - this.animationID = createElementID(); - this.assetsPath = ''; - this.timeCompleted = 0; - this.segmentPos = 0; - this.isSubframeEnabled = getSubframeEnabled(); - this.segments = []; - this._idle = true; - this._completedLoop = false; - this.projectInterface = ProjectInterface(); - this.imagePreloader = new ImagePreloader(); - this.audioController = audioControllerFactory(); - this.markers = []; - this.configAnimation = this.configAnimation.bind(this); - this.onSetupError = this.onSetupError.bind(this); - this.onSegmentComplete = this.onSegmentComplete.bind(this); - this.drawnFrameEvent = new BMEnterFrameEvent('drawnFrame', 0, 0, 0); -}; - -extendPrototype([BaseEvent], AnimationItem); - -AnimationItem.prototype.setParams = function (params) { - if (params.wrapper || params.container) { - this.wrapper = params.wrapper || params.container; - } - var animType = 'svg'; - if (params.animType) { - animType = params.animType; - } else if (params.renderer) { - animType = params.renderer; - } - const RendererClass = getRenderer(animType); - this.renderer = new RendererClass(this, params.rendererSettings); - this.imagePreloader.setCacheType(animType, this.renderer.globalData.defs); - this.renderer.setProjectInterface(this.projectInterface); - this.animType = animType; - if (params.loop === '' - || params.loop === null - || params.loop === undefined - || params.loop === true) { - this.loop = true; - } else if (params.loop === false) { - this.loop = false; - } else { - this.loop = parseInt(params.loop, 10); - } - this.autoplay = 'autoplay' in params ? params.autoplay : true; - this.name = params.name ? params.name : ''; - this.autoloadSegments = Object.prototype.hasOwnProperty.call(params, 'autoloadSegments') ? params.autoloadSegments : true; - this.assetsPath = params.assetsPath; - this.initialSegment = params.initialSegment; - if (params.audioFactory) { - this.audioController.setAudioFactory(params.audioFactory); - } - if (params.animationData) { - this.setupAnimation(params.animationData); - } else if (params.path) { - if (params.path.lastIndexOf('\\') !== -1) { - this.path = params.path.substr(0, params.path.lastIndexOf('\\') + 1); - } else { - this.path = params.path.substr(0, params.path.lastIndexOf('/') + 1); - } - this.fileName = params.path.substr(params.path.lastIndexOf('/') + 1); - this.fileName = this.fileName.substr(0, this.fileName.lastIndexOf('.json')); - dataManager.loadAnimation( - params.path, - this.configAnimation, - this.onSetupError - ); - } -}; - -AnimationItem.prototype.onSetupError = function () { - this.trigger('data_failed'); -}; - -AnimationItem.prototype.setupAnimation = function (data) { - dataManager.completeAnimation( - data, - this.configAnimation - ); -}; - -AnimationItem.prototype.setData = function (wrapper, animationData) { - if (animationData) { - if (typeof animationData !== 'object') { - animationData = JSON.parse(animationData); - } - } - var params = { - wrapper: wrapper, - animationData: animationData, - }; - var wrapperAttributes = wrapper.attributes; - - params.path = wrapperAttributes.getNamedItem('data-animation-path') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-animation-path').value - : wrapperAttributes.getNamedItem('data-bm-path') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-path').value - : wrapperAttributes.getNamedItem('bm-path') - ? wrapperAttributes.getNamedItem('bm-path').value - : ''; - params.animType = wrapperAttributes.getNamedItem('data-anim-type') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-type').value - : wrapperAttributes.getNamedItem('data-bm-type') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-type').value - : wrapperAttributes.getNamedItem('bm-type') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('bm-type').value - : wrapperAttributes.getNamedItem('data-bm-renderer') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-renderer').value - : wrapperAttributes.getNamedItem('bm-renderer') - ? wrapperAttributes.getNamedItem('bm-renderer').value - : 'canvas'; - - var loop = wrapperAttributes.getNamedItem('data-anim-loop') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-loop').value - : wrapperAttributes.getNamedItem('data-bm-loop') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-loop').value - : wrapperAttributes.getNamedItem('bm-loop') - ? wrapperAttributes.getNamedItem('bm-loop').value - : ''; - if (loop === 'false') { - params.loop = false; - } else if (loop === 'true') { - params.loop = true; - } else if (loop !== '') { - params.loop = parseInt(loop, 10); - } - var autoplay = wrapperAttributes.getNamedItem('data-anim-autoplay') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-autoplay').value - : wrapperAttributes.getNamedItem('data-bm-autoplay') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-autoplay').value - : wrapperAttributes.getNamedItem('bm-autoplay') - ? wrapperAttributes.getNamedItem('bm-autoplay').value - : true; - params.autoplay = autoplay !== 'false'; - - params.name = wrapperAttributes.getNamedItem('data-name') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-name').value - : wrapperAttributes.getNamedItem('data-bm-name') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-name').value - : wrapperAttributes.getNamedItem('bm-name') - ? wrapperAttributes.getNamedItem('bm-name').value - : ''; - var prerender = wrapperAttributes.getNamedItem('data-anim-prerender') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-prerender').value - : wrapperAttributes.getNamedItem('data-bm-prerender') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-prerender').value - : wrapperAttributes.getNamedItem('bm-prerender') - ? wrapperAttributes.getNamedItem('bm-prerender').value - : ''; - - if (prerender === 'false') { - params.prerender = false; - } - this.setParams(params); -}; - -AnimationItem.prototype.includeLayers = function (data) { - if (data.op > this.animationData.op) { - this.animationData.op = data.op; - this.totalFrames = Math.floor(data.op - this.animationData.ip); - } - var layers = this.animationData.layers; - var i; - var len = layers.length; - var newLayers = data.layers; - var j; - var jLen = newLayers.length; - for (j = 0; j < jLen; j += 1) { - i = 0; - while (i < len) { - if (layers[i].id === newLayers[j].id) { - layers[i] = newLayers[j]; - break; - } - i += 1; - } - } - if (data.chars || data.fonts) { - this.renderer.globalData.fontManager.addChars(data.chars); - this.renderer.globalData.fontManager.addFonts(data.fonts, this.renderer.globalData.defs); - } - if (data.assets) { - len = data.assets.length; - for (i = 0; i < len; i += 1) { - this.animationData.assets.push(data.assets[i]); - } - } - this.animationData.__complete = false; - dataManager.completeAnimation( - this.animationData, - this.onSegmentComplete - ); -}; - -AnimationItem.prototype.onSegmentComplete = function (data) { - this.animationData = data; - var expressionsPlugin = getExpressionsPlugin(); - if (expressionsPlugin) { - expressionsPlugin.initExpressions(this); - } - this.loadNextSegment(); -}; - -AnimationItem.prototype.loadNextSegment = function () { - var segments = this.animationData.segments; - if (!segments || segments.length === 0 || !this.autoloadSegments) { - this.trigger('data_ready'); - this.timeCompleted = this.totalFrames; - return; - } - var segment = segments.shift(); - this.timeCompleted = segment.time * this.frameRate; - var segmentPath = this.path + this.fileName + '_' + this.segmentPos + '.json'; - this.segmentPos += 1; - dataManager.loadData(segmentPath, this.includeLayers.bind(this), function () { - this.trigger('data_failed'); - }.bind(this)); -}; - -AnimationItem.prototype.loadSegments = function () { - var segments = this.animationData.segments; - if (!segments) { - this.timeCompleted = this.totalFrames; - } - this.loadNextSegment(); -}; - -AnimationItem.prototype.imagesLoaded = function () { - this.trigger('loaded_images'); - this.checkLoaded(); -}; - -AnimationItem.prototype.preloadImages = function () { - this.imagePreloader.setAssetsPath(this.assetsPath); - this.imagePreloader.setPath(this.path); - this.imagePreloader.loadAssets(this.animationData.assets, this.imagesLoaded.bind(this)); -}; - -AnimationItem.prototype.configAnimation = function (animData) { - if (!this.renderer) { - return; - } - try { - this.animationData = animData; - if (this.initialSegment) { - this.totalFrames = Math.floor(this.initialSegment[1] - this.initialSegment[0]); - this.firstFrame = Math.round(this.initialSegment[0]); - } else { - this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip); - this.firstFrame = Math.round(this.animationData.ip); - } - this.renderer.configAnimation(animData); - if (!animData.assets) { - animData.assets = []; - } + function setPath(path) { + this.path = path || ''; + } - this.assets = this.animationData.assets; - this.frameRate = this.animationData.fr; - this.frameMult = this.animationData.fr / 1000; - this.renderer.searchExtraCompositions(animData.assets); - this.markers = markerParser(animData.markers || []); - this.trigger('config_ready'); - this.preloadImages(); - this.loadSegments(); - this.updaFrameModifier(); - this.waitForFontsLoaded(); - if (this.isPaused) { - this.audioController.pause(); - } - } catch (error) { - this.triggerConfigError(error); - } -}; - -AnimationItem.prototype.waitForFontsLoaded = function () { - if (!this.renderer) { - return; - } - if (this.renderer.globalData.fontManager.isLoaded) { - this.checkLoaded(); - } else { - setTimeout(this.waitForFontsLoaded.bind(this), 20); - } -}; - -AnimationItem.prototype.checkLoaded = function () { - if (!this.isLoaded - && this.renderer.globalData.fontManager.isLoaded - && (this.imagePreloader.loadedImages() || this.renderer.rendererType !== 'canvas') - && (this.imagePreloader.loadedFootages()) - ) { - this.isLoaded = true; - var expressionsPlugin = getExpressionsPlugin(); - if (expressionsPlugin) { - expressionsPlugin.initExpressions(this); - } - this.renderer.initItems(); - setTimeout(function () { - this.trigger('DOMLoaded'); - }.bind(this), 0); - this.gotoFrame(); - if (this.autoplay) { - this.play(); - } - } -}; - -AnimationItem.prototype.resize = function () { - this.renderer.updateContainerSize(); -}; - -AnimationItem.prototype.setSubframe = function (flag) { - this.isSubframeEnabled = !!flag; -}; - -AnimationItem.prototype.gotoFrame = function () { - this.currentFrame = this.isSubframeEnabled ? this.currentRawFrame : ~~this.currentRawFrame; // eslint-disable-line no-bitwise - - if (this.timeCompleted !== this.totalFrames && this.currentFrame > this.timeCompleted) { - this.currentFrame = this.timeCompleted; - } - this.trigger('enterFrame'); - this.renderFrame(); - this.trigger('drawnFrame'); -}; - -AnimationItem.prototype.renderFrame = function () { - if (this.isLoaded === false || !this.renderer) { - return; - } - try { - this.renderer.renderFrame(this.currentFrame + this.firstFrame); - } catch (error) { - this.triggerRenderFrameError(error); - } -}; - -AnimationItem.prototype.play = function (name) { - if (name && this.name !== name) { - return; - } - if (this.isPaused === true) { - this.isPaused = false; - this.trigger('_pause'); - this.audioController.resume(); - if (this._idle) { - this._idle = false; - this.trigger('_active'); - } - } -}; - -AnimationItem.prototype.pause = function (name) { - if (name && this.name !== name) { - return; - } - if (this.isPaused === false) { - this.isPaused = true; - this.trigger('_play'); - this._idle = true; - this.trigger('_idle'); - this.audioController.pause(); - } -}; - -AnimationItem.prototype.togglePause = function (name) { - if (name && this.name !== name) { - return; - } - if (this.isPaused === true) { - this.play(); - } else { - this.pause(); - } -}; - -AnimationItem.prototype.stop = function (name) { - if (name && this.name !== name) { - return; - } - this.pause(); - this.playCount = 0; - this._completedLoop = false; - this.setCurrentRawFrameValue(0); -}; - -AnimationItem.prototype.getMarkerData = function (markerName) { - var marker; - for (var i = 0; i < this.markers.length; i += 1) { - marker = this.markers[i]; - if (marker.payload && marker.payload.name === markerName) { - return marker; - } - } - return null; -}; - -AnimationItem.prototype.goToAndStop = function (value, isFrame, name) { - if (name && this.name !== name) { - return; - } - var numValue = Number(value); - if (isNaN(numValue)) { - var marker = this.getMarkerData(value); - if (marker) { - this.goToAndStop(marker.time, true); - } - } else if (isFrame) { - this.setCurrentRawFrameValue(value); - } else { - this.setCurrentRawFrameValue(value * this.frameModifier); - } - this.pause(); -}; - -AnimationItem.prototype.goToAndPlay = function (value, isFrame, name) { - if (name && this.name !== name) { - return; - } - var numValue = Number(value); - if (isNaN(numValue)) { - var marker = this.getMarkerData(value); - if (marker) { - if (!marker.duration) { - this.goToAndStop(marker.time, true); - } else { - this.playSegments([marker.time, marker.time + marker.duration], true); + function setAssetsPath(path) { + this.assetsPath = path || ''; } - } - } else { - this.goToAndStop(numValue, isFrame, name); - } - this.play(); -}; - -AnimationItem.prototype.advanceTime = function (value) { - if (this.isPaused === true || this.isLoaded === false) { - return; - } - var nextValue = this.currentRawFrame + value * this.frameModifier; - var _isComplete = false; - // Checking if nextValue > totalFrames - 1 for addressing non looping and looping animations. - // If animation won't loop, it should stop at totalFrames - 1. If it will loop it should complete the last frame and then loop. - if (nextValue >= this.totalFrames - 1 && this.frameModifier > 0) { - if (!this.loop || this.playCount === this.loop) { - if (!this.checkSegments(nextValue > this.totalFrames ? nextValue % this.totalFrames : 0)) { - _isComplete = true; - nextValue = this.totalFrames - 1; - } - } else if (nextValue >= this.totalFrames) { - this.playCount += 1; - if (!this.checkSegments(nextValue % this.totalFrames)) { - this.setCurrentRawFrameValue(nextValue % this.totalFrames); - this._completedLoop = true; - this.trigger('loopComplete'); - } - } else { - this.setCurrentRawFrameValue(nextValue); - } - } else if (nextValue < 0) { - if (!this.checkSegments(nextValue % this.totalFrames)) { - if (this.loop && !(this.playCount-- <= 0 && this.loop !== true)) { // eslint-disable-line no-plusplus - this.setCurrentRawFrameValue(this.totalFrames + (nextValue % this.totalFrames)); - if (!this._completedLoop) { - this._completedLoop = true; - } else { - this.trigger('loopComplete'); + + function getAsset(assetData) { + var i = 0; + var len = this.images.length; + while (i < len) { + if (this.images[i].assetData === assetData) { + return this.images[i].img; + } + i += 1; } - } else { - _isComplete = true; - nextValue = 0; + return null; } - } - } else { - this.setCurrentRawFrameValue(nextValue); - } - if (_isComplete) { - this.setCurrentRawFrameValue(nextValue); - this.pause(); - this.trigger('complete'); - } -}; - -AnimationItem.prototype.adjustSegment = function (arr, offset) { - this.playCount = 0; - if (arr[1] < arr[0]) { - if (this.frameModifier > 0) { - if (this.playSpeed < 0) { - this.setSpeed(-this.playSpeed); - } else { - this.setDirection(-1); + + function destroy() { + this.imagesLoadedCb = null; + this.images.length = 0; } - } - this.totalFrames = arr[0] - arr[1]; - this.timeCompleted = this.totalFrames; - this.firstFrame = arr[1]; - this.setCurrentRawFrameValue(this.totalFrames - 0.001 - offset); - } else if (arr[1] > arr[0]) { - if (this.frameModifier < 0) { - if (this.playSpeed < 0) { - this.setSpeed(-this.playSpeed); - } else { - this.setDirection(1); + + function loadedImages() { + return this.totalImages === this.loadedAssets; } - } - this.totalFrames = arr[1] - arr[0]; - this.timeCompleted = this.totalFrames; - this.firstFrame = arr[0]; - this.setCurrentRawFrameValue(0.001 + offset); - } - this.trigger('segmentStart'); -}; -AnimationItem.prototype.setSegment = function (init, end) { - var pendingFrame = -1; - if (this.isPaused) { - if (this.currentRawFrame + this.firstFrame < init) { - pendingFrame = init; - } else if (this.currentRawFrame + this.firstFrame > end) { - pendingFrame = end - init; - } - } - - this.firstFrame = init; - this.totalFrames = end - init; - this.timeCompleted = this.totalFrames; - if (pendingFrame !== -1) { - this.goToAndStop(pendingFrame, true); - } -}; - -AnimationItem.prototype.playSegments = function (arr, forceFlag) { - if (forceFlag) { - this.segments.length = 0; - } - if (typeof arr[0] === 'object') { - var i; - var len = arr.length; - for (i = 0; i < len; i += 1) { - this.segments.push(arr[i]); - } - } else { - this.segments.push(arr); - } - if (this.segments.length && forceFlag) { - this.adjustSegment(this.segments.shift(), 0); - } - if (this.isPaused) { - this.play(); - } -}; - -AnimationItem.prototype.resetSegments = function (forceFlag) { - this.segments.length = 0; - this.segments.push([this.animationData.ip, this.animationData.op]); - if (forceFlag) { - this.checkSegments(0); - } -}; -AnimationItem.prototype.checkSegments = function (offset) { - if (this.segments.length) { - this.adjustSegment(this.segments.shift(), offset); - return true; - } - return false; -}; - -AnimationItem.prototype.destroy = function (name) { - if ((name && this.name !== name) || !this.renderer) { - return; - } - this.renderer.destroy(); - this.imagePreloader.destroy(); - this.trigger('destroy'); - this._cbs = null; - this.onEnterFrame = null; - this.onLoopComplete = null; - this.onComplete = null; - this.onSegmentStart = null; - this.onDestroy = null; - this.renderer = null; - this.renderer = null; - this.imagePreloader = null; - this.projectInterface = null; -}; - -AnimationItem.prototype.setCurrentRawFrameValue = function (value) { - this.currentRawFrame = value; - this.gotoFrame(); -}; - -AnimationItem.prototype.setSpeed = function (val) { - this.playSpeed = val; - this.updaFrameModifier(); -}; - -AnimationItem.prototype.setDirection = function (val) { - this.playDirection = val < 0 ? -1 : 1; - this.updaFrameModifier(); -}; - -AnimationItem.prototype.setVolume = function (val, name) { - if (name && this.name !== name) { - return; - } - this.audioController.setVolume(val); -}; - -AnimationItem.prototype.getVolume = function () { - return this.audioController.getVolume(); -}; - -AnimationItem.prototype.mute = function (name) { - if (name && this.name !== name) { - return; - } - this.audioController.mute(); -}; - -AnimationItem.prototype.unmute = function (name) { - if (name && this.name !== name) { - return; - } - this.audioController.unmute(); -}; - -AnimationItem.prototype.updaFrameModifier = function () { - this.frameModifier = this.frameMult * this.playSpeed * this.playDirection; - this.audioController.setRate(this.playSpeed * this.playDirection); -}; - -AnimationItem.prototype.getPath = function () { - return this.path; -}; - -AnimationItem.prototype.getAssetsPath = function (assetData) { - var path = ''; - if (assetData.e) { - path = assetData.p; - } else if (this.assetsPath) { - var imagePath = assetData.p; - if (imagePath.indexOf('images/') !== -1) { - imagePath = imagePath.split('/')[1]; - } - path = this.assetsPath + imagePath; - } else { - path = this.path; - path += assetData.u ? assetData.u : ''; - path += assetData.p; - } - return path; -}; - -AnimationItem.prototype.getAssetData = function (id) { - var i = 0; - var len = this.assets.length; - while (i < len) { - if (id === this.assets[i].id) { - return this.assets[i]; - } - i += 1; - } - return null; -}; - -AnimationItem.prototype.hide = function () { - this.renderer.hide(); -}; - -AnimationItem.prototype.show = function () { - this.renderer.show(); -}; - -AnimationItem.prototype.getDuration = function (isFrame) { - return isFrame ? this.totalFrames : this.totalFrames / this.frameRate; -}; - -AnimationItem.prototype.updateDocumentData = function (path, documentData, index) { - try { - var element = this.renderer.getElementByPath(path); - element.updateDocumentData(documentData, index); - } catch (error) { - // TODO: decide how to handle catch case - } -}; - -AnimationItem.prototype.trigger = function (name) { - if (this._cbs && this._cbs[name]) { - switch (name) { - case 'enterFrame': - this.triggerEvent(name, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameModifier)); - break; - case 'drawnFrame': - this.drawnFrameEvent.currentTime = this.currentFrame; - this.drawnFrameEvent.totalTime = this.totalFrames; - this.drawnFrameEvent.direction = this.frameModifier; - this.triggerEvent(name, this.drawnFrameEvent); - break; - case 'loopComplete': - this.triggerEvent(name, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); - break; - case 'complete': - this.triggerEvent(name, new BMCompleteEvent(name, this.frameMult)); - break; - case 'segmentStart': - this.triggerEvent(name, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); - break; - case 'destroy': - this.triggerEvent(name, new BMDestroyEvent(name, this)); - break; - default: - this.triggerEvent(name); - } - } - if (name === 'enterFrame' && this.onEnterFrame) { - this.onEnterFrame.call(this, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameMult)); - } - if (name === 'loopComplete' && this.onLoopComplete) { - this.onLoopComplete.call(this, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); - } - if (name === 'complete' && this.onComplete) { - this.onComplete.call(this, new BMCompleteEvent(name, this.frameMult)); - } - if (name === 'segmentStart' && this.onSegmentStart) { - this.onSegmentStart.call(this, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); - } - if (name === 'destroy' && this.onDestroy) { - this.onDestroy.call(this, new BMDestroyEvent(name, this)); - } -}; - -AnimationItem.prototype.triggerRenderFrameError = function (nativeError) { - var error = new BMRenderFrameErrorEvent(nativeError, this.currentFrame); - this.triggerEvent('error', error); - - if (this.onError) { - this.onError.call(this, error); - } -}; - -AnimationItem.prototype.triggerConfigError = function (nativeError) { - var error = new BMConfigErrorEvent(nativeError, this.currentFrame); - this.triggerEvent('error', error); - - if (this.onError) { - this.onError.call(this, error); - } -}; - -const animationManager = (function () { - var moduleOb = {}; - var registeredAnimations = []; - var initTime = 0; - var len = 0; - var playingAnimationsNum = 0; - var _stopped = true; - var _isFrozen = false; - - function removeElement(ev) { - var i = 0; - var animItem = ev.target; - while (i < len) { - if (registeredAnimations[i].animation === animItem) { - registeredAnimations.splice(i, 1); - i -= 1; - len -= 1; - if (!animItem.isPaused) { - subtractPlayingCount(); - } - } - i += 1; - } - } - function registerAnimation(element, animationData) { - if (!element) { - return null; - } - var i = 0; - while (i < len) { - if (registeredAnimations[i].elem === element && registeredAnimations[i].elem !== null) { - return registeredAnimations[i].animation; + function loadedFootages() { + return this.totalFootages === this.loadedFootagesCount; } - i += 1; - } - var animItem = new AnimationItem(); - setupAnimation(animItem, element); - animItem.setData(element, animationData); - return animItem; - } - - function getRegisteredAnimations() { - var i; - var lenAnims = registeredAnimations.length; - var animations = []; - for (i = 0; i < lenAnims; i += 1) { - animations.push(registeredAnimations[i].animation); - } - return animations; - } - - function addPlayingCount() { - playingAnimationsNum += 1; - activate(); - } - - function subtractPlayingCount() { - playingAnimationsNum -= 1; - } - - function setupAnimation(animItem, element) { - animItem.addEventListener('destroy', removeElement); - animItem.addEventListener('_active', addPlayingCount); - animItem.addEventListener('_idle', subtractPlayingCount); - registeredAnimations.push({ elem: element, animation: animItem }); - len += 1; - } - - function loadAnimation(params) { - var animItem = new AnimationItem(); - setupAnimation(animItem, null); - animItem.setParams(params); - return animItem; - } - - function setSpeed(val, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.setSpeed(val, animation); - } - } - function setDirection(val, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.setDirection(val, animation); - } - } + function setCacheType(type, elementHelper) { + if (type === 'svg') { + this._elementHelper = elementHelper; + this._createImageData = this.createImageData.bind(this); + } else { + this._createImageData = this.createImgData.bind(this); + } + } + + function ImagePreloaderFactory() { + this._imageLoaded = imageLoaded.bind(this); + this._footageLoaded = footageLoaded.bind(this); + this.testImageLoaded = testImageLoaded.bind(this); + this.createFootageData = createFootageData.bind(this); + this.assetsPath = ''; + this.path = ''; + this.totalImages = 0; + this.totalFootages = 0; + this.loadedAssets = 0; + this.loadedFootagesCount = 0; + this.imagesLoadedCb = null; + this.images = []; + } + + ImagePreloaderFactory.prototype = { + loadAssets: loadAssets, + setAssetsPath: setAssetsPath, + setPath: setPath, + loadedImages: loadedImages, + loadedFootages: loadedFootages, + destroy: destroy, + getAsset: getAsset, + createImgData: createImgData, + createImageData: createImageData, + imageLoaded: imageLoaded, + footageLoaded: footageLoaded, + setCacheType: setCacheType, + }; - function play(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.play(animation); - } - } - function resume(nowTime) { - var elapsedTime = nowTime - initTime; - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.advanceTime(elapsedTime); - } - initTime = nowTime; - if (playingAnimationsNum && !_isFrozen) { - window.requestAnimationFrame(resume); - } else { - _stopped = true; - } - } + return ImagePreloaderFactory; + }()); - function first(nowTime) { - initTime = nowTime; - window.requestAnimationFrame(resume); - } + function BaseEvent() {} + BaseEvent.prototype = { + triggerEvent: function (eventName, args) { + if (this._cbs[eventName]) { + var callbacks = this._cbs[eventName]; + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i](args); + } + } + }, + addEventListener: function (eventName, callback) { + if (!this._cbs[eventName]) { + this._cbs[eventName] = []; + } + this._cbs[eventName].push(callback); - function pause(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.pause(animation); - } - } + return function () { + this.removeEventListener(eventName, callback); + }.bind(this); + }, + removeEventListener: function (eventName, callback) { + if (!callback) { + this._cbs[eventName] = null; + } else if (this._cbs[eventName]) { + var i = 0; + var len = this._cbs[eventName].length; + while (i < len) { + if (this._cbs[eventName][i] === callback) { + this._cbs[eventName].splice(i, 1); + i -= 1; + len -= 1; + } + i += 1; + } + if (!this._cbs[eventName].length) { + this._cbs[eventName] = null; + } + } + }, + }; - function goToAndStop(value, isFrame, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.goToAndStop(value, isFrame, animation); - } - } + const markerParser = ( + + function () { + function parsePayloadLines(payload) { + var lines = payload.split('\r\n'); + var keys = {}; + var line; + var keysCount = 0; + for (var i = 0; i < lines.length; i += 1) { + line = lines[i].split(':'); + if (line.length === 2) { + keys[line[0]] = line[1].trim(); + keysCount += 1; + } + } + if (keysCount === 0) { + throw new Error(); + } + return keys; + } - function stop(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.stop(animation); - } - } + return function (_markers) { + var markers = []; + for (var i = 0; i < _markers.length; i += 1) { + var _marker = _markers[i]; + var markerData = { + time: _marker.tm, + duration: _marker.dr, + }; + try { + markerData.payload = JSON.parse(_markers[i].cm); + } catch (_) { + try { + markerData.payload = parsePayloadLines(_markers[i].cm); + } catch (__) { + markerData.payload = { + name: _markers[i].cm, + }; + } + } + markers.push(markerData); + } + return markers; + }; + }()); - function togglePause(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.togglePause(animation); - } - } + const ProjectInterface = (function () { + function registerComposition(comp) { + this.compositions.push(comp); + } - function destroy(animation) { - var i; - for (i = (len - 1); i >= 0; i -= 1) { - registeredAnimations[i].animation.destroy(animation); - } - } - - function searchAnimations(animationData, standalone, renderer) { - var animElements = [].concat([].slice.call(document.getElementsByClassName('lottie')), - [].slice.call(document.getElementsByClassName('bodymovin'))); - var i; - var lenAnims = animElements.length; - for (i = 0; i < lenAnims; i += 1) { - if (renderer) { - animElements[i].setAttribute('data-bm-type', renderer); - } - registerAnimation(animElements[i], animationData); - } - if (standalone && lenAnims === 0) { - if (!renderer) { - renderer = 'svg'; - } - var body = document.getElementsByTagName('body')[0]; - body.innerText = ''; - var div = createTag('div'); - div.style.width = '100%'; - div.style.height = '100%'; - div.setAttribute('data-bm-type', renderer); - body.appendChild(div); - registerAnimation(div, animationData); - } - } + return function () { + function _thisProjectFunction(name) { + var i = 0; + var len = this.compositions.length; + while (i < len) { + if (this.compositions[i].data && this.compositions[i].data.nm === name) { + if (this.compositions[i].prepareFrame && this.compositions[i].data.xt) { + this.compositions[i].prepareFrame(this.currentFrame); + } + return this.compositions[i].compInterface; + } + i += 1; + } + return null; + } - function resize() { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.resize(); - } - } + _thisProjectFunction.compositions = []; + _thisProjectFunction.currentFrame = 0; - function activate() { - if (!_isFrozen && playingAnimationsNum) { - if (_stopped) { - window.requestAnimationFrame(first); - _stopped = false; - } - } - } + _thisProjectFunction.registerComposition = registerComposition; - function freeze() { - _isFrozen = true; - } + return _thisProjectFunction; + }; + }()); - function unfreeze() { - _isFrozen = false; - activate(); - } + const renderers = {}; - function setVolume(val, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.setVolume(val, animation); - } - } + const registerRenderer = (key, value) => { + renderers[key] = value; + }; - function mute(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.mute(animation); - } - } + function getRenderer(key) { + return renderers[key]; + } + + const AnimationItem = function () { + this._cbs = []; + this.name = ''; + this.path = ''; + this.isLoaded = false; + this.currentFrame = 0; + this.currentRawFrame = 0; + this.firstFrame = 0; + this.totalFrames = 0; + this.frameRate = 0; + this.frameMult = 0; + this.playSpeed = 1; + this.playDirection = 1; + this.playCount = 0; + this.animationData = {}; + this.assets = []; + this.isPaused = true; + this.autoplay = false; + this.loop = true; + this.renderer = null; + this.animationID = createElementID(); + this.assetsPath = ''; + this.timeCompleted = 0; + this.segmentPos = 0; + this.isSubframeEnabled = getSubframeEnabled(); + this.segments = []; + this._idle = true; + this._completedLoop = false; + this.projectInterface = ProjectInterface(); + this.imagePreloader = new ImagePreloader(); + this.audioController = audioControllerFactory(); + this.markers = []; + this.configAnimation = this.configAnimation.bind(this); + this.onSetupError = this.onSetupError.bind(this); + this.onSegmentComplete = this.onSegmentComplete.bind(this); + this.drawnFrameEvent = new BMEnterFrameEvent('drawnFrame', 0, 0, 0); + }; - function unmute(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.unmute(animation); - } - } - - moduleOb.registerAnimation = registerAnimation; - moduleOb.loadAnimation = loadAnimation; - moduleOb.setSpeed = setSpeed; - moduleOb.setDirection = setDirection; - moduleOb.play = play; - moduleOb.pause = pause; - moduleOb.stop = stop; - moduleOb.togglePause = togglePause; - moduleOb.searchAnimations = searchAnimations; - moduleOb.resize = resize; - // moduleOb.start = start; - moduleOb.goToAndStop = goToAndStop; - moduleOb.destroy = destroy; - moduleOb.freeze = freeze; - moduleOb.unfreeze = unfreeze; - moduleOb.setVolume = setVolume; - moduleOb.mute = mute; - moduleOb.unmute = unmute; - moduleOb.getRegisteredAnimations = getRegisteredAnimations; - return moduleOb; -}()); - -/* eslint-disable */ -const BezierFactory = (function () { - /** - * BezierEasing - use bezier curve for transition easing function - * by Gaëtan Renaudeau 2014 - 2015 – MIT License - * - * Credits: is based on Firefox's nsSMILKeySpline.cpp - * Usage: - * var spline = BezierEasing([ 0.25, 0.1, 0.25, 1.0 ]) - * spline.get(x) => returns the easing value | x must be in [0, 1] range - * - */ - - var ob = {}; - ob.getBezierEasing = getBezierEasing; - var beziers = {}; - - function getBezierEasing(a, b, c, d, nm) { - var str = nm || ('bez_' + a + '_' + b + '_' + c + '_' + d).replace(/\./g, 'p'); - if (beziers[str]) { - return beziers[str]; - } - var bezEasing = new BezierEasing([a, b, c, d]); - beziers[str] = bezEasing; - return bezEasing; - } - - // These values are established by empiricism with tests (tradeoff: performance VS precision) - var NEWTON_ITERATIONS = 4; - var NEWTON_MIN_SLOPE = 0.001; - var SUBDIVISION_PRECISION = 0.0000001; - var SUBDIVISION_MAX_ITERATIONS = 10; - - var kSplineTableSize = 11; - var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); - - var float32ArraySupported = typeof Float32Array === 'function'; - - function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } - function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } - function C(aA1) { return 3.0 * aA1; } - - // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. - function calcBezier(aT, aA1, aA2) { - return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; - } - - // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. - function getSlope(aT, aA1, aA2) { - return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); - } - - function binarySubdivide(aX, aA, aB, mX1, mX2) { - var currentX, - currentT, - i = 0; - do { - currentT = aA + (aB - aA) / 2.0; - currentX = calcBezier(currentT, mX1, mX2) - aX; - if (currentX > 0.0) { - aB = currentT; + extendPrototype([BaseEvent], AnimationItem); + + AnimationItem.prototype.setParams = function (params) { + if (params.wrapper || params.container) { + this.wrapper = params.wrapper || params.container; + } + var animType = 'svg'; + if (params.animType) { + animType = params.animType; + } else if (params.renderer) { + animType = params.renderer; + } + const RendererClass = getRenderer(animType); + this.renderer = new RendererClass(this, params.rendererSettings); + this.imagePreloader.setCacheType(animType, this.renderer.globalData.defs); + this.renderer.setProjectInterface(this.projectInterface); + this.animType = animType; + if (params.loop === '' + || params.loop === null + || params.loop === undefined + || params.loop === true) { + this.loop = true; + } else if (params.loop === false) { + this.loop = false; } else { - aA = currentT; - } - } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); - return currentT; - } - - function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { - for (var i = 0; i < NEWTON_ITERATIONS; ++i) { - var currentSlope = getSlope(aGuessT, mX1, mX2); - if (currentSlope === 0.0) return aGuessT; - var currentX = calcBezier(aGuessT, mX1, mX2) - aX; - aGuessT -= currentX / currentSlope; - } - return aGuessT; - } - - /** - * points is an array of [ mX1, mY1, mX2, mY2 ] - */ - function BezierEasing(points) { - this._p = points; - this._mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); - this._precomputed = false; - - this.get = this.get.bind(this); - } - - BezierEasing.prototype = { - - get: function (x) { - var mX1 = this._p[0], - mY1 = this._p[1], - mX2 = this._p[2], - mY2 = this._p[3]; - if (!this._precomputed) this._precompute(); - if (mX1 === mY1 && mX2 === mY2) return x; // linear - // Because JavaScript number are imprecise, we should guarantee the extremes are right. - if (x === 0) return 0; - if (x === 1) return 1; - return calcBezier(this._getTForX(x), mY1, mY2); - }, - - // Private part - - _precompute: function () { - var mX1 = this._p[0], - mY1 = this._p[1], - mX2 = this._p[2], - mY2 = this._p[3]; - this._precomputed = true; - if (mX1 !== mY1 || mX2 !== mY2) { this._calcSampleValues(); } - }, - - _calcSampleValues: function () { - var mX1 = this._p[0], - mX2 = this._p[2]; - for (var i = 0; i < kSplineTableSize; ++i) { - this._mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); - } - }, + this.loop = parseInt(params.loop, 10); + } + this.autoplay = 'autoplay' in params ? params.autoplay : true; + this.name = params.name ? params.name : ''; + this.autoloadSegments = Object.prototype.hasOwnProperty.call(params, 'autoloadSegments') ? params.autoloadSegments : true; + this.assetsPath = params.assetsPath; + this.initialSegment = params.initialSegment; + if (params.audioFactory) { + this.audioController.setAudioFactory(params.audioFactory); + } + if (params.animationData) { + this.setupAnimation(params.animationData); + } else if (params.path) { + if (params.path.lastIndexOf('\\') !== -1) { + this.path = params.path.substr(0, params.path.lastIndexOf('\\') + 1); + } else { + this.path = params.path.substr(0, params.path.lastIndexOf('/') + 1); + } + this.fileName = params.path.substr(params.path.lastIndexOf('/') + 1); + this.fileName = this.fileName.substr(0, this.fileName.lastIndexOf('.json')); + dataManager.loadAnimation( + params.path, + this.configAnimation, + this.onSetupError + ); + } + }; - /** - * getTForX chose the fastest heuristic to determine the percentage value precisely from a given X projection. - */ - _getTForX: function (aX) { - var mX1 = this._p[0], - mX2 = this._p[2], - mSampleValues = this._mSampleValues; + AnimationItem.prototype.onSetupError = function () { + this.trigger('data_failed'); + }; - var intervalStart = 0.0; - var currentSample = 1; - var lastSample = kSplineTableSize - 1; + AnimationItem.prototype.setupAnimation = function (data) { + dataManager.completeAnimation( + data, + this.configAnimation + ); + }; - for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { - intervalStart += kSampleStepSize; + AnimationItem.prototype.setData = function (wrapper, animationData) { + if (animationData) { + if (typeof animationData !== 'object') { + animationData = JSON.parse(animationData); + } } - --currentSample; - - // Interpolate to provide an initial guess for t - var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]); - var guessForT = intervalStart + dist * kSampleStepSize; + var params = { + wrapper: wrapper, + animationData: animationData, + }; + var wrapperAttributes = wrapper.attributes; + + params.path = wrapperAttributes.getNamedItem('data-animation-path') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-animation-path').value + : wrapperAttributes.getNamedItem('data-bm-path') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-path').value + : wrapperAttributes.getNamedItem('bm-path') + ? wrapperAttributes.getNamedItem('bm-path').value + : ''; + params.animType = wrapperAttributes.getNamedItem('data-anim-type') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-type').value + : wrapperAttributes.getNamedItem('data-bm-type') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-type').value + : wrapperAttributes.getNamedItem('bm-type') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('bm-type').value + : wrapperAttributes.getNamedItem('data-bm-renderer') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-renderer').value + : wrapperAttributes.getNamedItem('bm-renderer') + ? wrapperAttributes.getNamedItem('bm-renderer').value + : 'canvas'; + + var loop = wrapperAttributes.getNamedItem('data-anim-loop') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-loop').value + : wrapperAttributes.getNamedItem('data-bm-loop') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-loop').value + : wrapperAttributes.getNamedItem('bm-loop') + ? wrapperAttributes.getNamedItem('bm-loop').value + : ''; + if (loop === 'false') { + params.loop = false; + } else if (loop === 'true') { + params.loop = true; + } else if (loop !== '') { + params.loop = parseInt(loop, 10); + } + var autoplay = wrapperAttributes.getNamedItem('data-anim-autoplay') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-autoplay').value + : wrapperAttributes.getNamedItem('data-bm-autoplay') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-autoplay').value + : wrapperAttributes.getNamedItem('bm-autoplay') + ? wrapperAttributes.getNamedItem('bm-autoplay').value + : true; + params.autoplay = autoplay !== 'false'; + + params.name = wrapperAttributes.getNamedItem('data-name') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-name').value + : wrapperAttributes.getNamedItem('data-bm-name') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-name').value + : wrapperAttributes.getNamedItem('bm-name') + ? wrapperAttributes.getNamedItem('bm-name').value + : ''; + var prerender = wrapperAttributes.getNamedItem('data-anim-prerender') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-prerender').value + : wrapperAttributes.getNamedItem('data-bm-prerender') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-prerender').value + : wrapperAttributes.getNamedItem('bm-prerender') + ? wrapperAttributes.getNamedItem('bm-prerender').value + : ''; + + if (prerender === 'false') { + params.prerender = false; + } + this.setParams(params); + }; - var initialSlope = getSlope(guessForT, mX1, mX2); - if (initialSlope >= NEWTON_MIN_SLOPE) { - return newtonRaphsonIterate(aX, guessForT, mX1, mX2); - } if (initialSlope === 0.0) { - return guessForT; + AnimationItem.prototype.includeLayers = function (data) { + if (data.op > this.animationData.op) { + this.animationData.op = data.op; + this.totalFrames = Math.floor(data.op - this.animationData.ip); + } + var layers = this.animationData.layers; + var i; + var len = layers.length; + var newLayers = data.layers; + var j; + var jLen = newLayers.length; + for (j = 0; j < jLen; j += 1) { + i = 0; + while (i < len) { + if (layers[i].id === newLayers[j].id) { + layers[i] = newLayers[j]; + break; + } + i += 1; + } + } + if (data.chars || data.fonts) { + this.renderer.globalData.fontManager.addChars(data.chars); + this.renderer.globalData.fontManager.addFonts(data.fonts, this.renderer.globalData.defs); + } + if (data.assets) { + len = data.assets.length; + for (i = 0; i < len; i += 1) { + this.animationData.assets.push(data.assets[i]); + } } - return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); - }, - }; + this.animationData.__complete = false; + dataManager.completeAnimation( + this.animationData, + this.onSegmentComplete + ); + }; - return ob; -}()); + AnimationItem.prototype.onSegmentComplete = function (data) { + this.animationData = data; + var expressionsPlugin = getExpressionsPlugin(); + if (expressionsPlugin) { + expressionsPlugin.initExpressions(this); + } + this.loadNextSegment(); + }; -const pooling = (function () { - function double(arr) { - return arr.concat(createSizedArray(arr.length)); - } + AnimationItem.prototype.loadNextSegment = function () { + var segments = this.animationData.segments; + if (!segments || segments.length === 0 || !this.autoloadSegments) { + this.trigger('data_ready'); + this.timeCompleted = this.totalFrames; + return; + } + var segment = segments.shift(); + this.timeCompleted = segment.time * this.frameRate; + var segmentPath = this.path + this.fileName + '_' + this.segmentPos + '.json'; + this.segmentPos += 1; + dataManager.loadData(segmentPath, this.includeLayers.bind(this), function () { + this.trigger('data_failed'); + }.bind(this)); + }; - return { - double: double, - }; -}()); + AnimationItem.prototype.loadSegments = function () { + var segments = this.animationData.segments; + if (!segments) { + this.timeCompleted = this.totalFrames; + } + this.loadNextSegment(); + }; -const poolFactory = (function () { - return function (initialLength, _create, _release) { - var _length = 0; - var _maxLength = initialLength; - var pool = createSizedArray(_maxLength); + AnimationItem.prototype.imagesLoaded = function () { + this.trigger('loaded_images'); + this.checkLoaded(); + }; - var ob = { - newElement: newElement, - release: release, + AnimationItem.prototype.preloadImages = function () { + this.imagePreloader.setAssetsPath(this.assetsPath); + this.imagePreloader.setPath(this.path); + this.imagePreloader.loadAssets(this.animationData.assets, this.imagesLoaded.bind(this)); }; - function newElement() { - var element; - if (_length) { - _length -= 1; - element = pool[_length]; - } else { - element = _create(); + AnimationItem.prototype.configAnimation = function (animData) { + if (!this.renderer) { + return; } - return element; - } - - function release(element) { - if (_length === _maxLength) { - pool = pooling.double(pool); - _maxLength *= 2; + try { + this.animationData = animData; + if (this.initialSegment) { + this.totalFrames = Math.floor(this.initialSegment[1] - this.initialSegment[0]); + this.firstFrame = Math.round(this.initialSegment[0]); + } else { + this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip); + this.firstFrame = Math.round(this.animationData.ip); + } + this.renderer.configAnimation(animData); + if (!animData.assets) { + animData.assets = []; + } + + this.assets = this.animationData.assets; + this.frameRate = this.animationData.fr; + this.frameMult = this.animationData.fr / 1000; + this.renderer.searchExtraCompositions(animData.assets); + this.markers = markerParser(animData.markers || []); + this.trigger('config_ready'); + this.preloadImages(); + this.loadSegments(); + this.updaFrameModifier(); + this.waitForFontsLoaded(); + if (this.isPaused) { + this.audioController.pause(); + } + } catch (error) { + this.triggerConfigError(error); + } + }; + + AnimationItem.prototype.waitForFontsLoaded = function () { + if (!this.renderer) { + return; } - if (_release) { - _release(element); + if (this.renderer.globalData.fontManager.isLoaded) { + this.checkLoaded(); + } else { + setTimeout(this.waitForFontsLoaded.bind(this), 20); } - pool[_length] = element; - _length += 1; - } + }; - return ob; - }; -}()); - -const bezierLengthPool = (function () { - function create() { - return { - addedLength: 0, - percents: createTypedArray('float32', getDefaultCurveSegments()), - lengths: createTypedArray('float32', getDefaultCurveSegments()), - }; - } - return poolFactory(8, create); -}()); - -const segmentsLengthPool = (function () { - function create() { - return { - lengths: [], - totalLength: 0, - }; - } - - function release(element) { - var i; - var len = element.lengths.length; - for (i = 0; i < len; i += 1) { - bezierLengthPool.release(element.lengths[i]); - } - element.lengths.length = 0; - } + AnimationItem.prototype.checkLoaded = function () { + if (!this.isLoaded + && this.renderer.globalData.fontManager.isLoaded + && (this.imagePreloader.loadedImages() || this.renderer.rendererType !== 'canvas') + && (this.imagePreloader.loadedFootages()) + ) { + this.isLoaded = true; + var expressionsPlugin = getExpressionsPlugin(); + if (expressionsPlugin) { + expressionsPlugin.initExpressions(this); + } + this.renderer.initItems(); + setTimeout(function () { + this.trigger('DOMLoaded'); + }.bind(this), 0); + this.gotoFrame(); + if (this.autoplay) { + this.play(); + } + } + }; - return poolFactory(8, create, release); -}()); + AnimationItem.prototype.resize = function () { + this.renderer.updateContainerSize(); + }; -function bezFunction() { - var math = Math; + AnimationItem.prototype.setSubframe = function (flag) { + this.isSubframeEnabled = !!flag; + }; - function pointOnLine2D(x1, y1, x2, y2, x3, y3) { - var det1 = (x1 * y2) + (y1 * x3) + (x2 * y3) - (x3 * y2) - (y3 * x1) - (x2 * y1); - return det1 > -0.001 && det1 < 0.001; - } + AnimationItem.prototype.gotoFrame = function () { + this.currentFrame = this.isSubframeEnabled ? this.currentRawFrame : ~~this.currentRawFrame; // eslint-disable-line no-bitwise - function pointOnLine3D(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - if (z1 === 0 && z2 === 0 && z3 === 0) { - return pointOnLine2D(x1, y1, x2, y2, x3, y3); - } - var dist1 = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2)); - var dist2 = math.sqrt(math.pow(x3 - x1, 2) + math.pow(y3 - y1, 2) + math.pow(z3 - z1, 2)); - var dist3 = math.sqrt(math.pow(x3 - x2, 2) + math.pow(y3 - y2, 2) + math.pow(z3 - z2, 2)); - var diffDist; - if (dist1 > dist2) { - if (dist1 > dist3) { - diffDist = dist1 - dist2 - dist3; - } else { - diffDist = dist3 - dist2 - dist1; + if (this.timeCompleted !== this.totalFrames && this.currentFrame > this.timeCompleted) { + this.currentFrame = this.timeCompleted; } - } else if (dist3 > dist2) { - diffDist = dist3 - dist2 - dist1; - } else { - diffDist = dist2 - dist1 - dist3; - } - return diffDist > -0.0001 && diffDist < 0.0001; - } + this.trigger('enterFrame'); + this.renderFrame(); + this.trigger('drawnFrame'); + }; - var getBezierLength = (function () { - return function (pt1, pt2, pt3, pt4) { - var curveSegments = getDefaultCurveSegments(); - var k; - var i; - var len; - var ptCoord; - var perc; - var addedLength = 0; - var ptDistance; - var point = []; - var lastPoint = []; - var lengthData = bezierLengthPool.newElement(); - len = pt3.length; - for (k = 0; k < curveSegments; k += 1) { - perc = k / (curveSegments - 1); - ptDistance = 0; - for (i = 0; i < len; i += 1) { - ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * pt3[i] + 3 * (1 - perc) * bmPow(perc, 2) * pt4[i] + bmPow(perc, 3) * pt2[i]; - point[i] = ptCoord; - if (lastPoint[i] !== null) { - ptDistance += bmPow(point[i] - lastPoint[i], 2); - } - lastPoint[i] = point[i]; - } - if (ptDistance) { - ptDistance = bmSqrt(ptDistance); - addedLength += ptDistance; - } - lengthData.percents[k] = perc; - lengthData.lengths[k] = addedLength; - } - lengthData.addedLength = addedLength; - return lengthData; - }; - }()); - - function getSegmentsLength(shapeData) { - var segmentsLength = segmentsLengthPool.newElement(); - var closed = shapeData.c; - var pathV = shapeData.v; - var pathO = shapeData.o; - var pathI = shapeData.i; - var i; - var len = shapeData._length; - var lengths = segmentsLength.lengths; - var totalLength = 0; - for (i = 0; i < len - 1; i += 1) { - lengths[i] = getBezierLength(pathV[i], pathV[i + 1], pathO[i], pathI[i + 1]); - totalLength += lengths[i].addedLength; - } - if (closed && len) { - lengths[i] = getBezierLength(pathV[i], pathV[0], pathO[i], pathI[0]); - totalLength += lengths[i].addedLength; - } - segmentsLength.totalLength = totalLength; - return segmentsLength; - } - - function BezierData(length) { - this.segmentLength = 0; - this.points = new Array(length); - } - - function PointData(partial, point) { - this.partialLength = partial; - this.point = point; - } - - var buildBezierData = (function () { - var storedData = {}; - - return function (pt1, pt2, pt3, pt4) { - var bezierName = (pt1[0] + '_' + pt1[1] + '_' + pt2[0] + '_' + pt2[1] + '_' + pt3[0] + '_' + pt3[1] + '_' + pt4[0] + '_' + pt4[1]).replace(/\./g, 'p'); - if (!storedData[bezierName]) { - var curveSegments = getDefaultCurveSegments(); - var k; - var i; - var len; - var ptCoord; - var perc; - var addedLength = 0; - var ptDistance; - var point; - var lastPoint = null; - if (pt1.length === 2 && (pt1[0] !== pt2[0] || pt1[1] !== pt2[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt1[0] + pt3[0], pt1[1] + pt3[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt2[0] + pt4[0], pt2[1] + pt4[1])) { - curveSegments = 2; - } - var bezierData = new BezierData(curveSegments); - len = pt3.length; - for (k = 0; k < curveSegments; k += 1) { - point = createSizedArray(len); - perc = k / (curveSegments - 1); - ptDistance = 0; - for (i = 0; i < len; i += 1) { - ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * (pt1[i] + pt3[i]) + 3 * (1 - perc) * bmPow(perc, 2) * (pt2[i] + pt4[i]) + bmPow(perc, 3) * pt2[i]; - point[i] = ptCoord; - if (lastPoint !== null) { - ptDistance += bmPow(point[i] - lastPoint[i], 2); - } - } - ptDistance = bmSqrt(ptDistance); - addedLength += ptDistance; - bezierData.points[k] = new PointData(ptDistance, point); - lastPoint = point; + AnimationItem.prototype.renderFrame = function () { + if (this.isLoaded === false || !this.renderer) { + return; + } + try { + this.renderer.renderFrame(this.currentFrame + this.firstFrame); + } catch (error) { + this.triggerRenderFrameError(error); + } + }; + + AnimationItem.prototype.play = function (name) { + if (name && this.name !== name) { + return; + } + if (this.isPaused === true) { + this.isPaused = false; + this.trigger('_pause'); + this.audioController.resume(); + if (this._idle) { + this._idle = false; + this.trigger('_active'); } - bezierData.segmentLength = addedLength; - storedData[bezierName] = bezierData; } - return storedData[bezierName]; }; - }()); - function getDistancePerc(perc, bezierData) { - var percents = bezierData.percents; - var lengths = bezierData.lengths; - var len = percents.length; - var initPos = bmFloor((len - 1) * perc); - var lengthPos = perc * bezierData.addedLength; - var lPerc = 0; - if (initPos === len - 1 || initPos === 0 || lengthPos === lengths[initPos]) { - return percents[initPos]; - } - var dir = lengths[initPos] > lengthPos ? -1 : 1; - var flag = true; - while (flag) { - if (lengths[initPos] <= lengthPos && lengths[initPos + 1] > lengthPos) { - lPerc = (lengthPos - lengths[initPos]) / (lengths[initPos + 1] - lengths[initPos]); - flag = false; + AnimationItem.prototype.pause = function (name) { + if (name && this.name !== name) { + return; + } + if (this.isPaused === false) { + this.isPaused = true; + this.trigger('_play'); + this._idle = true; + this.trigger('_idle'); + this.audioController.pause(); + } + }; + + AnimationItem.prototype.togglePause = function (name) { + if (name && this.name !== name) { + return; + } + if (this.isPaused === true) { + this.play(); } else { - initPos += dir; + this.pause(); } - if (initPos < 0 || initPos >= len - 1) { - // FIX for TypedArrays that don't store floating point values with enough accuracy - if (initPos === len - 1) { - return percents[initPos]; + }; + + AnimationItem.prototype.stop = function (name) { + if (name && this.name !== name) { + return; + } + this.pause(); + this.playCount = 0; + this._completedLoop = false; + this.setCurrentRawFrameValue(0); + }; + + AnimationItem.prototype.getMarkerData = function (markerName) { + var marker; + for (var i = 0; i < this.markers.length; i += 1) { + marker = this.markers[i]; + if (marker.payload && marker.payload.name === markerName) { + return marker; } - flag = false; } - } - return percents[initPos] + (percents[initPos + 1] - percents[initPos]) * lPerc; - } - - function getPointInSegment(pt1, pt2, pt3, pt4, percent, bezierData) { - var t1 = getDistancePerc(percent, bezierData); - var u1 = 1 - t1; - var ptX = math.round((u1 * u1 * u1 * pt1[0] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[0] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[0] + t1 * t1 * t1 * pt2[0]) * 1000) / 1000; - var ptY = math.round((u1 * u1 * u1 * pt1[1] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[1] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[1] + t1 * t1 * t1 * pt2[1]) * 1000) / 1000; - return [ptX, ptY]; - } - - var bezierSegmentPoints = createTypedArray('float32', 8); - - function getNewSegment(pt1, pt2, pt3, pt4, startPerc, endPerc, bezierData) { - if (startPerc < 0) { - startPerc = 0; - } else if (startPerc > 1) { - startPerc = 1; - } - var t0 = getDistancePerc(startPerc, bezierData); - endPerc = endPerc > 1 ? 1 : endPerc; - var t1 = getDistancePerc(endPerc, bezierData); - var i; - var len = pt1.length; - var u0 = 1 - t0; - var u1 = 1 - t1; - var u0u0u0 = u0 * u0 * u0; - var t0u0u0_3 = t0 * u0 * u0 * 3; // eslint-disable-line camelcase - var t0t0u0_3 = t0 * t0 * u0 * 3; // eslint-disable-line camelcase - var t0t0t0 = t0 * t0 * t0; - // - var u0u0u1 = u0 * u0 * u1; - var t0u0u1_3 = t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1; // eslint-disable-line camelcase - var t0t0u1_3 = t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1; // eslint-disable-line camelcase - var t0t0t1 = t0 * t0 * t1; - // - var u0u1u1 = u0 * u1 * u1; - var t0u1u1_3 = t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1; // eslint-disable-line camelcase - var t0t1u1_3 = t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1; // eslint-disable-line camelcase - var t0t1t1 = t0 * t1 * t1; - // - var u1u1u1 = u1 * u1 * u1; - var t1u1u1_3 = t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1; // eslint-disable-line camelcase - var t1t1u1_3 = t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1; // eslint-disable-line camelcase - var t1t1t1 = t1 * t1 * t1; - for (i = 0; i < len; i += 1) { - bezierSegmentPoints[i * 4] = math.round((u0u0u0 * pt1[i] + t0u0u0_3 * pt3[i] + t0t0u0_3 * pt4[i] + t0t0t0 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - bezierSegmentPoints[i * 4 + 1] = math.round((u0u0u1 * pt1[i] + t0u0u1_3 * pt3[i] + t0t0u1_3 * pt4[i] + t0t0t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - bezierSegmentPoints[i * 4 + 2] = math.round((u0u1u1 * pt1[i] + t0u1u1_3 * pt3[i] + t0t1u1_3 * pt4[i] + t0t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - bezierSegmentPoints[i * 4 + 3] = math.round((u1u1u1 * pt1[i] + t1u1u1_3 * pt3[i] + t1t1u1_3 * pt4[i] + t1t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - } + return null; + }; - return bezierSegmentPoints; - } - - return { - getSegmentsLength: getSegmentsLength, - getNewSegment: getNewSegment, - getPointInSegment: getPointInSegment, - buildBezierData: buildBezierData, - pointOnLine2D: pointOnLine2D, - pointOnLine3D: pointOnLine3D, - }; -} - -const bez = bezFunction(); - -const PropertyFactory = (function () { - var initFrame = initialDefaultFrame; - var mathAbs = Math.abs; - - function interpolateValue(frameNum, caching) { - var offsetTime = this.offsetTime; - var newValue; - if (this.propType === 'multidimensional') { - newValue = createTypedArray('float32', this.pv.length); - } - var iterationIndex = caching.lastIndex; - var i = iterationIndex; - var len = this.keyframes.length - 1; - var flag = true; - var keyData; - var nextKeyData; - var keyframeMetadata; - - while (flag) { - keyData = this.keyframes[i]; - nextKeyData = this.keyframes[i + 1]; - if (i === len - 1 && frameNum >= nextKeyData.t - offsetTime) { - if (keyData.h) { - keyData = nextKeyData; - } - iterationIndex = 0; - break; - } - if ((nextKeyData.t - offsetTime) > frameNum) { - iterationIndex = i; - break; - } - if (i < len - 1) { - i += 1; - } else { - iterationIndex = 0; - flag = false; + AnimationItem.prototype.goToAndStop = function (value, isFrame, name) { + if (name && this.name !== name) { + return; } - } - keyframeMetadata = this.keyframesMetadata[i] || {}; - - var k; - var kLen; - var perc; - var jLen; - var j; - var fnc; - var nextKeyTime = nextKeyData.t - offsetTime; - var keyTime = keyData.t - offsetTime; - var endValue; - if (keyData.to) { - if (!keyframeMetadata.bezierData) { - keyframeMetadata.bezierData = bez.buildBezierData(keyData.s, nextKeyData.s || keyData.e, keyData.to, keyData.ti); - } - var bezierData = keyframeMetadata.bezierData; - if (frameNum >= nextKeyTime || frameNum < keyTime) { - var ind = frameNum >= nextKeyTime ? bezierData.points.length - 1 : 0; - kLen = bezierData.points[ind].point.length; - for (k = 0; k < kLen; k += 1) { - newValue[k] = bezierData.points[ind].point[k]; - } - // caching._lastKeyframeIndex = -1; - } else { - if (keyframeMetadata.__fnct) { - fnc = keyframeMetadata.__fnct; - } else { - fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y, keyData.n).get; - keyframeMetadata.__fnct = fnc; + var numValue = Number(value); + if (isNaN(numValue)) { + var marker = this.getMarkerData(value); + if (marker) { + this.goToAndStop(marker.time, true); } - perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); - var distanceInLine = bezierData.segmentLength * perc; + } else if (isFrame) { + this.setCurrentRawFrameValue(value); + } else { + this.setCurrentRawFrameValue(value * this.frameModifier); + } + this.pause(); + }; - var segmentPerc; - var addedLength = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastAddedLength : 0; - j = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastPoint : 0; - flag = true; - jLen = bezierData.points.length; - while (flag) { - addedLength += bezierData.points[j].partialLength; - if (distanceInLine === 0 || perc === 0 || j === bezierData.points.length - 1) { - kLen = bezierData.points[j].point.length; - for (k = 0; k < kLen; k += 1) { - newValue[k] = bezierData.points[j].point[k]; - } - break; - } else if (distanceInLine >= addedLength && distanceInLine < addedLength + bezierData.points[j + 1].partialLength) { - segmentPerc = (distanceInLine - addedLength) / bezierData.points[j + 1].partialLength; - kLen = bezierData.points[j].point.length; - for (k = 0; k < kLen; k += 1) { - newValue[k] = bezierData.points[j].point[k] + (bezierData.points[j + 1].point[k] - bezierData.points[j].point[k]) * segmentPerc; - } - break; - } - if (j < jLen - 1) { - j += 1; + AnimationItem.prototype.goToAndPlay = function (value, isFrame, name) { + if (name && this.name !== name) { + return; + } + var numValue = Number(value); + if (isNaN(numValue)) { + var marker = this.getMarkerData(value); + if (marker) { + if (!marker.duration) { + this.goToAndStop(marker.time, true); } else { - flag = false; + this.playSegments([marker.time, marker.time + marker.duration], true); } } - caching._lastPoint = j; - caching._lastAddedLength = addedLength - bezierData.points[j].partialLength; - caching._lastKeyframeIndex = i; - } - } else { - var outX; - var outY; - var inX; - var inY; - var keyValue; - len = keyData.s.length; - endValue = nextKeyData.s || keyData.e; - if (this.sh && keyData.h !== 1) { - if (frameNum >= nextKeyTime) { - newValue[0] = endValue[0]; - newValue[1] = endValue[1]; - newValue[2] = endValue[2]; - } else if (frameNum <= keyTime) { - newValue[0] = keyData.s[0]; - newValue[1] = keyData.s[1]; - newValue[2] = keyData.s[2]; - } else { - var quatStart = createQuaternion(keyData.s); - var quatEnd = createQuaternion(endValue); - var time = (frameNum - keyTime) / (nextKeyTime - keyTime); - quaternionToEuler(newValue, slerp(quatStart, quatEnd, time)); - } } else { - for (i = 0; i < len; i += 1) { - if (keyData.h !== 1) { - if (frameNum >= nextKeyTime) { - perc = 1; - } else if (frameNum < keyTime) { - perc = 0; + this.goToAndStop(numValue, isFrame, name); + } + this.play(); + }; + + AnimationItem.prototype.advanceTime = function (value) { + if (this.isPaused === true || this.isLoaded === false) { + return; + } + var nextValue = this.currentRawFrame + value * this.frameModifier; + var _isComplete = false; + // Checking if nextValue > totalFrames - 1 for addressing non looping and looping animations. + // If animation won't loop, it should stop at totalFrames - 1. If it will loop it should complete the last frame and then loop. + if (nextValue >= this.totalFrames - 1 && this.frameModifier > 0) { + if (!this.loop || this.playCount === this.loop) { + if (!this.checkSegments(nextValue > this.totalFrames ? nextValue % this.totalFrames : 0)) { + _isComplete = true; + nextValue = this.totalFrames - 1; + } + } else if (nextValue >= this.totalFrames) { + this.playCount += 1; + if (!this.checkSegments(nextValue % this.totalFrames)) { + this.setCurrentRawFrameValue(nextValue % this.totalFrames); + this._completedLoop = true; + this.trigger('loopComplete'); + } + } else { + this.setCurrentRawFrameValue(nextValue); + } + } else if (nextValue < 0) { + if (!this.checkSegments(nextValue % this.totalFrames)) { + if (this.loop && !(this.playCount-- <= 0 && this.loop !== true)) { // eslint-disable-line no-plusplus + this.setCurrentRawFrameValue(this.totalFrames + (nextValue % this.totalFrames)); + if (!this._completedLoop) { + this._completedLoop = true; } else { - if (keyData.o.x.constructor === Array) { - if (!keyframeMetadata.__fnct) { - keyframeMetadata.__fnct = []; - } - if (!keyframeMetadata.__fnct[i]) { - outX = keyData.o.x[i] === undefined ? keyData.o.x[0] : keyData.o.x[i]; - outY = keyData.o.y[i] === undefined ? keyData.o.y[0] : keyData.o.y[i]; - inX = keyData.i.x[i] === undefined ? keyData.i.x[0] : keyData.i.x[i]; - inY = keyData.i.y[i] === undefined ? keyData.i.y[0] : keyData.i.y[i]; - fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; - keyframeMetadata.__fnct[i] = fnc; - } else { - fnc = keyframeMetadata.__fnct[i]; - } - } else if (!keyframeMetadata.__fnct) { - outX = keyData.o.x; - outY = keyData.o.y; - inX = keyData.i.x; - inY = keyData.i.y; - fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; - keyData.keyframeMetadata = fnc; - } else { - fnc = keyframeMetadata.__fnct; - } - perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); + this.trigger('loopComplete'); } - } - - endValue = nextKeyData.s || keyData.e; - keyValue = keyData.h === 1 ? keyData.s[i] : keyData.s[i] + (endValue[i] - keyData.s[i]) * perc; - - if (this.propType === 'multidimensional') { - newValue[i] = keyValue; } else { - newValue = keyValue; + _isComplete = true; + nextValue = 0; } } + } else { + this.setCurrentRawFrameValue(nextValue); } - } - caching.lastIndex = iterationIndex; - return newValue; - } - - // based on @Toji's https://github.com/toji/gl-matrix/ - function slerp(a, b, t) { - var out = []; - var ax = a[0]; - var ay = a[1]; - var az = a[2]; - var aw = a[3]; - var bx = b[0]; - var by = b[1]; - var bz = b[2]; - var bw = b[3]; - - var omega; - var cosom; - var sinom; - var scale0; - var scale1; - - cosom = ax * bx + ay * by + az * bz + aw * bw; - if (cosom < 0.0) { - cosom = -cosom; - bx = -bx; - by = -by; - bz = -bz; - bw = -bw; - } - if ((1.0 - cosom) > 0.000001) { - omega = Math.acos(cosom); - sinom = Math.sin(omega); - scale0 = Math.sin((1.0 - t) * omega) / sinom; - scale1 = Math.sin(t * omega) / sinom; - } else { - scale0 = 1.0 - t; - scale1 = t; - } - out[0] = scale0 * ax + scale1 * bx; - out[1] = scale0 * ay + scale1 * by; - out[2] = scale0 * az + scale1 * bz; - out[3] = scale0 * aw + scale1 * bw; - - return out; - } - - function quaternionToEuler(out, quat) { - var qx = quat[0]; - var qy = quat[1]; - var qz = quat[2]; - var qw = quat[3]; - var heading = Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy * qy - 2 * qz * qz); - var attitude = Math.asin(2 * qx * qy + 2 * qz * qw); - var bank = Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx * qx - 2 * qz * qz); - out[0] = heading / degToRads; - out[1] = attitude / degToRads; - out[2] = bank / degToRads; - } - - function createQuaternion(values) { - var heading = values[0] * degToRads; - var attitude = values[1] * degToRads; - var bank = values[2] * degToRads; - var c1 = Math.cos(heading / 2); - var c2 = Math.cos(attitude / 2); - var c3 = Math.cos(bank / 2); - var s1 = Math.sin(heading / 2); - var s2 = Math.sin(attitude / 2); - var s3 = Math.sin(bank / 2); - var w = c1 * c2 * c3 - s1 * s2 * s3; - var x = s1 * s2 * c3 + c1 * c2 * s3; - var y = s1 * c2 * c3 + c1 * s2 * s3; - var z = c1 * s2 * c3 - s1 * c2 * s3; - - return [x, y, z, w]; - } - - function getValueAtCurrentTime() { - var frameNum = this.comp.renderedFrame - this.offsetTime; - var initTime = this.keyframes[0].t - this.offsetTime; - var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; - if (!(frameNum === this._caching.lastFrame || (this._caching.lastFrame !== initFrame && ((this._caching.lastFrame >= endTime && frameNum >= endTime) || (this._caching.lastFrame < initTime && frameNum < initTime))))) { - if (this._caching.lastFrame >= frameNum) { - this._caching._lastKeyframeIndex = -1; - this._caching.lastIndex = 0; - } - - var renderResult = this.interpolateValue(frameNum, this._caching); - this.pv = renderResult; - } - this._caching.lastFrame = frameNum; - return this.pv; - } - - function setVValue(val) { - var multipliedValue; - if (this.propType === 'unidimensional') { - multipliedValue = val * this.mult; - if (mathAbs(this.v - multipliedValue) > 0.00001) { - this.v = multipliedValue; - this._mdf = true; - } - } else { - var i = 0; - var len = this.v.length; - while (i < len) { - multipliedValue = val[i] * this.mult; - if (mathAbs(this.v[i] - multipliedValue) > 0.00001) { - this.v[i] = multipliedValue; - this._mdf = true; - } - i += 1; + if (_isComplete) { + this.setCurrentRawFrameValue(nextValue); + this.pause(); + this.trigger('complete'); } - } - } + }; - function processEffectsSequence() { - if (this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) { - return; - } - if (this.lock) { - this.setVValue(this.pv); - return; - } - this.lock = true; - this._mdf = this._isFirstFrame; - var i; - var len = this.effectsSequence.length; - var finalValue = this.kf ? this.pv : this.data.k; - for (i = 0; i < len; i += 1) { - finalValue = this.effectsSequence[i](finalValue); - } - this.setVValue(finalValue); - this._isFirstFrame = false; - this.lock = false; - this.frameId = this.elem.globalData.frameId; - } - - function addEffect(effectFunction) { - this.effectsSequence.push(effectFunction); - this.container.addDynamicProperty(this); - } - - function ValueProperty(elem, data, mult, container) { - this.propType = 'unidimensional'; - this.mult = mult || 1; - this.data = data; - this.v = mult ? data.k * mult : data.k; - this.pv = data.k; - this._mdf = false; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.k = false; - this.kf = false; - this.vel = 0; - this.effectsSequence = []; - this._isFirstFrame = true; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.addEffect = addEffect; - } - - function MultiDimensionalProperty(elem, data, mult, container) { - this.propType = 'multidimensional'; - this.mult = mult || 1; - this.data = data; - this._mdf = false; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.k = false; - this.kf = false; - this.frameId = -1; - var i; - var len = data.k.length; - this.v = createTypedArray('float32', len); - this.pv = createTypedArray('float32', len); - this.vel = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - this.v[i] = data.k[i] * this.mult; - this.pv[i] = data.k[i]; - } - this._isFirstFrame = true; - this.effectsSequence = []; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.addEffect = addEffect; - } - - function KeyframedValueProperty(elem, data, mult, container) { - this.propType = 'unidimensional'; - this.keyframes = data.k; - this.keyframesMetadata = []; - this.offsetTime = elem.data.st; - this.frameId = -1; - this._caching = { - lastFrame: initFrame, lastIndex: 0, value: 0, _lastKeyframeIndex: -1, - }; - this.k = true; - this.kf = true; - this.data = data; - this.mult = mult || 1; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.v = initFrame; - this.pv = initFrame; - this._isFirstFrame = true; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.interpolateValue = interpolateValue; - this.effectsSequence = [getValueAtCurrentTime.bind(this)]; - this.addEffect = addEffect; - } - - function KeyframedMultidimensionalProperty(elem, data, mult, container) { - this.propType = 'multidimensional'; - var i; - var len = data.k.length; - var s; - var e; - var to; - var ti; - for (i = 0; i < len - 1; i += 1) { - if (data.k[i].to && data.k[i].s && data.k[i + 1] && data.k[i + 1].s) { - s = data.k[i].s; - e = data.k[i + 1].s; - to = data.k[i].to; - ti = data.k[i].ti; - if ((s.length === 2 && !(s[0] === e[0] && s[1] === e[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], s[0] + to[0], s[1] + to[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], e[0] + ti[0], e[1] + ti[1])) || (s.length === 3 && !(s[0] === e[0] && s[1] === e[1] && s[2] === e[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], s[0] + to[0], s[1] + to[1], s[2] + to[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], e[0] + ti[0], e[1] + ti[1], e[2] + ti[2]))) { - data.k[i].to = null; - data.k[i].ti = null; - } - if (s[0] === e[0] && s[1] === e[1] && to[0] === 0 && to[1] === 0 && ti[0] === 0 && ti[1] === 0) { - if (s.length === 2 || (s[2] === e[2] && to[2] === 0 && ti[2] === 0)) { - data.k[i].to = null; - data.k[i].ti = null; + AnimationItem.prototype.adjustSegment = function (arr, offset) { + this.playCount = 0; + if (arr[1] < arr[0]) { + if (this.frameModifier > 0) { + if (this.playSpeed < 0) { + this.setSpeed(-this.playSpeed); + } else { + this.setDirection(-1); + } + } + this.totalFrames = arr[0] - arr[1]; + this.timeCompleted = this.totalFrames; + this.firstFrame = arr[1]; + this.setCurrentRawFrameValue(this.totalFrames - 0.001 - offset); + } else if (arr[1] > arr[0]) { + if (this.frameModifier < 0) { + if (this.playSpeed < 0) { + this.setSpeed(-this.playSpeed); + } else { + this.setDirection(1); } } + this.totalFrames = arr[1] - arr[0]; + this.timeCompleted = this.totalFrames; + this.firstFrame = arr[0]; + this.setCurrentRawFrameValue(0.001 + offset); } - } - this.effectsSequence = [getValueAtCurrentTime.bind(this)]; - this.data = data; - this.keyframes = data.k; - this.keyframesMetadata = []; - this.offsetTime = elem.data.st; - this.k = true; - this.kf = true; - this._isFirstFrame = true; - this.mult = mult || 1; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.interpolateValue = interpolateValue; - this.frameId = -1; - var arrLen = data.k[0].s.length; - this.v = createTypedArray('float32', arrLen); - this.pv = createTypedArray('float32', arrLen); - for (i = 0; i < arrLen; i += 1) { - this.v[i] = initFrame; - this.pv[i] = initFrame; - } - this._caching = { lastFrame: initFrame, lastIndex: 0, value: createTypedArray('float32', arrLen) }; - this.addEffect = addEffect; - } - - function getProp(elem, data, type, mult, container) { - var p; - if (!data.k.length) { - p = new ValueProperty(elem, data, mult, container); - } else if (typeof (data.k[0]) === 'number') { - p = new MultiDimensionalProperty(elem, data, mult, container); - } else { - switch (type) { - case 0: - p = new KeyframedValueProperty(elem, data, mult, container); - break; - case 1: - p = new KeyframedMultidimensionalProperty(elem, data, mult, container); - break; - default: - break; + this.trigger('segmentStart'); + }; + AnimationItem.prototype.setSegment = function (init, end) { + var pendingFrame = -1; + if (this.isPaused) { + if (this.currentRawFrame + this.firstFrame < init) { + pendingFrame = init; + } else if (this.currentRawFrame + this.firstFrame > end) { + pendingFrame = end - init; + } } - } - if (p.effectsSequence.length) { - container.addDynamicProperty(p); - } - return p; - } - - var ob = { - getProp: getProp, - }; - return ob; -}()); - -function DynamicPropertyContainer() {} -DynamicPropertyContainer.prototype = { - addDynamicProperty: function (prop) { - if (this.dynamicProperties.indexOf(prop) === -1) { - this.dynamicProperties.push(prop); - this.container.addDynamicProperty(this); - this._isAnimated = true; - } - }, - iterateDynamicProperties: function () { - this._mdf = false; - var i; - var len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - this.dynamicProperties[i].getValue(); - if (this.dynamicProperties[i]._mdf) { - this._mdf = true; + + this.firstFrame = init; + this.totalFrames = end - init; + this.timeCompleted = this.totalFrames; + if (pendingFrame !== -1) { + this.goToAndStop(pendingFrame, true); } - } - }, - initDynamicPropertyContainer: function (container) { - this.container = container; - this.dynamicProperties = []; - this._mdf = false; - this._isAnimated = false; - }, -}; - -const pointPool = (function () { - function create() { - return createTypedArray('float32', 2); - } - return poolFactory(8, create); -}()); - -function ShapePath() { - this.c = false; - this._length = 0; - this._maxLength = 8; - this.v = createSizedArray(this._maxLength); - this.o = createSizedArray(this._maxLength); - this.i = createSizedArray(this._maxLength); -} - -ShapePath.prototype.setPathData = function (closed, len) { - this.c = closed; - this.setLength(len); - var i = 0; - while (i < len) { - this.v[i] = pointPool.newElement(); - this.o[i] = pointPool.newElement(); - this.i[i] = pointPool.newElement(); - i += 1; - } -}; - -ShapePath.prototype.setLength = function (len) { - while (this._maxLength < len) { - this.doubleArrayLength(); - } - this._length = len; -}; - -ShapePath.prototype.doubleArrayLength = function () { - this.v = this.v.concat(createSizedArray(this._maxLength)); - this.i = this.i.concat(createSizedArray(this._maxLength)); - this.o = this.o.concat(createSizedArray(this._maxLength)); - this._maxLength *= 2; -}; - -ShapePath.prototype.setXYAt = function (x, y, type, pos, replace) { - var arr; - this._length = Math.max(this._length, pos + 1); - if (this._length >= this._maxLength) { - this.doubleArrayLength(); - } - switch (type) { - case 'v': - arr = this.v; - break; - case 'i': - arr = this.i; - break; - case 'o': - arr = this.o; - break; - default: - arr = []; - break; - } - if (!arr[pos] || (arr[pos] && !replace)) { - arr[pos] = pointPool.newElement(); - } - arr[pos][0] = x; - arr[pos][1] = y; -}; - -ShapePath.prototype.setTripleAt = function (vX, vY, oX, oY, iX, iY, pos, replace) { - this.setXYAt(vX, vY, 'v', pos, replace); - this.setXYAt(oX, oY, 'o', pos, replace); - this.setXYAt(iX, iY, 'i', pos, replace); -}; - -ShapePath.prototype.reverse = function () { - var newPath = new ShapePath(); - newPath.setPathData(this.c, this._length); - var vertices = this.v; - var outPoints = this.o; - var inPoints = this.i; - var init = 0; - if (this.c) { - newPath.setTripleAt(vertices[0][0], vertices[0][1], inPoints[0][0], inPoints[0][1], outPoints[0][0], outPoints[0][1], 0, false); - init = 1; - } - var cnt = this._length - 1; - var len = this._length; - - var i; - for (i = init; i < len; i += 1) { - newPath.setTripleAt(vertices[cnt][0], vertices[cnt][1], inPoints[cnt][0], inPoints[cnt][1], outPoints[cnt][0], outPoints[cnt][1], i, false); - cnt -= 1; - } - return newPath; -}; - -const shapePool = (function () { - function create() { - return new ShapePath(); - } - - function release(shapePath) { - var len = shapePath._length; - var i; - for (i = 0; i < len; i += 1) { - pointPool.release(shapePath.v[i]); - pointPool.release(shapePath.i[i]); - pointPool.release(shapePath.o[i]); - shapePath.v[i] = null; - shapePath.i[i] = null; - shapePath.o[i] = null; - } - shapePath._length = 0; - shapePath.c = false; - } - - function clone(shape) { - var cloned = factory.newElement(); - var i; - var len = shape._length === undefined ? shape.v.length : shape._length; - cloned.setLength(len); - cloned.c = shape.c; - - for (i = 0; i < len; i += 1) { - cloned.setTripleAt(shape.v[i][0], shape.v[i][1], shape.o[i][0], shape.o[i][1], shape.i[i][0], shape.i[i][1], i); - } - return cloned; - } - - var factory = poolFactory(4, create, release); - factory.clone = clone; - - return factory; -}()); - -function ShapeCollection() { - this._length = 0; - this._maxLength = 4; - this.shapes = createSizedArray(this._maxLength); -} - -ShapeCollection.prototype.addShape = function (shapeData) { - if (this._length === this._maxLength) { - this.shapes = this.shapes.concat(createSizedArray(this._maxLength)); - this._maxLength *= 2; - } - this.shapes[this._length] = shapeData; - this._length += 1; -}; - -ShapeCollection.prototype.releaseShapes = function () { - var i; - for (i = 0; i < this._length; i += 1) { - shapePool.release(this.shapes[i]); - } - this._length = 0; -}; - -const shapeCollectionPool = (function () { - var ob = { - newShapeCollection: newShapeCollection, - release: release, - }; - - var _length = 0; - var _maxLength = 4; - var pool = createSizedArray(_maxLength); - - function newShapeCollection() { - var shapeCollection; - if (_length) { - _length -= 1; - shapeCollection = pool[_length]; - } else { - shapeCollection = new ShapeCollection(); - } - return shapeCollection; - } - - function release(shapeCollection) { - var i; - var len = shapeCollection._length; - for (i = 0; i < len; i += 1) { - shapePool.release(shapeCollection.shapes[i]); - } - shapeCollection._length = 0; + }; - if (_length === _maxLength) { - pool = pooling.double(pool); - _maxLength *= 2; - } - pool[_length] = shapeCollection; - _length += 1; - } - - return ob; -}()); - -const ShapePropertyFactory = (function () { - var initFrame = -999999; - - function interpolateShape(frameNum, previousValue, caching) { - var iterationIndex = caching.lastIndex; - var keyPropS; - var keyPropE; - var isHold; - var j; - var k; - var jLen; - var kLen; - var perc; - var vertexValue; - var kf = this.keyframes; - if (frameNum < kf[0].t - this.offsetTime) { - keyPropS = kf[0].s[0]; - isHold = true; - iterationIndex = 0; - } else if (frameNum >= kf[kf.length - 1].t - this.offsetTime) { - keyPropS = kf[kf.length - 1].s ? kf[kf.length - 1].s[0] : kf[kf.length - 2].e[0]; - /* if(kf[kf.length - 1].s){ - keyPropS = kf[kf.length - 1].s[0]; - }else{ - keyPropS = kf[kf.length - 2].e[0]; - } */ - isHold = true; - } else { - var i = iterationIndex; - var len = kf.length - 1; - var flag = true; - var keyData; - var nextKeyData; - var keyframeMetadata; - while (flag) { - keyData = kf[i]; - nextKeyData = kf[i + 1]; - if ((nextKeyData.t - this.offsetTime) > frameNum) { - break; - } - if (i < len - 1) { - i += 1; - } else { - flag = false; - } + AnimationItem.prototype.playSegments = function (arr, forceFlag) { + if (forceFlag) { + this.segments.length = 0; } - keyframeMetadata = this.keyframesMetadata[i] || {}; - isHold = keyData.h === 1; - iterationIndex = i; - if (!isHold) { - if (frameNum >= nextKeyData.t - this.offsetTime) { - perc = 1; - } else if (frameNum < keyData.t - this.offsetTime) { - perc = 0; - } else { - var fnc; - if (keyframeMetadata.__fnct) { - fnc = keyframeMetadata.__fnct; - } else { - fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y).get; - keyframeMetadata.__fnct = fnc; - } - perc = fnc((frameNum - (keyData.t - this.offsetTime)) / ((nextKeyData.t - this.offsetTime) - (keyData.t - this.offsetTime))); + if (typeof arr[0] === 'object') { + var i; + var len = arr.length; + for (i = 0; i < len; i += 1) { + this.segments.push(arr[i]); } - keyPropE = nextKeyData.s ? nextKeyData.s[0] : keyData.e[0]; + } else { + this.segments.push(arr); } - keyPropS = keyData.s[0]; - } - jLen = previousValue._length; - kLen = keyPropS.i[0].length; - caching.lastIndex = iterationIndex; - - for (j = 0; j < jLen; j += 1) { - for (k = 0; k < kLen; k += 1) { - vertexValue = isHold ? keyPropS.i[j][k] : keyPropS.i[j][k] + (keyPropE.i[j][k] - keyPropS.i[j][k]) * perc; - previousValue.i[j][k] = vertexValue; - vertexValue = isHold ? keyPropS.o[j][k] : keyPropS.o[j][k] + (keyPropE.o[j][k] - keyPropS.o[j][k]) * perc; - previousValue.o[j][k] = vertexValue; - vertexValue = isHold ? keyPropS.v[j][k] : keyPropS.v[j][k] + (keyPropE.v[j][k] - keyPropS.v[j][k]) * perc; - previousValue.v[j][k] = vertexValue; + if (this.segments.length && forceFlag) { + this.adjustSegment(this.segments.shift(), 0); } - } - } - - function interpolateShapeCurrentTime() { - var frameNum = this.comp.renderedFrame - this.offsetTime; - var initTime = this.keyframes[0].t - this.offsetTime; - var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; - var lastFrame = this._caching.lastFrame; - if (!(lastFrame !== initFrame && ((lastFrame < initTime && frameNum < initTime) || (lastFrame > endTime && frameNum > endTime)))) { - /// / - this._caching.lastIndex = lastFrame < frameNum ? this._caching.lastIndex : 0; - this.interpolateShape(frameNum, this.pv, this._caching); - /// / - } - this._caching.lastFrame = frameNum; - return this.pv; - } - - function resetShape() { - this.paths = this.localShapeCollection; - } + if (this.isPaused) { + this.play(); + } + }; - function shapesEqual(shape1, shape2) { - if (shape1._length !== shape2._length || shape1.c !== shape2.c) { - return false; - } - var i; - var len = shape1._length; - for (i = 0; i < len; i += 1) { - if (shape1.v[i][0] !== shape2.v[i][0] - || shape1.v[i][1] !== shape2.v[i][1] - || shape1.o[i][0] !== shape2.o[i][0] - || shape1.o[i][1] !== shape2.o[i][1] - || shape1.i[i][0] !== shape2.i[i][0] - || shape1.i[i][1] !== shape2.i[i][1]) { - return false; + AnimationItem.prototype.resetSegments = function (forceFlag) { + this.segments.length = 0; + this.segments.push([this.animationData.ip, this.animationData.op]); + if (forceFlag) { + this.checkSegments(0); } - } - return true; - } - - function setVValue(newPath) { - if (!shapesEqual(this.v, newPath)) { - this.v = shapePool.clone(newPath); - this.localShapeCollection.releaseShapes(); - this.localShapeCollection.addShape(this.v); - this._mdf = true; - this.paths = this.localShapeCollection; - } - } + }; + AnimationItem.prototype.checkSegments = function (offset) { + if (this.segments.length) { + this.adjustSegment(this.segments.shift(), offset); + return true; + } + return false; + }; - function processEffectsSequence() { - if (this.elem.globalData.frameId === this.frameId) { - return; - } if (!this.effectsSequence.length) { - this._mdf = false; - return; - } - if (this.lock) { - this.setVValue(this.pv); - return; - } - this.lock = true; - this._mdf = false; - var finalValue; - if (this.kf) { - finalValue = this.pv; - } else if (this.data.ks) { - finalValue = this.data.ks.k; - } else { - finalValue = this.data.pt.k; - } - var i; - var len = this.effectsSequence.length; - for (i = 0; i < len; i += 1) { - finalValue = this.effectsSequence[i](finalValue); - } - this.setVValue(finalValue); - this.lock = false; - this.frameId = this.elem.globalData.frameId; - } - - function ShapeProperty(elem, data, type) { - this.propType = 'shape'; - this.comp = elem.comp; - this.container = elem; - this.elem = elem; - this.data = data; - this.k = false; - this.kf = false; - this._mdf = false; - var pathData = type === 3 ? data.pt.k : data.ks.k; - this.v = shapePool.clone(pathData); - this.pv = shapePool.clone(this.v); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.paths = this.localShapeCollection; - this.paths.addShape(this.v); - this.reset = resetShape; - this.effectsSequence = []; - } - - function addEffect(effectFunction) { - this.effectsSequence.push(effectFunction); - this.container.addDynamicProperty(this); - } - - ShapeProperty.prototype.interpolateShape = interpolateShape; - ShapeProperty.prototype.getValue = processEffectsSequence; - ShapeProperty.prototype.setVValue = setVValue; - ShapeProperty.prototype.addEffect = addEffect; - - function KeyframedShapeProperty(elem, data, type) { - this.propType = 'shape'; - this.comp = elem.comp; - this.elem = elem; - this.container = elem; - this.offsetTime = elem.data.st; - this.keyframes = type === 3 ? data.pt.k : data.ks.k; - this.keyframesMetadata = []; - this.k = true; - this.kf = true; - var len = this.keyframes[0].s[0].i.length; - this.v = shapePool.newElement(); - this.v.setPathData(this.keyframes[0].s[0].c, len); - this.pv = shapePool.clone(this.v); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.paths = this.localShapeCollection; - this.paths.addShape(this.v); - this.lastFrame = initFrame; - this.reset = resetShape; - this._caching = { lastFrame: initFrame, lastIndex: 0 }; - this.effectsSequence = [interpolateShapeCurrentTime.bind(this)]; - } - KeyframedShapeProperty.prototype.getValue = processEffectsSequence; - KeyframedShapeProperty.prototype.interpolateShape = interpolateShape; - KeyframedShapeProperty.prototype.setVValue = setVValue; - KeyframedShapeProperty.prototype.addEffect = addEffect; - - var EllShapeProperty = (function () { - var cPoint = roundCorner; - - function EllShapePropertyFactory(elem, data) { - this.v = shapePool.newElement(); - this.v.setPathData(true, 4); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.paths = this.localShapeCollection; - this.localShapeCollection.addShape(this.v); - this.d = data.d; - this.elem = elem; - this.comp = elem.comp; - this.frameId = -1; - this.initDynamicPropertyContainer(elem); - this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); - this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.k = false; - this.convertEllToPath(); + AnimationItem.prototype.destroy = function (name) { + if ((name && this.name !== name) || !this.renderer) { + return; } - } + this.renderer.destroy(); + this.imagePreloader.destroy(); + this.trigger('destroy'); + this._cbs = null; + this.onEnterFrame = null; + this.onLoopComplete = null; + this.onComplete = null; + this.onSegmentStart = null; + this.onDestroy = null; + this.renderer = null; + this.renderer = null; + this.imagePreloader = null; + this.projectInterface = null; + }; - EllShapePropertyFactory.prototype = { - reset: resetShape, - getValue: function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); + AnimationItem.prototype.setCurrentRawFrameValue = function (value) { + this.currentRawFrame = value; + this.gotoFrame(); + }; - if (this._mdf) { - this.convertEllToPath(); - } - }, - convertEllToPath: function () { - var p0 = this.p.v[0]; - var p1 = this.p.v[1]; - var s0 = this.s.v[0] / 2; - var s1 = this.s.v[1] / 2; - var _cw = this.d !== 3; - var _v = this.v; - _v.v[0][0] = p0; - _v.v[0][1] = p1 - s1; - _v.v[1][0] = _cw ? p0 + s0 : p0 - s0; - _v.v[1][1] = p1; - _v.v[2][0] = p0; - _v.v[2][1] = p1 + s1; - _v.v[3][0] = _cw ? p0 - s0 : p0 + s0; - _v.v[3][1] = p1; - _v.i[0][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; - _v.i[0][1] = p1 - s1; - _v.i[1][0] = _cw ? p0 + s0 : p0 - s0; - _v.i[1][1] = p1 - s1 * cPoint; - _v.i[2][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; - _v.i[2][1] = p1 + s1; - _v.i[3][0] = _cw ? p0 - s0 : p0 + s0; - _v.i[3][1] = p1 + s1 * cPoint; - _v.o[0][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; - _v.o[0][1] = p1 - s1; - _v.o[1][0] = _cw ? p0 + s0 : p0 - s0; - _v.o[1][1] = p1 + s1 * cPoint; - _v.o[2][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; - _v.o[2][1] = p1 + s1; - _v.o[3][0] = _cw ? p0 - s0 : p0 + s0; - _v.o[3][1] = p1 - s1 * cPoint; - }, + AnimationItem.prototype.setSpeed = function (val) { + this.playSpeed = val; + this.updaFrameModifier(); }; - extendPrototype([DynamicPropertyContainer], EllShapePropertyFactory); + AnimationItem.prototype.setDirection = function (val) { + this.playDirection = val < 0 ? -1 : 1; + this.updaFrameModifier(); + }; - return EllShapePropertyFactory; - }()); + AnimationItem.prototype.setVolume = function (val, name) { + if (name && this.name !== name) { + return; + } + this.audioController.setVolume(val); + }; - var StarShapeProperty = (function () { - function StarShapePropertyFactory(elem, data) { - this.v = shapePool.newElement(); - this.v.setPathData(true, 0); - this.elem = elem; - this.comp = elem.comp; - this.data = data; - this.frameId = -1; - this.d = data.d; - this.initDynamicPropertyContainer(elem); - if (data.sy === 1) { - this.ir = PropertyFactory.getProp(elem, data.ir, 0, 0, this); - this.is = PropertyFactory.getProp(elem, data.is, 0, 0.01, this); - this.convertToPath = this.convertStarToPath; - } else { - this.convertToPath = this.convertPolygonToPath; - } - this.pt = PropertyFactory.getProp(elem, data.pt, 0, 0, this); - this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); - this.r = PropertyFactory.getProp(elem, data.r, 0, degToRads, this); - this.or = PropertyFactory.getProp(elem, data.or, 0, 0, this); - this.os = PropertyFactory.getProp(elem, data.os, 0, 0.01, this); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.localShapeCollection.addShape(this.v); - this.paths = this.localShapeCollection; - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.k = false; - this.convertToPath(); + AnimationItem.prototype.getVolume = function () { + return this.audioController.getVolume(); + }; + + AnimationItem.prototype.mute = function (name) { + if (name && this.name !== name) { + return; } - } + this.audioController.mute(); + }; - StarShapePropertyFactory.prototype = { - reset: resetShape, - getValue: function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - if (this._mdf) { - this.convertToPath(); - } - }, - convertStarToPath: function () { - var numPts = Math.floor(this.pt.v) * 2; - var angle = (Math.PI * 2) / numPts; - /* this.v.v.length = numPts; - this.v.i.length = numPts; - this.v.o.length = numPts; */ - var longFlag = true; - var longRad = this.or.v; - var shortRad = this.ir.v; - var longRound = this.os.v; - var shortRound = this.is.v; - var longPerimSegment = (2 * Math.PI * longRad) / (numPts * 2); - var shortPerimSegment = (2 * Math.PI * shortRad) / (numPts * 2); - var i; - var rad; - var roundness; - var perimSegment; - var currentAng = -Math.PI / 2; - currentAng += this.r.v; - var dir = this.data.d === 3 ? -1 : 1; - this.v._length = 0; - for (i = 0; i < numPts; i += 1) { - rad = longFlag ? longRad : shortRad; - roundness = longFlag ? longRound : shortRound; - perimSegment = longFlag ? longPerimSegment : shortPerimSegment; - var x = rad * Math.cos(currentAng); - var y = rad * Math.sin(currentAng); - var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); - var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); - x += +this.p.v[0]; - y += +this.p.v[1]; - this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); - - /* this.v.v[i] = [x,y]; - this.v.i[i] = [x+ox*perimSegment*roundness*dir,y+oy*perimSegment*roundness*dir]; - this.v.o[i] = [x-ox*perimSegment*roundness*dir,y-oy*perimSegment*roundness*dir]; - this.v._length = numPts; */ - longFlag = !longFlag; - currentAng += angle * dir; - } - }, - convertPolygonToPath: function () { - var numPts = Math.floor(this.pt.v); - var angle = (Math.PI * 2) / numPts; - var rad = this.or.v; - var roundness = this.os.v; - var perimSegment = (2 * Math.PI * rad) / (numPts * 4); - var i; - var currentAng = -Math.PI * 0.5; - var dir = this.data.d === 3 ? -1 : 1; - currentAng += this.r.v; - this.v._length = 0; - for (i = 0; i < numPts; i += 1) { - var x = rad * Math.cos(currentAng); - var y = rad * Math.sin(currentAng); - var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); - var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); - x += +this.p.v[0]; - y += +this.p.v[1]; - this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); - currentAng += angle * dir; - } - this.paths.length = 0; - this.paths[0] = this.v; - }, + AnimationItem.prototype.unmute = function (name) { + if (name && this.name !== name) { + return; + } + this.audioController.unmute(); + }; + AnimationItem.prototype.updaFrameModifier = function () { + this.frameModifier = this.frameMult * this.playSpeed * this.playDirection; + this.audioController.setRate(this.playSpeed * this.playDirection); }; - extendPrototype([DynamicPropertyContainer], StarShapePropertyFactory); - return StarShapePropertyFactory; - }()); + AnimationItem.prototype.getPath = function () { + return this.path; + }; - var RectShapeProperty = (function () { - function RectShapePropertyFactory(elem, data) { - this.v = shapePool.newElement(); - this.v.c = true; - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.localShapeCollection.addShape(this.v); - this.paths = this.localShapeCollection; - this.elem = elem; - this.comp = elem.comp; - this.frameId = -1; - this.d = data.d; - this.initDynamicPropertyContainer(elem); - this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); - this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); - this.r = PropertyFactory.getProp(elem, data.r, 0, 0, this); - if (this.dynamicProperties.length) { - this.k = true; + AnimationItem.prototype.getAssetsPath = function (assetData) { + var path = ''; + if (assetData.e) { + path = assetData.p; + } else if (this.assetsPath) { + var imagePath = assetData.p; + if (imagePath.indexOf('images/') !== -1) { + imagePath = imagePath.split('/')[1]; + } + path = this.assetsPath + imagePath; } else { - this.k = false; - this.convertRectToPath(); + path = this.path; + path += assetData.u ? assetData.u : ''; + path += assetData.p; } - } + return path; + }; - RectShapePropertyFactory.prototype = { - convertRectToPath: function () { - var p0 = this.p.v[0]; - var p1 = this.p.v[1]; - var v0 = this.s.v[0] / 2; - var v1 = this.s.v[1] / 2; - var round = bmMin(v0, v1, this.r.v); - var cPoint = round * (1 - roundCorner); - this.v._length = 0; - - if (this.d === 2 || this.d === 1) { - this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, 0, true); - this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, p0 + v0, p1 + v1 - round, 1, true); - if (round !== 0) { - this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, 2, true); - this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0 + round, p1 + v1, 3, true); - this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, 4, true); - this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1 + round, 5, true); - this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, 6, true); - this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, p0 + v0 - round, p1 - v1, 7, true); - } else { - this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0, p1 + v1, 2); - this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1, 3); - } - } else { - this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, p0 + v0, p1 - v1 + round, 0, true); - if (round !== 0) { - this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, 1, true); - this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0 + round, p1 - v1, 2, true); - this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, 3, true); - this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1 - round, 4, true); - this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, 5, true); - this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0 - round, p1 + v1, 6, true); - this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, 7, true); - } else { - this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0, p1 - v1, 1, true); - this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1, 2, true); - this.v.setTripleAt(p0 + v0, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0, p1 + v1, 3, true); - } - } - }, - getValue: function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - if (this._mdf) { - this.convertRectToPath(); + AnimationItem.prototype.getAssetData = function (id) { + var i = 0; + var len = this.assets.length; + while (i < len) { + if (id === this.assets[i].id) { + return this.assets[i]; } - }, - reset: resetShape, + i += 1; + } + return null; }; - extendPrototype([DynamicPropertyContainer], RectShapePropertyFactory); - return RectShapePropertyFactory; - }()); + AnimationItem.prototype.hide = function () { + this.renderer.hide(); + }; - function getShapeProp(elem, data, type) { - var prop; - if (type === 3 || type === 4) { - var dataProp = type === 3 ? data.pt : data.ks; - var keys = dataProp.k; - if (keys.length) { - prop = new KeyframedShapeProperty(elem, data, type); - } else { - prop = new ShapeProperty(elem, data, type); - } - } else if (type === 5) { - prop = new RectShapeProperty(elem, data); - } else if (type === 6) { - prop = new EllShapeProperty(elem, data); - } else if (type === 7) { - prop = new StarShapeProperty(elem, data); - } - if (prop.k) { - elem.addDynamicProperty(prop); - } - return prop; - } - - function getConstructorFunction() { - return ShapeProperty; - } - - function getKeyframedConstructorFunction() { - return KeyframedShapeProperty; - } - - var ob = {}; - ob.getShapeProp = getShapeProp; - ob.getConstructorFunction = getConstructorFunction; - ob.getKeyframedConstructorFunction = getKeyframedConstructorFunction; - return ob; -}()); - -/*! - Transformation Matrix v2.0 - (c) Epistemex 2014-2015 - www.epistemex.com - By Ken Fyrstenberg - Contributions by leeoniya. - License: MIT, header required. - */ - -/** - * 2D transformation matrix object initialized with identity matrix. - * - * The matrix can synchronize a canvas context by supplying the context - * as an argument, or later apply current absolute transform to an - * existing context. - * - * All values are handled as floating point values. - * - * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix - * @prop {number} a - scale x - * @prop {number} b - shear y - * @prop {number} c - shear x - * @prop {number} d - scale y - * @prop {number} e - translate x - * @prop {number} f - translate y - * @prop {CanvasRenderingContext2D|null} [context=null] - set or get current canvas context - * @constructor - */ - -const Matrix = (function () { - var _cos = Math.cos; - var _sin = Math.sin; - var _tan = Math.tan; - var _rnd = Math.round; - - function reset() { - this.props[0] = 1; - this.props[1] = 0; - this.props[2] = 0; - this.props[3] = 0; - this.props[4] = 0; - this.props[5] = 1; - this.props[6] = 0; - this.props[7] = 0; - this.props[8] = 0; - this.props[9] = 0; - this.props[10] = 1; - this.props[11] = 0; - this.props[12] = 0; - this.props[13] = 0; - this.props[14] = 0; - this.props[15] = 1; - return this; - } - - function rotate(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - } - - function rotateX(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(1, 0, 0, 0, 0, mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1); - } - - function rotateY(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, 0, mSin, 0, 0, 1, 0, 0, -mSin, 0, mCos, 0, 0, 0, 0, 1); - } - - function rotateZ(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - } - - function shear(sx, sy) { - return this._t(1, sy, sx, 1, 0, 0); - } - - function skew(ax, ay) { - return this.shear(_tan(ax), _tan(ay)); - } - - function skewFromAxis(ax, angle) { - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, mSin, 0, 0, -mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) - ._t(1, 0, 0, 0, _tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) - ._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - // return this._t(mCos, mSin, -mSin, mCos, 0, 0)._t(1, 0, _tan(ax), 1, 0, 0)._t(mCos, -mSin, mSin, mCos, 0, 0); - } - - function scale(sx, sy, sz) { - if (!sz && sz !== 0) { - sz = 1; - } - if (sx === 1 && sy === 1 && sz === 1) { - return this; - } - return this._t(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1); - } - - function setTransform(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { - this.props[0] = a; - this.props[1] = b; - this.props[2] = c; - this.props[3] = d; - this.props[4] = e; - this.props[5] = f; - this.props[6] = g; - this.props[7] = h; - this.props[8] = i; - this.props[9] = j; - this.props[10] = k; - this.props[11] = l; - this.props[12] = m; - this.props[13] = n; - this.props[14] = o; - this.props[15] = p; - return this; - } - - function translate(tx, ty, tz) { - tz = tz || 0; - if (tx !== 0 || ty !== 0 || tz !== 0) { - return this._t(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1); - } - return this; - } - - function transform(a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2) { - var _p = this.props; - - if (a2 === 1 && b2 === 0 && c2 === 0 && d2 === 0 && e2 === 0 && f2 === 1 && g2 === 0 && h2 === 0 && i2 === 0 && j2 === 0 && k2 === 1 && l2 === 0) { - // NOTE: commenting this condition because TurboFan deoptimizes code when present - // if(m2 !== 0 || n2 !== 0 || o2 !== 0){ - _p[12] = _p[12] * a2 + _p[15] * m2; - _p[13] = _p[13] * f2 + _p[15] * n2; - _p[14] = _p[14] * k2 + _p[15] * o2; - _p[15] *= p2; - // } - this._identityCalculated = false; - return this; - } + AnimationItem.prototype.show = function () { + this.renderer.show(); + }; - var a1 = _p[0]; - var b1 = _p[1]; - var c1 = _p[2]; - var d1 = _p[3]; - var e1 = _p[4]; - var f1 = _p[5]; - var g1 = _p[6]; - var h1 = _p[7]; - var i1 = _p[8]; - var j1 = _p[9]; - var k1 = _p[10]; - var l1 = _p[11]; - var m1 = _p[12]; - var n1 = _p[13]; - var o1 = _p[14]; - var p1 = _p[15]; - - /* matrix order (canvas compatible): - * ace - * bdf - * 001 - */ - _p[0] = a1 * a2 + b1 * e2 + c1 * i2 + d1 * m2; - _p[1] = a1 * b2 + b1 * f2 + c1 * j2 + d1 * n2; - _p[2] = a1 * c2 + b1 * g2 + c1 * k2 + d1 * o2; - _p[3] = a1 * d2 + b1 * h2 + c1 * l2 + d1 * p2; - - _p[4] = e1 * a2 + f1 * e2 + g1 * i2 + h1 * m2; - _p[5] = e1 * b2 + f1 * f2 + g1 * j2 + h1 * n2; - _p[6] = e1 * c2 + f1 * g2 + g1 * k2 + h1 * o2; - _p[7] = e1 * d2 + f1 * h2 + g1 * l2 + h1 * p2; - - _p[8] = i1 * a2 + j1 * e2 + k1 * i2 + l1 * m2; - _p[9] = i1 * b2 + j1 * f2 + k1 * j2 + l1 * n2; - _p[10] = i1 * c2 + j1 * g2 + k1 * k2 + l1 * o2; - _p[11] = i1 * d2 + j1 * h2 + k1 * l2 + l1 * p2; - - _p[12] = m1 * a2 + n1 * e2 + o1 * i2 + p1 * m2; - _p[13] = m1 * b2 + n1 * f2 + o1 * j2 + p1 * n2; - _p[14] = m1 * c2 + n1 * g2 + o1 * k2 + p1 * o2; - _p[15] = m1 * d2 + n1 * h2 + o1 * l2 + p1 * p2; - - this._identityCalculated = false; - return this; - } - - function isIdentity() { - if (!this._identityCalculated) { - this._identity = !(this.props[0] !== 1 || this.props[1] !== 0 || this.props[2] !== 0 || this.props[3] !== 0 || this.props[4] !== 0 || this.props[5] !== 1 || this.props[6] !== 0 || this.props[7] !== 0 || this.props[8] !== 0 || this.props[9] !== 0 || this.props[10] !== 1 || this.props[11] !== 0 || this.props[12] !== 0 || this.props[13] !== 0 || this.props[14] !== 0 || this.props[15] !== 1); - this._identityCalculated = true; - } - return this._identity; - } + AnimationItem.prototype.getDuration = function (isFrame) { + return isFrame ? this.totalFrames : this.totalFrames / this.frameRate; + }; - function equals(matr) { - var i = 0; - while (i < 16) { - if (matr.props[i] !== this.props[i]) { - return false; + AnimationItem.prototype.updateDocumentData = function (path, documentData, index) { + try { + var element = this.renderer.getElementByPath(path); + element.updateDocumentData(documentData, index); + } catch (error) { + // TODO: decide how to handle catch case } - i += 1; - } - return true; - } + }; - function clone(matr) { - var i; - for (i = 0; i < 16; i += 1) { - matr.props[i] = this.props[i]; - } - return matr; - } + AnimationItem.prototype.trigger = function (name) { + if (this._cbs && this._cbs[name]) { + switch (name) { + case 'enterFrame': + this.triggerEvent(name, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameModifier)); + break; + case 'drawnFrame': + this.drawnFrameEvent.currentTime = this.currentFrame; + this.drawnFrameEvent.totalTime = this.totalFrames; + this.drawnFrameEvent.direction = this.frameModifier; + this.triggerEvent(name, this.drawnFrameEvent); + break; + case 'loopComplete': + this.triggerEvent(name, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); + break; + case 'complete': + this.triggerEvent(name, new BMCompleteEvent(name, this.frameMult)); + break; + case 'segmentStart': + this.triggerEvent(name, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); + break; + case 'destroy': + this.triggerEvent(name, new BMDestroyEvent(name, this)); + break; + default: + this.triggerEvent(name); + } + } + if (name === 'enterFrame' && this.onEnterFrame) { + this.onEnterFrame.call(this, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameMult)); + } + if (name === 'loopComplete' && this.onLoopComplete) { + this.onLoopComplete.call(this, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); + } + if (name === 'complete' && this.onComplete) { + this.onComplete.call(this, new BMCompleteEvent(name, this.frameMult)); + } + if (name === 'segmentStart' && this.onSegmentStart) { + this.onSegmentStart.call(this, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); + } + if (name === 'destroy' && this.onDestroy) { + this.onDestroy.call(this, new BMDestroyEvent(name, this)); + } + }; - function cloneFromProps(props) { - var i; - for (i = 0; i < 16; i += 1) { - this.props[i] = props[i]; - } - } - - function applyToPoint(x, y, z) { - return { - x: x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], - y: x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], - z: x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], - }; - /* return { - x: x * me.a + y * me.c + me.e, - y: x * me.b + y * me.d + me.f - }; */ - } - function applyToX(x, y, z) { - return x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12]; - } - function applyToY(x, y, z) { - return x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13]; - } - function applyToZ(x, y, z) { - return x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14]; - } - - function getInverseMatrix() { - var determinant = this.props[0] * this.props[5] - this.props[1] * this.props[4]; - var a = this.props[5] / determinant; - var b = -this.props[1] / determinant; - var c = -this.props[4] / determinant; - var d = this.props[0] / determinant; - var e = (this.props[4] * this.props[13] - this.props[5] * this.props[12]) / determinant; - var f = -(this.props[0] * this.props[13] - this.props[1] * this.props[12]) / determinant; - var inverseMatrix = new Matrix(); - inverseMatrix.props[0] = a; - inverseMatrix.props[1] = b; - inverseMatrix.props[4] = c; - inverseMatrix.props[5] = d; - inverseMatrix.props[12] = e; - inverseMatrix.props[13] = f; - return inverseMatrix; - } - - function inversePoint(pt) { - var inverseMatrix = this.getInverseMatrix(); - return inverseMatrix.applyToPointArray(pt[0], pt[1], pt[2] || 0); - } - - function inversePoints(pts) { - var i; - var len = pts.length; - var retPts = []; - for (i = 0; i < len; i += 1) { - retPts[i] = inversePoint(pts[i]); - } - return retPts; - } - - function applyToTriplePoints(pt1, pt2, pt3) { - var arr = createTypedArray('float32', 6); - if (this.isIdentity()) { - arr[0] = pt1[0]; - arr[1] = pt1[1]; - arr[2] = pt2[0]; - arr[3] = pt2[1]; - arr[4] = pt3[0]; - arr[5] = pt3[1]; - } else { - var p0 = this.props[0]; - var p1 = this.props[1]; - var p4 = this.props[4]; - var p5 = this.props[5]; - var p12 = this.props[12]; - var p13 = this.props[13]; - arr[0] = pt1[0] * p0 + pt1[1] * p4 + p12; - arr[1] = pt1[0] * p1 + pt1[1] * p5 + p13; - arr[2] = pt2[0] * p0 + pt2[1] * p4 + p12; - arr[3] = pt2[0] * p1 + pt2[1] * p5 + p13; - arr[4] = pt3[0] * p0 + pt3[1] * p4 + p12; - arr[5] = pt3[0] * p1 + pt3[1] * p5 + p13; - } - return arr; - } - - function applyToPointArray(x, y, z) { - var arr; - if (this.isIdentity()) { - arr = [x, y, z]; - } else { - arr = [ - x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], - x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], - x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], - ]; - } - return arr; - } + AnimationItem.prototype.triggerRenderFrameError = function (nativeError) { + var error = new BMRenderFrameErrorEvent(nativeError, this.currentFrame); + this.triggerEvent('error', error); - function applyToPointStringified(x, y) { - if (this.isIdentity()) { - return x + ',' + y; - } - var _p = this.props; - return Math.round((x * _p[0] + y * _p[4] + _p[12]) * 100) / 100 + ',' + Math.round((x * _p[1] + y * _p[5] + _p[13]) * 100) / 100; - } - - function toCSS() { - // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. - /* if(this.isIdentity()) { - return ''; - } */ - var i = 0; - var props = this.props; - var cssValue = 'matrix3d('; - var v = 10000; - while (i < 16) { - cssValue += _rnd(props[i] * v) / v; - cssValue += i === 15 ? ')' : ','; - i += 1; - } - return cssValue; - } + if (this.onError) { + this.onError.call(this, error); + } + }; - function roundMatrixProperty(val) { - var v = 10000; - if ((val < 0.000001 && val > 0) || (val > -0.000001 && val < 0)) { - return _rnd(val * v) / v; - } - return val; - } + AnimationItem.prototype.triggerConfigError = function (nativeError) { + var error = new BMConfigErrorEvent(nativeError, this.currentFrame); + this.triggerEvent('error', error); - function to2dCSS() { - // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. - /* if(this.isIdentity()) { - return ''; - } */ - var props = this.props; - var _a = roundMatrixProperty(props[0]); - var _b = roundMatrixProperty(props[1]); - var _c = roundMatrixProperty(props[4]); - var _d = roundMatrixProperty(props[5]); - var _e = roundMatrixProperty(props[12]); - var _f = roundMatrixProperty(props[13]); - return 'matrix(' + _a + ',' + _b + ',' + _c + ',' + _d + ',' + _e + ',' + _f + ')'; - } - - return function () { - this.reset = reset; - this.rotate = rotate; - this.rotateX = rotateX; - this.rotateY = rotateY; - this.rotateZ = rotateZ; - this.skew = skew; - this.skewFromAxis = skewFromAxis; - this.shear = shear; - this.scale = scale; - this.setTransform = setTransform; - this.translate = translate; - this.transform = transform; - this.applyToPoint = applyToPoint; - this.applyToX = applyToX; - this.applyToY = applyToY; - this.applyToZ = applyToZ; - this.applyToPointArray = applyToPointArray; - this.applyToTriplePoints = applyToTriplePoints; - this.applyToPointStringified = applyToPointStringified; - this.toCSS = toCSS; - this.to2dCSS = to2dCSS; - this.clone = clone; - this.cloneFromProps = cloneFromProps; - this.equals = equals; - this.inversePoints = inversePoints; - this.inversePoint = inversePoint; - this.getInverseMatrix = getInverseMatrix; - this._t = this.transform; - this.isIdentity = isIdentity; - this._identity = true; - this._identityCalculated = false; - - this.props = createTypedArray('float32', 16); - this.reset(); - }; -}()); - -const lottie = {}; -var standalone = '__[STANDALONE]__'; -var animationData = '__[ANIMATIONDATA]__'; -var renderer = ''; - -function setLocation(href) { - setLocationHref(href); -} - -function searchAnimations() { - if (standalone === true) { - animationManager.searchAnimations(animationData, standalone, renderer); - } else { - animationManager.searchAnimations(); - } -} - -function setSubframeRendering(flag) { - setSubframeEnabled(flag); -} - -function setPrefix(prefix) { - setIdPrefix(prefix); -} - -function loadAnimation(params) { - if (standalone === true) { - params.animationData = JSON.parse(animationData); - } - return animationManager.loadAnimation(params); -} - -function setQuality(value) { - if (typeof value === 'string') { - switch (value) { - case 'high': - setDefaultCurveSegments(200); - break; - default: - case 'medium': - setDefaultCurveSegments(50); - break; - case 'low': - setDefaultCurveSegments(10); - break; - } - } else if (!isNaN(value) && value > 1) { - setDefaultCurveSegments(value); - } - if (getDefaultCurveSegments() >= 50) { - roundValues(false); - } else { - roundValues(true); - } -} - -function inBrowser() { - return typeof navigator !== 'undefined'; -} - -function installPlugin(type, plugin) { - if (type === 'expressions') { - setExpressionsPlugin(plugin); - } -} - -function getFactory(name) { - switch (name) { - case 'propertyFactory': - return PropertyFactory; - case 'shapePropertyFactory': - return ShapePropertyFactory; - case 'matrix': - return Matrix; - default: - return null; - } -} - -lottie.play = animationManager.play; -lottie.pause = animationManager.pause; -lottie.setLocationHref = setLocation; -lottie.togglePause = animationManager.togglePause; -lottie.setSpeed = animationManager.setSpeed; -lottie.setDirection = animationManager.setDirection; -lottie.stop = animationManager.stop; -lottie.searchAnimations = searchAnimations; -lottie.registerAnimation = animationManager.registerAnimation; -lottie.loadAnimation = loadAnimation; -lottie.setSubframeRendering = setSubframeRendering; -lottie.resize = animationManager.resize; -// lottie.start = start; -lottie.goToAndStop = animationManager.goToAndStop; -lottie.destroy = animationManager.destroy; -lottie.setQuality = setQuality; -lottie.inBrowser = inBrowser; -lottie.installPlugin = installPlugin; -lottie.freeze = animationManager.freeze; -lottie.unfreeze = animationManager.unfreeze; -lottie.setVolume = animationManager.setVolume; -lottie.mute = animationManager.mute; -lottie.unmute = animationManager.unmute; -lottie.getRegisteredAnimations = animationManager.getRegisteredAnimations; -lottie.useWebWorker = setWebWorker; -lottie.setIDPrefix = setPrefix; -lottie.__getFactory = getFactory; -lottie.version = '[[BM_VERSION]]'; - -function checkReady() { - if (document.readyState === 'complete') { - clearInterval(readyStateCheckInterval); - searchAnimations(); - } -} - -function getQueryVariable(variable) { - var vars = queryString.split('&'); - for (var i = 0; i < vars.length; i += 1) { - var pair = vars[i].split('='); - if (decodeURIComponent(pair[0]) == variable) { // eslint-disable-line eqeqeq - return decodeURIComponent(pair[1]); - } - } - return null; -} -var queryString = ''; -if (standalone) { - var scripts = document.getElementsByTagName('script'); - var index = scripts.length - 1; - var myScript = scripts[index] || { - src: '', - }; - queryString = myScript.src ? myScript.src.replace(/^[^\?]+\??/, '') : ''; // eslint-disable-line no-useless-escape - renderer = getQueryVariable('renderer'); -} -var readyStateCheckInterval = setInterval(checkReady, 100); - -// this adds bodymovin to the window object for backwards compatibility -try { - if (!(typeof exports === 'object' && typeof module !== 'undefined') - && !(typeof define === 'function' && define.amd) // eslint-disable-line no-undef - ) { - window.bodymovin = lottie; - } -} catch (err) { - // -} - -const ShapeModifiers = (function () { - var ob = {}; - var modifiers = {}; - ob.registerModifier = registerModifier; - ob.getModifier = getModifier; - - function registerModifier(nm, factory) { - if (!modifiers[nm]) { - modifiers[nm] = factory; - } - } - - function getModifier(nm, elem, data) { - return new modifiers[nm](elem, data); - } - - return ob; -}()); - -function ShapeModifier() {} -ShapeModifier.prototype.initModifierProperties = function () {}; -ShapeModifier.prototype.addShapeToModifier = function () {}; -ShapeModifier.prototype.addShape = function (data) { - if (!this.closed) { - // Adding shape to dynamic properties. It covers the case where a shape has no effects applied, to reset it's _mdf state on every tick. - data.sh.container.addDynamicProperty(data.sh); - var shapeData = { shape: data.sh, data: data, localShapeCollection: shapeCollectionPool.newShapeCollection() }; - this.shapes.push(shapeData); - this.addShapeToModifier(shapeData); - if (this._isAnimated) { - data.setAsAnimated(); - } - } -}; -ShapeModifier.prototype.init = function (elem, data) { - this.shapes = []; - this.elem = elem; - this.initDynamicPropertyContainer(elem); - this.initModifierProperties(elem, data); - this.frameId = initialDefaultFrame; - this.closed = false; - this.k = false; - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.getValue(true); - } -}; -ShapeModifier.prototype.processKeys = function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); -}; - -extendPrototype([DynamicPropertyContainer], ShapeModifier); - -function TrimModifier() { -} -extendPrototype([ShapeModifier], TrimModifier); -TrimModifier.prototype.initModifierProperties = function (elem, data) { - this.s = PropertyFactory.getProp(elem, data.s, 0, 0.01, this); - this.e = PropertyFactory.getProp(elem, data.e, 0, 0.01, this); - this.o = PropertyFactory.getProp(elem, data.o, 0, 0, this); - this.sValue = 0; - this.eValue = 0; - this.getValue = this.processKeys; - this.m = data.m; - this._isAnimated = !!this.s.effectsSequence.length || !!this.e.effectsSequence.length || !!this.o.effectsSequence.length; -}; - -TrimModifier.prototype.addShapeToModifier = function (shapeData) { - shapeData.pathsData = []; -}; - -TrimModifier.prototype.calculateShapeEdges = function (s, e, shapeLength, addedLength, totalModifierLength) { - var segments = []; - if (e <= 1) { - segments.push({ - s: s, - e: e, - }); - } else if (s >= 1) { - segments.push({ - s: s - 1, - e: e - 1, - }); - } else { - segments.push({ - s: s, - e: 1, - }); - segments.push({ - s: 0, - e: e - 1, - }); - } - var shapeSegments = []; - var i; - var len = segments.length; - var segmentOb; - for (i = 0; i < len; i += 1) { - segmentOb = segments[i]; - if (!(segmentOb.e * totalModifierLength < addedLength || segmentOb.s * totalModifierLength > addedLength + shapeLength)) { - var shapeS; - var shapeE; - if (segmentOb.s * totalModifierLength <= addedLength) { - shapeS = 0; - } else { - shapeS = (segmentOb.s * totalModifierLength - addedLength) / shapeLength; - } - if (segmentOb.e * totalModifierLength >= addedLength + shapeLength) { - shapeE = 1; - } else { - shapeE = ((segmentOb.e * totalModifierLength - addedLength) / shapeLength); + if (this.onError) { + this.onError.call(this, error); } - shapeSegments.push([shapeS, shapeE]); - } - } - if (!shapeSegments.length) { - shapeSegments.push([0, 0]); - } - return shapeSegments; -}; - -TrimModifier.prototype.releasePathsData = function (pathsData) { - var i; - var len = pathsData.length; - for (i = 0; i < len; i += 1) { - segmentsLengthPool.release(pathsData[i]); - } - pathsData.length = 0; - return pathsData; -}; - -TrimModifier.prototype.processShapes = function (_isFirstFrame) { - var s; - var e; - if (this._mdf || _isFirstFrame) { - var o = (this.o.v % 360) / 360; - if (o < 0) { - o += 1; - } - if (this.s.v > 1) { - s = 1 + o; - } else if (this.s.v < 0) { - s = 0 + o; - } else { - s = this.s.v + o; - } - if (this.e.v > 1) { - e = 1 + o; - } else if (this.e.v < 0) { - e = 0 + o; - } else { - e = this.e.v + o; - } + }; - if (s > e) { - var _s = s; - s = e; - e = _s; - } - s = Math.round(s * 10000) * 0.0001; - e = Math.round(e * 10000) * 0.0001; - this.sValue = s; - this.eValue = e; - } else { - s = this.sValue; - e = this.eValue; - } - var shapePaths; - var i; - var len = this.shapes.length; - var j; - var jLen; - var pathsData; - var pathData; - var totalShapeLength; - var totalModifierLength = 0; - - if (e === s) { - for (i = 0; i < len; i += 1) { - this.shapes[i].localShapeCollection.releaseShapes(); - this.shapes[i].shape._mdf = true; - this.shapes[i].shape.paths = this.shapes[i].localShapeCollection; - if (this._mdf) { - this.shapes[i].pathsData.length = 0; - } - } - } else if (!((e === 1 && s === 0) || (e === 0 && s === 1))) { - var segments = []; - var shapeData; - var localShapeCollection; - for (i = 0; i < len; i += 1) { - shapeData = this.shapes[i]; - // if shape hasn't changed and trim properties haven't changed, cached previous path can be used - if (!shapeData.shape._mdf && !this._mdf && !_isFirstFrame && this.m !== 2) { - shapeData.shape.paths = shapeData.localShapeCollection; - } else { - shapePaths = shapeData.shape.paths; - jLen = shapePaths._length; - totalShapeLength = 0; - if (!shapeData.shape._mdf && shapeData.pathsData.length) { - totalShapeLength = shapeData.totalShapeLength; - } else { - pathsData = this.releasePathsData(shapeData.pathsData); - for (j = 0; j < jLen; j += 1) { - pathData = bez.getSegmentsLength(shapePaths.shapes[j]); - pathsData.push(pathData); - totalShapeLength += pathData.totalLength; + const animationManager = (function () { + var moduleOb = {}; + var registeredAnimations = []; + var initTime = 0; + var len = 0; + var playingAnimationsNum = 0; + var _stopped = true; + var _isFrozen = false; + + function removeElement(ev) { + var i = 0; + var animItem = ev.target; + while (i < len) { + if (registeredAnimations[i].animation === animItem) { + registeredAnimations.splice(i, 1); + i -= 1; + len -= 1; + if (!animItem.isPaused) { + subtractPlayingCount(); + } } - shapeData.totalShapeLength = totalShapeLength; - shapeData.pathsData = pathsData; + i += 1; } - - totalModifierLength += totalShapeLength; - shapeData.shape._mdf = true; } - } - var shapeS = s; - var shapeE = e; - var addedLength = 0; - var edges; - for (i = len - 1; i >= 0; i -= 1) { - shapeData = this.shapes[i]; - if (shapeData.shape._mdf) { - localShapeCollection = shapeData.localShapeCollection; - localShapeCollection.releaseShapes(); - // if m === 2 means paths are trimmed individually so edges need to be found for this specific shape relative to whoel group - if (this.m === 2 && len > 1) { - edges = this.calculateShapeEdges(s, e, shapeData.totalShapeLength, addedLength, totalModifierLength); - addedLength += shapeData.totalShapeLength; - } else { - edges = [[shapeS, shapeE]]; + + function registerAnimation(element, animationData) { + if (!element) { + return null; } - jLen = edges.length; - for (j = 0; j < jLen; j += 1) { - shapeS = edges[j][0]; - shapeE = edges[j][1]; - segments.length = 0; - if (shapeE <= 1) { - segments.push({ - s: shapeData.totalShapeLength * shapeS, - e: shapeData.totalShapeLength * shapeE, - }); - } else if (shapeS >= 1) { - segments.push({ - s: shapeData.totalShapeLength * (shapeS - 1), - e: shapeData.totalShapeLength * (shapeE - 1), - }); - } else { - segments.push({ - s: shapeData.totalShapeLength * shapeS, - e: shapeData.totalShapeLength, - }); - segments.push({ - s: 0, - e: shapeData.totalShapeLength * (shapeE - 1), - }); - } - var newShapesData = this.addShapes(shapeData, segments[0]); - if (segments[0].s !== segments[0].e) { - if (segments.length > 1) { - var lastShapeInCollection = shapeData.shape.paths.shapes[shapeData.shape.paths._length - 1]; - if (lastShapeInCollection.c) { - var lastShape = newShapesData.pop(); - this.addPaths(newShapesData, localShapeCollection); - newShapesData = this.addShapes(shapeData, segments[1], lastShape); - } else { - this.addPaths(newShapesData, localShapeCollection); - newShapesData = this.addShapes(shapeData, segments[1]); - } - } - this.addPaths(newShapesData, localShapeCollection); + var i = 0; + while (i < len) { + if (registeredAnimations[i].elem === element && registeredAnimations[i].elem !== null) { + return registeredAnimations[i].animation; } + i += 1; } - shapeData.shape.paths = localShapeCollection; + var animItem = new AnimationItem(); + setupAnimation(animItem, element); + animItem.setData(element, animationData); + return animItem; } - } - } else if (this._mdf) { - for (i = 0; i < len; i += 1) { - // Releasign Trim Cached paths data when no trim applied in case shapes are modified inbetween. - // Don't remove this even if it's losing cached info. - this.shapes[i].pathsData.length = 0; - this.shapes[i].shape._mdf = true; - } - } -}; - -TrimModifier.prototype.addPaths = function (newPaths, localShapeCollection) { - var i; - var len = newPaths.length; - for (i = 0; i < len; i += 1) { - localShapeCollection.addShape(newPaths[i]); - } -}; - -TrimModifier.prototype.addSegment = function (pt1, pt2, pt3, pt4, shapePath, pos, newShape) { - shapePath.setXYAt(pt2[0], pt2[1], 'o', pos); - shapePath.setXYAt(pt3[0], pt3[1], 'i', pos + 1); - if (newShape) { - shapePath.setXYAt(pt1[0], pt1[1], 'v', pos); - } - shapePath.setXYAt(pt4[0], pt4[1], 'v', pos + 1); -}; - -TrimModifier.prototype.addSegmentFromArray = function (points, shapePath, pos, newShape) { - shapePath.setXYAt(points[1], points[5], 'o', pos); - shapePath.setXYAt(points[2], points[6], 'i', pos + 1); - if (newShape) { - shapePath.setXYAt(points[0], points[4], 'v', pos); - } - shapePath.setXYAt(points[3], points[7], 'v', pos + 1); -}; - -TrimModifier.prototype.addShapes = function (shapeData, shapeSegment, shapePath) { - var pathsData = shapeData.pathsData; - var shapePaths = shapeData.shape.paths.shapes; - var i; - var len = shapeData.shape.paths._length; - var j; - var jLen; - var addedLength = 0; - var currentLengthData; - var segmentCount; - var lengths; - var segment; - var shapes = []; - var initPos; - var newShape = true; - if (!shapePath) { - shapePath = shapePool.newElement(); - segmentCount = 0; - initPos = 0; - } else { - segmentCount = shapePath._length; - initPos = shapePath._length; - } - shapes.push(shapePath); - for (i = 0; i < len; i += 1) { - lengths = pathsData[i].lengths; - shapePath.c = shapePaths[i].c; - jLen = shapePaths[i].c ? lengths.length : lengths.length + 1; - for (j = 1; j < jLen; j += 1) { - currentLengthData = lengths[j - 1]; - if (addedLength + currentLengthData.addedLength < shapeSegment.s) { - addedLength += currentLengthData.addedLength; - shapePath.c = false; - } else if (addedLength > shapeSegment.e) { - shapePath.c = false; - break; - } else { - if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + currentLengthData.addedLength) { - this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[j], shapePaths[i].v[j], shapePath, segmentCount, newShape); - newShape = false; - } else { - segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[j], shapePaths[i].o[j - 1], shapePaths[i].i[j], (shapeSegment.s - addedLength) / currentLengthData.addedLength, (shapeSegment.e - addedLength) / currentLengthData.addedLength, lengths[j - 1]); - this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); - // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); - newShape = false; - shapePath.c = false; + + function getRegisteredAnimations() { + var i; + var lenAnims = registeredAnimations.length; + var animations = []; + for (i = 0; i < lenAnims; i += 1) { + animations.push(registeredAnimations[i].animation); } - addedLength += currentLengthData.addedLength; - segmentCount += 1; + return animations; } - } - if (shapePaths[i].c && lengths.length) { - currentLengthData = lengths[j - 1]; - if (addedLength <= shapeSegment.e) { - var segmentLength = lengths[j - 1].addedLength; - if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + segmentLength) { - this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[0], shapePaths[i].v[0], shapePath, segmentCount, newShape); - newShape = false; - } else { - segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[0], shapePaths[i].o[j - 1], shapePaths[i].i[0], (shapeSegment.s - addedLength) / segmentLength, (shapeSegment.e - addedLength) / segmentLength, lengths[j - 1]); - this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); - // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); - newShape = false; - shapePath.c = false; - } - } else { - shapePath.c = false; + + function addPlayingCount() { + playingAnimationsNum += 1; + activate(); } - addedLength += currentLengthData.addedLength; - segmentCount += 1; - } - if (shapePath._length) { - shapePath.setXYAt(shapePath.v[initPos][0], shapePath.v[initPos][1], 'i', initPos); - shapePath.setXYAt(shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1], 'o', shapePath._length - 1); - } - if (addedLength > shapeSegment.e) { - break; - } - if (i < len - 1) { - shapePath = shapePool.newElement(); - newShape = true; - shapes.push(shapePath); - segmentCount = 0; - } - } - return shapes; -}; - -function PuckerAndBloatModifier() {} -extendPrototype([ShapeModifier], PuckerAndBloatModifier); -PuckerAndBloatModifier.prototype.initModifierProperties = function (elem, data) { - this.getValue = this.processKeys; - this.amount = PropertyFactory.getProp(elem, data.a, 0, null, this); - this._isAnimated = !!this.amount.effectsSequence.length; -}; - -PuckerAndBloatModifier.prototype.processPath = function (path, amount) { - var percent = amount / 100; - var centerPoint = [0, 0]; - var pathLength = path._length; - var i = 0; - for (i = 0; i < pathLength; i += 1) { - centerPoint[0] += path.v[i][0]; - centerPoint[1] += path.v[i][1]; - } - centerPoint[0] /= pathLength; - centerPoint[1] /= pathLength; - var clonedPath = shapePool.newElement(); - clonedPath.c = path.c; - var vX; - var vY; - var oX; - var oY; - var iX; - var iY; - for (i = 0; i < pathLength; i += 1) { - vX = path.v[i][0] + (centerPoint[0] - path.v[i][0]) * percent; - vY = path.v[i][1] + (centerPoint[1] - path.v[i][1]) * percent; - oX = path.o[i][0] + (centerPoint[0] - path.o[i][0]) * -percent; - oY = path.o[i][1] + (centerPoint[1] - path.o[i][1]) * -percent; - iX = path.i[i][0] + (centerPoint[0] - path.i[i][0]) * -percent; - iY = path.i[i][1] + (centerPoint[1] - path.i[i][1]) * -percent; - clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, i); - } - return clonedPath; -}; - -PuckerAndBloatModifier.prototype.processShapes = function (_isFirstFrame) { - var shapePaths; - var i; - var len = this.shapes.length; - var j; - var jLen; - var amount = this.amount.v; - - if (amount !== 0) { - var shapeData; - var localShapeCollection; - for (i = 0; i < len; i += 1) { - shapeData = this.shapes[i]; - localShapeCollection = shapeData.localShapeCollection; - if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { - localShapeCollection.releaseShapes(); - shapeData.shape._mdf = true; - shapePaths = shapeData.shape.paths.shapes; - jLen = shapeData.shape.paths._length; - for (j = 0; j < jLen; j += 1) { - localShapeCollection.addShape(this.processPath(shapePaths[j], amount)); - } + + function subtractPlayingCount() { + playingAnimationsNum -= 1; } - shapeData.shape.paths = shapeData.localShapeCollection; - } - } - if (!this.dynamicProperties.length) { - this._mdf = false; - } -}; - -const TransformPropertyFactory = (function () { - var defaultVector = [0, 0]; - - function applyToMatrix(mat) { - var _mdf = this._mdf; - this.iterateDynamicProperties(); - this._mdf = this._mdf || _mdf; - if (this.a) { - mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); - } - if (this.s) { - mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]); - } - if (this.sk) { - mat.skewFromAxis(-this.sk.v, this.sa.v); - } - if (this.r) { - mat.rotate(-this.r.v); - } else { - mat.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) - .rotateY(this.or.v[1]) - .rotateX(this.or.v[0]); - } - if (this.data.p.s) { - if (this.data.p.z) { - mat.translate(this.px.v, this.py.v, -this.pz.v); - } else { - mat.translate(this.px.v, this.py.v, 0); + + function setupAnimation(animItem, element) { + animItem.addEventListener('destroy', removeElement); + animItem.addEventListener('_active', addPlayingCount); + animItem.addEventListener('_idle', subtractPlayingCount); + registeredAnimations.push({ elem: element, animation: animItem }); + len += 1; } - } else { - mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); - } - } - function processKeys(forceRender) { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - if (this._isDirty) { - this.precalculateMatrix(); - this._isDirty = false; - } - this.iterateDynamicProperties(); - - if (this._mdf || forceRender) { - var frameRate; - this.v.cloneFromProps(this.pre.props); - if (this.appliedTransformations < 1) { - this.v.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); - } - if (this.appliedTransformations < 2) { - this.v.scale(this.s.v[0], this.s.v[1], this.s.v[2]); - } - if (this.sk && this.appliedTransformations < 3) { - this.v.skewFromAxis(-this.sk.v, this.sa.v); - } - if (this.r && this.appliedTransformations < 4) { - this.v.rotate(-this.r.v); - } else if (!this.r && this.appliedTransformations < 4) { - this.v.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) - .rotateY(this.or.v[1]) - .rotateX(this.or.v[0]); - } - if (this.autoOriented) { - var v1; - var v2; - frameRate = this.elem.globalData.frameRate; - if (this.p && this.p.keyframes && this.p.getValueAtTime) { - if (this.p._caching.lastFrame + this.p.offsetTime <= this.p.keyframes[0].t) { - v1 = this.p.getValueAtTime((this.p.keyframes[0].t + 0.01) / frameRate, 0); - v2 = this.p.getValueAtTime(this.p.keyframes[0].t / frameRate, 0); - } else if (this.p._caching.lastFrame + this.p.offsetTime >= this.p.keyframes[this.p.keyframes.length - 1].t) { - v1 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t / frameRate), 0); - v2 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t - 0.05) / frameRate, 0); - } else { - v1 = this.p.pv; - v2 = this.p.getValueAtTime((this.p._caching.lastFrame + this.p.offsetTime - 0.01) / frameRate, this.p.offsetTime); - } - } else if (this.px && this.px.keyframes && this.py.keyframes && this.px.getValueAtTime && this.py.getValueAtTime) { - v1 = []; - v2 = []; - var px = this.px; - var py = this.py; - if (px._caching.lastFrame + px.offsetTime <= px.keyframes[0].t) { - v1[0] = px.getValueAtTime((px.keyframes[0].t + 0.01) / frameRate, 0); - v1[1] = py.getValueAtTime((py.keyframes[0].t + 0.01) / frameRate, 0); - v2[0] = px.getValueAtTime((px.keyframes[0].t) / frameRate, 0); - v2[1] = py.getValueAtTime((py.keyframes[0].t) / frameRate, 0); - } else if (px._caching.lastFrame + px.offsetTime >= px.keyframes[px.keyframes.length - 1].t) { - v1[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t / frameRate), 0); - v1[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t / frameRate), 0); - v2[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t - 0.01) / frameRate, 0); - v2[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t - 0.01) / frameRate, 0); - } else { - v1 = [px.pv, py.pv]; - v2[0] = px.getValueAtTime((px._caching.lastFrame + px.offsetTime - 0.01) / frameRate, px.offsetTime); - v2[1] = py.getValueAtTime((py._caching.lastFrame + py.offsetTime - 0.01) / frameRate, py.offsetTime); - } - } else { - v2 = defaultVector; - v1 = v2; - } - this.v.rotate(-Math.atan2(v1[1] - v2[1], v1[0] - v2[0])); + function loadAnimation(params) { + var animItem = new AnimationItem(); + setupAnimation(animItem, null); + animItem.setParams(params); + return animItem; } - if (this.data.p && this.data.p.s) { - if (this.data.p.z) { - this.v.translate(this.px.v, this.py.v, -this.pz.v); - } else { - this.v.translate(this.px.v, this.py.v, 0); + + function setSpeed(val, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.setSpeed(val, animation); } - } else { - this.v.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); - } - } - this.frameId = this.elem.globalData.frameId; - } - - function precalculateMatrix() { - if (!this.a.k) { - this.pre.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); - this.appliedTransformations = 1; - } else { - return; - } - if (!this.s.effectsSequence.length) { - this.pre.scale(this.s.v[0], this.s.v[1], this.s.v[2]); - this.appliedTransformations = 2; - } else { - return; - } - if (this.sk) { - if (!this.sk.effectsSequence.length && !this.sa.effectsSequence.length) { - this.pre.skewFromAxis(-this.sk.v, this.sa.v); - this.appliedTransformations = 3; - } else { - return; } - } - if (this.r) { - if (!this.r.effectsSequence.length) { - this.pre.rotate(-this.r.v); - this.appliedTransformations = 4; - } - } else if (!this.rz.effectsSequence.length && !this.ry.effectsSequence.length && !this.rx.effectsSequence.length && !this.or.effectsSequence.length) { - this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) - .rotateY(this.or.v[1]) - .rotateX(this.or.v[0]); - this.appliedTransformations = 4; - } - } - function autoOrient() { - // - // var prevP = this.getValueAtTime(); - } - - function addDynamicProperty(prop) { - this._addDynamicProperty(prop); - this.elem.addDynamicProperty(prop); - this._isDirty = true; - } - - function TransformProperty(elem, data, container) { - this.elem = elem; - this.frameId = -1; - this.propType = 'transform'; - this.data = data; - this.v = new Matrix(); - // Precalculated matrix with non animated properties - this.pre = new Matrix(); - this.appliedTransformations = 0; - this.initDynamicPropertyContainer(container || elem); - if (data.p && data.p.s) { - this.px = PropertyFactory.getProp(elem, data.p.x, 0, 0, this); - this.py = PropertyFactory.getProp(elem, data.p.y, 0, 0, this); - if (data.p.z) { - this.pz = PropertyFactory.getProp(elem, data.p.z, 0, 0, this); - } - } else { - this.p = PropertyFactory.getProp(elem, data.p || { k: [0, 0, 0] }, 1, 0, this); - } - if (data.rx) { - this.rx = PropertyFactory.getProp(elem, data.rx, 0, degToRads, this); - this.ry = PropertyFactory.getProp(elem, data.ry, 0, degToRads, this); - this.rz = PropertyFactory.getProp(elem, data.rz, 0, degToRads, this); - if (data.or.k[0].ti) { + function setDirection(val, animation) { var i; - var len = data.or.k.length; for (i = 0; i < len; i += 1) { - data.or.k[i].to = null; - data.or.k[i].ti = null; + registeredAnimations[i].animation.setDirection(val, animation); } } - this.or = PropertyFactory.getProp(elem, data.or, 1, degToRads, this); - // sh Indicates it needs to be capped between -180 and 180 - this.or.sh = true; - } else { - this.r = PropertyFactory.getProp(elem, data.r || { k: 0 }, 0, degToRads, this); - } - if (data.sk) { - this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this); - this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this); - } - this.a = PropertyFactory.getProp(elem, data.a || { k: [0, 0, 0] }, 1, 0, this); - this.s = PropertyFactory.getProp(elem, data.s || { k: [100, 100, 100] }, 1, 0.01, this); - // Opacity is not part of the transform properties, that's why it won't use this.dynamicProperties. That way transforms won't get updated if opacity changes. - if (data.o) { - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, elem); - } else { - this.o = { _mdf: false, v: 1 }; - } - this._isDirty = true; - if (!this.dynamicProperties.length) { - this.getValue(true); - } - } - - TransformProperty.prototype = { - applyToMatrix: applyToMatrix, - getValue: processKeys, - precalculateMatrix: precalculateMatrix, - autoOrient: autoOrient, - }; - - extendPrototype([DynamicPropertyContainer], TransformProperty); - TransformProperty.prototype.addDynamicProperty = addDynamicProperty; - TransformProperty.prototype._addDynamicProperty = DynamicPropertyContainer.prototype.addDynamicProperty; - - function getTransformProperty(elem, data, container) { - return new TransformProperty(elem, data, container); - } - - return { - getTransformProperty: getTransformProperty, - }; -}()); - -function RepeaterModifier() {} -extendPrototype([ShapeModifier], RepeaterModifier); - -RepeaterModifier.prototype.initModifierProperties = function (elem, data) { - this.getValue = this.processKeys; - this.c = PropertyFactory.getProp(elem, data.c, 0, null, this); - this.o = PropertyFactory.getProp(elem, data.o, 0, null, this); - this.tr = TransformPropertyFactory.getTransformProperty(elem, data.tr, this); - this.so = PropertyFactory.getProp(elem, data.tr.so, 0, 0.01, this); - this.eo = PropertyFactory.getProp(elem, data.tr.eo, 0, 0.01, this); - this.data = data; - if (!this.dynamicProperties.length) { - this.getValue(true); - } - this._isAnimated = !!this.dynamicProperties.length; - this.pMatrix = new Matrix(); - this.rMatrix = new Matrix(); - this.sMatrix = new Matrix(); - this.tMatrix = new Matrix(); - this.matrix = new Matrix(); -}; - -RepeaterModifier.prototype.applyTransforms = function (pMatrix, rMatrix, sMatrix, transform, perc, inv) { - var dir = inv ? -1 : 1; - var scaleX = transform.s.v[0] + (1 - transform.s.v[0]) * (1 - perc); - var scaleY = transform.s.v[1] + (1 - transform.s.v[1]) * (1 - perc); - pMatrix.translate(transform.p.v[0] * dir * perc, transform.p.v[1] * dir * perc, transform.p.v[2]); - rMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); - rMatrix.rotate(-transform.r.v * dir * perc); - rMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); - sMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); - sMatrix.scale(inv ? 1 / scaleX : scaleX, inv ? 1 / scaleY : scaleY); - sMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); -}; - -RepeaterModifier.prototype.init = function (elem, arr, pos, elemsData) { - this.elem = elem; - this.arr = arr; - this.pos = pos; - this.elemsData = elemsData; - this._currentCopies = 0; - this._elements = []; - this._groups = []; - this.frameId = -1; - this.initDynamicPropertyContainer(elem); - this.initModifierProperties(elem, arr[pos]); - while (pos > 0) { - pos -= 1; - // this._elements.unshift(arr.splice(pos,1)[0]); - this._elements.unshift(arr[pos]); - } - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.getValue(true); - } -}; - -RepeaterModifier.prototype.resetElements = function (elements) { - var i; - var len = elements.length; - for (i = 0; i < len; i += 1) { - elements[i]._processed = false; - if (elements[i].ty === 'gr') { - this.resetElements(elements[i].it); - } - } -}; - -RepeaterModifier.prototype.cloneElements = function (elements) { - var newElements = JSON.parse(JSON.stringify(elements)); - this.resetElements(newElements); - return newElements; -}; - -RepeaterModifier.prototype.changeGroupRender = function (elements, renderFlag) { - var i; - var len = elements.length; - for (i = 0; i < len; i += 1) { - elements[i]._render = renderFlag; - if (elements[i].ty === 'gr') { - this.changeGroupRender(elements[i].it, renderFlag); - } - } -}; - -RepeaterModifier.prototype.processShapes = function (_isFirstFrame) { - var items; - var itemsTransform; - var i; - var dir; - var cont; - var hasReloaded = false; - if (this._mdf || _isFirstFrame) { - var copies = Math.ceil(this.c.v); - if (this._groups.length < copies) { - while (this._groups.length < copies) { - var group = { - it: this.cloneElements(this._elements), - ty: 'gr', - }; - group.it.push({ - a: { a: 0, ix: 1, k: [0, 0] }, nm: 'Transform', o: { a: 0, ix: 7, k: 100 }, p: { a: 0, ix: 2, k: [0, 0] }, r: { a: 1, ix: 6, k: [{ s: 0, e: 0, t: 0 }, { s: 0, e: 0, t: 1 }] }, s: { a: 0, ix: 3, k: [100, 100] }, sa: { a: 0, ix: 5, k: 0 }, sk: { a: 0, ix: 4, k: 0 }, ty: 'tr', - }); - this.arr.splice(0, 0, group); - this._groups.splice(0, 0, group); - this._currentCopies += 1; + function play(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.play(animation); + } } - this.elem.reloadShapes(); - hasReloaded = true; - } - cont = 0; - var renderFlag; - for (i = 0; i <= this._groups.length - 1; i += 1) { - renderFlag = cont < copies; - this._groups[i]._render = renderFlag; - this.changeGroupRender(this._groups[i].it, renderFlag); - if (!renderFlag) { - var elems = this.elemsData[i].it; - var transformData = elems[elems.length - 1]; - if (transformData.transform.op.v !== 0) { - transformData.transform.op._mdf = true; - transformData.transform.op.v = 0; + function resume(nowTime) { + var elapsedTime = nowTime - initTime; + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.advanceTime(elapsedTime); + } + initTime = nowTime; + if (playingAnimationsNum && !_isFrozen) { + window.requestAnimationFrame(resume); } else { - transformData.transform.op._mdf = false; + _stopped = true; } } - cont += 1; - } - this._currentCopies = copies; - /// / - - var offset = this.o.v; - var offsetModulo = offset % 1; - var roundOffset = offset > 0 ? Math.floor(offset) : Math.ceil(offset); - var pProps = this.pMatrix.props; - var rProps = this.rMatrix.props; - var sProps = this.sMatrix.props; - this.pMatrix.reset(); - this.rMatrix.reset(); - this.sMatrix.reset(); - this.tMatrix.reset(); - this.matrix.reset(); - var iteration = 0; - - if (offset > 0) { - while (iteration < roundOffset) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); - iteration += 1; - } - if (offsetModulo) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, offsetModulo, false); - iteration += offsetModulo; - } - } else if (offset < 0) { - while (iteration > roundOffset) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, true); - iteration -= 1; - } - if (offsetModulo) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, -offsetModulo, true); - iteration -= offsetModulo; + function first(nowTime) { + initTime = nowTime; + window.requestAnimationFrame(resume); } - } - i = this.data.m === 1 ? 0 : this._currentCopies - 1; - dir = this.data.m === 1 ? 1 : -1; - cont = this._currentCopies; - var j; - var jLen; - while (cont) { - items = this.elemsData[i].it; - itemsTransform = items[items.length - 1].transform.mProps.v.props; - jLen = itemsTransform.length; - items[items.length - 1].transform.mProps._mdf = true; - items[items.length - 1].transform.op._mdf = true; - items[items.length - 1].transform.op.v = this._currentCopies === 1 - ? this.so.v - : this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1)); - - if (iteration !== 0) { - if ((i !== 0 && dir === 1) || (i !== this._currentCopies - 1 && dir === -1)) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); - } - this.matrix.transform(rProps[0], rProps[1], rProps[2], rProps[3], rProps[4], rProps[5], rProps[6], rProps[7], rProps[8], rProps[9], rProps[10], rProps[11], rProps[12], rProps[13], rProps[14], rProps[15]); - this.matrix.transform(sProps[0], sProps[1], sProps[2], sProps[3], sProps[4], sProps[5], sProps[6], sProps[7], sProps[8], sProps[9], sProps[10], sProps[11], sProps[12], sProps[13], sProps[14], sProps[15]); - this.matrix.transform(pProps[0], pProps[1], pProps[2], pProps[3], pProps[4], pProps[5], pProps[6], pProps[7], pProps[8], pProps[9], pProps[10], pProps[11], pProps[12], pProps[13], pProps[14], pProps[15]); - for (j = 0; j < jLen; j += 1) { - itemsTransform[j] = this.matrix.props[j]; - } - this.matrix.reset(); - } else { - this.matrix.reset(); - for (j = 0; j < jLen; j += 1) { - itemsTransform[j] = this.matrix.props[j]; + function pause(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.pause(animation); } } - iteration += 1; - cont -= 1; - i += dir; - } - } else { - cont = this._currentCopies; - i = 0; - dir = 1; - while (cont) { - items = this.elemsData[i].it; - itemsTransform = items[items.length - 1].transform.mProps.v.props; - items[items.length - 1].transform.mProps._mdf = false; - items[items.length - 1].transform.op._mdf = false; - cont -= 1; - i += dir; - } - } - return hasReloaded; -}; - -RepeaterModifier.prototype.addShape = function () {}; - -function RoundCornersModifier() {} -extendPrototype([ShapeModifier], RoundCornersModifier); -RoundCornersModifier.prototype.initModifierProperties = function (elem, data) { - this.getValue = this.processKeys; - this.rd = PropertyFactory.getProp(elem, data.r, 0, null, this); - this._isAnimated = !!this.rd.effectsSequence.length; -}; - -RoundCornersModifier.prototype.processPath = function (path, round) { - var clonedPath = shapePool.newElement(); - clonedPath.c = path.c; - var i; - var len = path._length; - var currentV; - var currentI; - var currentO; - var closerV; - var distance; - var newPosPerc; - var index = 0; - var vX; - var vY; - var oX; - var oY; - var iX; - var iY; - for (i = 0; i < len; i += 1) { - currentV = path.v[i]; - currentO = path.o[i]; - currentI = path.i[i]; - if (currentV[0] === currentO[0] && currentV[1] === currentO[1] && currentV[0] === currentI[0] && currentV[1] === currentI[1]) { - if ((i === 0 || i === len - 1) && !path.c) { - clonedPath.setTripleAt(currentV[0], currentV[1], currentO[0], currentO[1], currentI[0], currentI[1], index); - /* clonedPath.v[index] = currentV; - clonedPath.o[index] = currentO; - clonedPath.i[index] = currentI; */ - index += 1; - } else { - if (i === 0) { - closerV = path.v[len - 1]; - } else { - closerV = path.v[i - 1]; - } - distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); - newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; - iX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; - vX = iX; - iY = currentV[1] - (currentV[1] - closerV[1]) * newPosPerc; - vY = iY; - oX = vX - (vX - currentV[0]) * roundCorner; - oY = vY - (vY - currentV[1]) * roundCorner; - clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); - index += 1; - - if (i === len - 1) { - closerV = path.v[0]; - } else { - closerV = path.v[i + 1]; - } - distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); - newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; - oX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; - vX = oX; - oY = currentV[1] + (closerV[1] - currentV[1]) * newPosPerc; - vY = oY; - iX = vX - (vX - currentV[0]) * roundCorner; - iY = vY - (vY - currentV[1]) * roundCorner; - clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); - index += 1; - } - } else { - clonedPath.setTripleAt(path.v[i][0], path.v[i][1], path.o[i][0], path.o[i][1], path.i[i][0], path.i[i][1], index); - index += 1; - } - } - return clonedPath; -}; - -RoundCornersModifier.prototype.processShapes = function (_isFirstFrame) { - var shapePaths; - var i; - var len = this.shapes.length; - var j; - var jLen; - var rd = this.rd.v; - - if (rd !== 0) { - var shapeData; - var localShapeCollection; - for (i = 0; i < len; i += 1) { - shapeData = this.shapes[i]; - localShapeCollection = shapeData.localShapeCollection; - if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { - localShapeCollection.releaseShapes(); - shapeData.shape._mdf = true; - shapePaths = shapeData.shape.paths.shapes; - jLen = shapeData.shape.paths._length; - for (j = 0; j < jLen; j += 1) { - localShapeCollection.addShape(this.processPath(shapePaths[j], rd)); + + function goToAndStop(value, isFrame, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.goToAndStop(value, isFrame, animation); } } - shapeData.shape.paths = shapeData.localShapeCollection; - } - } - if (!this.dynamicProperties.length) { - this._mdf = false; - } -}; - -function getFontProperties(fontData) { - var styles = fontData.fStyle ? fontData.fStyle.split(' ') : []; - - var fWeight = 'normal'; var - fStyle = 'normal'; - var len = styles.length; - var styleName; - for (var i = 0; i < len; i += 1) { - styleName = styles[i].toLowerCase(); - switch (styleName) { - case 'italic': - fStyle = 'italic'; - break; - case 'bold': - fWeight = '700'; - break; - case 'black': - fWeight = '900'; - break; - case 'medium': - fWeight = '500'; - break; - case 'regular': - case 'normal': - fWeight = '400'; - break; - case 'light': - case 'thin': - fWeight = '200'; - break; - default: - break; - } - } - - return { - style: fStyle, - weight: fontData.fWeight || fWeight, - }; -} - -const FontManager = (function () { - var maxWaitingTime = 5000; - var emptyChar = { - w: 0, - size: 0, - shapes: [], - data: { - shapes: [], - }, - }; - var combinedCharacters = []; - // Hindi characters - combinedCharacters = combinedCharacters.concat([2304, 2305, 2306, 2307, 2362, 2363, 2364, 2364, 2366, - 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, - 2380, 2381, 2382, 2383, 2387, 2388, 2389, 2390, 2391, 2402, 2403]); - - var surrogateModifiers = [ - 'd83cdffb', - 'd83cdffc', - 'd83cdffd', - 'd83cdffe', - 'd83cdfff', - ]; - - var zeroWidthJoiner = [65039, 8205]; - - function trimFontOptions(font) { - var familyArray = font.split(','); - var i; - var len = familyArray.length; - var enabledFamilies = []; - for (i = 0; i < len; i += 1) { - if (familyArray[i] !== 'sans-serif' && familyArray[i] !== 'monospace') { - enabledFamilies.push(familyArray[i]); - } - } - return enabledFamilies.join(','); - } - - function setUpNode(font, family) { - var parentNode = createTag('span'); - // Node is invisible to screen readers. - parentNode.setAttribute('aria-hidden', true); - parentNode.style.fontFamily = family; - var node = createTag('span'); - // Characters that vary significantly among different fonts - node.innerText = 'giItT1WQy@!-/#'; - // Visible - so we can measure it - but not on the screen - parentNode.style.position = 'absolute'; - parentNode.style.left = '-10000px'; - parentNode.style.top = '-10000px'; - // Large font size makes even subtle changes obvious - parentNode.style.fontSize = '300px'; - // Reset any font properties - parentNode.style.fontVariant = 'normal'; - parentNode.style.fontStyle = 'normal'; - parentNode.style.fontWeight = 'normal'; - parentNode.style.letterSpacing = '0'; - parentNode.appendChild(node); - document.body.appendChild(parentNode); - - // Remember width with no applied web font - var width = node.offsetWidth; - node.style.fontFamily = trimFontOptions(font) + ', ' + family; - return { node: node, w: width, parent: parentNode }; - } - - function checkLoadedFonts() { - var i; - var len = this.fonts.length; - var node; - var w; - var loadedCount = len; - for (i = 0; i < len; i += 1) { - if (this.fonts[i].loaded) { - loadedCount -= 1; - } else if (this.fonts[i].fOrigin === 'n' || this.fonts[i].origin === 0) { - this.fonts[i].loaded = true; - } else { - node = this.fonts[i].monoCase.node; - w = this.fonts[i].monoCase.w; - if (node.offsetWidth !== w) { - loadedCount -= 1; - this.fonts[i].loaded = true; - } else { - node = this.fonts[i].sansCase.node; - w = this.fonts[i].sansCase.w; - if (node.offsetWidth !== w) { - loadedCount -= 1; - this.fonts[i].loaded = true; - } - } - if (this.fonts[i].loaded) { - this.fonts[i].sansCase.parent.parentNode.removeChild(this.fonts[i].sansCase.parent); - this.fonts[i].monoCase.parent.parentNode.removeChild(this.fonts[i].monoCase.parent); + + function stop(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.stop(animation); } } - } - if (loadedCount !== 0 && Date.now() - this.initTime < maxWaitingTime) { - setTimeout(this.checkLoadedFontsBinded, 20); - } else { - setTimeout(this.setIsLoadedBinded, 10); - } - } - - function createHelper(fontData, def) { - var engine = (document.body && def) ? 'svg' : 'canvas'; - var helper; - var fontProps = getFontProperties(fontData); - if (engine === 'svg') { - var tHelper = createNS('text'); - tHelper.style.fontSize = '100px'; - // tHelper.style.fontFamily = fontData.fFamily; - tHelper.setAttribute('font-family', fontData.fFamily); - tHelper.setAttribute('font-style', fontProps.style); - tHelper.setAttribute('font-weight', fontProps.weight); - tHelper.textContent = '1'; - if (fontData.fClass) { - tHelper.style.fontFamily = 'inherit'; - tHelper.setAttribute('class', fontData.fClass); - } else { - tHelper.style.fontFamily = fontData.fFamily; - } - def.appendChild(tHelper); - helper = tHelper; - } else { - var tCanvasHelper = new OffscreenCanvas(500, 500).getContext('2d'); - tCanvasHelper.font = fontProps.style + ' ' + fontProps.weight + ' 100px ' + fontData.fFamily; - helper = tCanvasHelper; - } - function measure(text) { - if (engine === 'svg') { - helper.textContent = text; - return helper.getComputedTextLength(); + function togglePause(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.togglePause(animation); + } } - return helper.measureText(text).width; - } - return { - measureText: measure, - }; - } - - function addFonts(fontData, defs) { - if (!fontData) { - this.isLoaded = true; - return; - } - if (this.chars) { - this.isLoaded = true; - this.fonts = fontData.list; - return; - } - if (!document.body) { - this.isLoaded = true; - fontData.list.forEach((data) => { - data.helper = createHelper(data); - data.cache = {}; - }); - this.fonts = fontData.list; - return; - } - var fontArr = fontData.list; - var i; - var len = fontArr.length; - var _pendingFonts = len; - for (i = 0; i < len; i += 1) { - var shouldLoadFont = true; - var loadedSelector; - var j; - fontArr[i].loaded = false; - fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace'); - fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif'); - if (!fontArr[i].fPath) { - fontArr[i].loaded = true; - _pendingFonts -= 1; - } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) { - loadedSelector = document.querySelectorAll('style[f-forigin="p"][f-family="' + fontArr[i].fFamily + '"], style[f-origin="3"][f-family="' + fontArr[i].fFamily + '"]'); - - if (loadedSelector.length > 0) { - shouldLoadFont = false; + function destroy(animation) { + var i; + for (i = (len - 1); i >= 0; i -= 1) { + registeredAnimations[i].animation.destroy(animation); } + } - if (shouldLoadFont) { - var s = createTag('style'); - s.setAttribute('f-forigin', fontArr[i].fOrigin); - s.setAttribute('f-origin', fontArr[i].origin); - s.setAttribute('f-family', fontArr[i].fFamily); - s.type = 'text/css'; - s.innerText = '@font-face {font-family: ' + fontArr[i].fFamily + "; font-style: normal; src: url('" + fontArr[i].fPath + "');}"; - defs.appendChild(s); + function searchAnimations(animationData, standalone, renderer) { + var animElements = [].concat([].slice.call(document.getElementsByClassName('lottie')), + [].slice.call(document.getElementsByClassName('bodymovin'))); + var i; + var lenAnims = animElements.length; + for (i = 0; i < lenAnims; i += 1) { + if (renderer) { + animElements[i].setAttribute('data-bm-type', renderer); + } + registerAnimation(animElements[i], animationData); } - } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) { - loadedSelector = document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'); - - for (j = 0; j < loadedSelector.length; j += 1) { - if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) { - // Font is already loaded - shouldLoadFont = false; + if (standalone && lenAnims === 0) { + if (!renderer) { + renderer = 'svg'; } + var body = document.getElementsByTagName('body')[0]; + body.innerText = ''; + var div = createTag('div'); + div.style.width = '100%'; + div.style.height = '100%'; + div.setAttribute('data-bm-type', renderer); + body.appendChild(div); + registerAnimation(div, animationData); } + } - if (shouldLoadFont) { - var l = createTag('link'); - l.setAttribute('f-forigin', fontArr[i].fOrigin); - l.setAttribute('f-origin', fontArr[i].origin); - l.type = 'text/css'; - l.rel = 'stylesheet'; - l.href = fontArr[i].fPath; - document.body.appendChild(l); + function resize() { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.resize(); } - } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) { - loadedSelector = document.querySelectorAll('script[f-forigin="t"], script[f-origin="2"]'); + } - for (j = 0; j < loadedSelector.length; j += 1) { - if (fontArr[i].fPath === loadedSelector[j].src) { - // Font is already loaded - shouldLoadFont = false; + function activate() { + if (!_isFrozen && playingAnimationsNum) { + if (_stopped) { + window.requestAnimationFrame(first); + _stopped = false; } } + } - if (shouldLoadFont) { - var sc = createTag('link'); - sc.setAttribute('f-forigin', fontArr[i].fOrigin); - sc.setAttribute('f-origin', fontArr[i].origin); - sc.setAttribute('rel', 'stylesheet'); - sc.setAttribute('href', fontArr[i].fPath); - defs.appendChild(sc); - } + function freeze() { + _isFrozen = true; } - fontArr[i].helper = createHelper(fontArr[i], defs); - fontArr[i].cache = {}; - this.fonts.push(fontArr[i]); - } - if (_pendingFonts === 0) { - this.isLoaded = true; - } else { - // On some cases even if the font is loaded, it won't load correctly when measuring text on canvas. - // Adding this timeout seems to fix it - setTimeout(this.checkLoadedFonts.bind(this), 100); - } - } - function addChars(chars) { - if (!chars) { - return; - } - if (!this.chars) { - this.chars = []; - } - var i; - var len = chars.length; - var j; - var jLen = this.chars.length; - var found; - for (i = 0; i < len; i += 1) { - j = 0; - found = false; - while (j < jLen) { - if (this.chars[j].style === chars[i].style && this.chars[j].fFamily === chars[i].fFamily && this.chars[j].ch === chars[i].ch) { - found = true; - } - j += 1; - } - if (!found) { - this.chars.push(chars[i]); - jLen += 1; + function unfreeze() { + _isFrozen = false; + activate(); } - } - } - function getCharData(char, style, font) { - var i = 0; - var len = this.chars.length; - while (i < len) { - if (this.chars[i].ch === char && this.chars[i].style === style && this.chars[i].fFamily === font) { - return this.chars[i]; + function setVolume(val, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.setVolume(val, animation); + } } - i += 1; - } - if (((typeof char === 'string' && char.charCodeAt(0) !== 13) || !char) - && console - && console.warn // eslint-disable-line no-console - && !this._warned - ) { - this._warned = true; - console.warn('Missing character from exported characters list: ', char, style, font); // eslint-disable-line no-console - } - return emptyChar; - } - - function measureText(char, fontName, size) { - var fontData = this.getFontByName(fontName); - var index = char.charCodeAt(0); - if (!fontData.cache[index + 1]) { - var tHelper = fontData.helper; - if (char === ' ') { - var doubleSize = tHelper.measureText('|' + char + '|'); - var singleSize = tHelper.measureText('||'); - fontData.cache[index + 1] = (doubleSize - singleSize) / 100; - } else { - fontData.cache[index + 1] = tHelper.measureText(char) / 100; + + function mute(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.mute(animation); + } } - } - return fontData.cache[index + 1] * size; - } - function getFontByName(name) { - var i = 0; - var len = this.fonts.length; - while (i < len) { - if (this.fonts[i].fName === name) { - return this.fonts[i]; + function unmute(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.unmute(animation); + } + } + + moduleOb.registerAnimation = registerAnimation; + moduleOb.loadAnimation = loadAnimation; + moduleOb.setSpeed = setSpeed; + moduleOb.setDirection = setDirection; + moduleOb.play = play; + moduleOb.pause = pause; + moduleOb.stop = stop; + moduleOb.togglePause = togglePause; + moduleOb.searchAnimations = searchAnimations; + moduleOb.resize = resize; + // moduleOb.start = start; + moduleOb.goToAndStop = goToAndStop; + moduleOb.destroy = destroy; + moduleOb.freeze = freeze; + moduleOb.unfreeze = unfreeze; + moduleOb.setVolume = setVolume; + moduleOb.mute = mute; + moduleOb.unmute = unmute; + moduleOb.getRegisteredAnimations = getRegisteredAnimations; + return moduleOb; + }()); + + /* eslint-disable */ + const BezierFactory = (function () { + /** + * BezierEasing - use bezier curve for transition easing function + * by Gaëtan Renaudeau 2014 - 2015 – MIT License + * + * Credits: is based on Firefox's nsSMILKeySpline.cpp + * Usage: + * var spline = BezierEasing([ 0.25, 0.1, 0.25, 1.0 ]) + * spline.get(x) => returns the easing value | x must be in [0, 1] range + * + */ + + var ob = {}; + ob.getBezierEasing = getBezierEasing; + var beziers = {}; + + function getBezierEasing(a, b, c, d, nm) { + var str = nm || ('bez_' + a + '_' + b + '_' + c + '_' + d).replace(/\./g, 'p'); + if (beziers[str]) { + return beziers[str]; + } + var bezEasing = new BezierEasing([a, b, c, d]); + beziers[str] = bezEasing; + return bezEasing; } - i += 1; - } - return this.fonts[0]; - } - function isModifier(firstCharCode, secondCharCode) { - var sum = firstCharCode.toString(16) + secondCharCode.toString(16); - return surrogateModifiers.indexOf(sum) !== -1; - } + // These values are established by empiricism with tests (tradeoff: performance VS precision) + var NEWTON_ITERATIONS = 4; + var NEWTON_MIN_SLOPE = 0.001; + var SUBDIVISION_PRECISION = 0.0000001; + var SUBDIVISION_MAX_ITERATIONS = 10; - function isZeroWidthJoiner(firstCharCode, secondCharCode) { - if (!secondCharCode) { - return firstCharCode === zeroWidthJoiner[1]; - } - return firstCharCode === zeroWidthJoiner[0] && secondCharCode === zeroWidthJoiner[1]; - } - - function isCombinedCharacter(char) { - return combinedCharacters.indexOf(char) !== -1; - } - - function setIsLoaded() { - this.isLoaded = true; - } - - var Font = function () { - this.fonts = []; - this.chars = null; - this.typekitLoaded = 0; - this.isLoaded = false; - this._warned = false; - this.initTime = Date.now(); - this.setIsLoadedBinded = this.setIsLoaded.bind(this); - this.checkLoadedFontsBinded = this.checkLoadedFonts.bind(this); - }; - Font.isModifier = isModifier; - Font.isZeroWidthJoiner = isZeroWidthJoiner; - Font.isCombinedCharacter = isCombinedCharacter; - - var fontPrototype = { - addChars: addChars, - addFonts: addFonts, - getCharData: getCharData, - getFontByName: getFontByName, - measureText: measureText, - checkLoadedFonts: checkLoadedFonts, - setIsLoaded: setIsLoaded, - }; - - Font.prototype = fontPrototype; - - return Font; -}()); - -function RenderableElement() { - -} - -RenderableElement.prototype = { - initRenderable: function () { - // layer's visibility related to inpoint and outpoint. Rename isVisible to isInRange - this.isInRange = false; - // layer's display state - this.hidden = false; - // If layer's transparency equals 0, it can be hidden - this.isTransparent = false; - // list of animated components - this.renderableComponents = []; - }, - addRenderableComponent: function (component) { - if (this.renderableComponents.indexOf(component) === -1) { - this.renderableComponents.push(component); - } - }, - removeRenderableComponent: function (component) { - if (this.renderableComponents.indexOf(component) !== -1) { - this.renderableComponents.splice(this.renderableComponents.indexOf(component), 1); - } - }, - prepareRenderableFrame: function (num) { - this.checkLayerLimits(num); - }, - checkTransparency: function () { - if (this.finalTransform.mProp.o.v <= 0) { - if (!this.isTransparent && this.globalData.renderConfig.hideOnTransparent) { - this.isTransparent = true; - this.hide(); - } - } else if (this.isTransparent) { - this.isTransparent = false; - this.show(); - } - }, - /** - * @function - * Initializes frame related properties. - * - * @param {number} num - * current frame number in Layer's time - * - */ - checkLayerLimits: function (num) { - if (this.data.ip - this.data.st <= num && this.data.op - this.data.st > num) { - if (this.isInRange !== true) { - this.globalData._mdf = true; - this._mdf = true; - this.isInRange = true; - this.show(); - } - } else if (this.isInRange !== false) { - this.globalData._mdf = true; - this.isInRange = false; - this.hide(); - } - }, - renderRenderable: function () { - var i; - var len = this.renderableComponents.length; - for (i = 0; i < len; i += 1) { - this.renderableComponents[i].renderFrame(this._isFirstFrame); - } - /* this.maskManager.renderFrame(this.finalTransform.mat); - this.renderableEffectsManager.renderFrame(this._isFirstFrame); */ - }, - sourceRectAtTime: function () { - return { - top: 0, - left: 0, - width: 100, - height: 100, - }; - }, - getLayerSize: function () { - if (this.data.ty === 5) { - return { w: this.data.textData.width, h: this.data.textData.height }; - } - return { w: this.data.width, h: this.data.height }; - }, -}; - -const MaskManagerInterface = (function () { - function MaskInterface(mask, data) { - this._mask = mask; - this._data = data; - } - Object.defineProperty(MaskInterface.prototype, 'maskPath', { - get: function () { - if (this._mask.prop.k) { - this._mask.prop.getValue(); - } - return this._mask.prop; - }, - }); - Object.defineProperty(MaskInterface.prototype, 'maskOpacity', { - get: function () { - if (this._mask.op.k) { - this._mask.op.getValue(); - } - return this._mask.op.v * 100; - }, - }); - - var MaskManager = function (maskManager) { - var _masksInterfaces = createSizedArray(maskManager.viewData.length); - var i; - var len = maskManager.viewData.length; - for (i = 0; i < len; i += 1) { - _masksInterfaces[i] = new MaskInterface(maskManager.viewData[i], maskManager.masksProperties[i]); - } + var kSplineTableSize = 11; + var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); - var maskFunction = function (name) { - i = 0; - while (i < len) { - if (maskManager.masksProperties[i].nm === name) { - return _masksInterfaces[i]; - } - i += 1; - } - return null; - }; - return maskFunction; - }; - return MaskManager; -}()); + var float32ArraySupported = typeof Float32Array === 'function'; -const ExpressionPropertyInterface = (function () { - var defaultUnidimensionalValue = { pv: 0, v: 0, mult: 1 }; - var defaultMultidimensionalValue = { pv: [0, 0, 0], v: [0, 0, 0], mult: 1 }; + function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } + function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } + function C(aA1) { return 3.0 * aA1; } - function completeProperty(expressionValue, property, type) { - Object.defineProperty(expressionValue, 'velocity', { - get: function () { - return property.getVelocityAtTime(property.comp.currentFrame); - }, - }); - expressionValue.numKeys = property.keyframes ? property.keyframes.length : 0; - expressionValue.key = function (pos) { - if (!expressionValue.numKeys) { - return 0; - } - var value = ''; - if ('s' in property.keyframes[pos - 1]) { - value = property.keyframes[pos - 1].s; - } else if ('e' in property.keyframes[pos - 2]) { - value = property.keyframes[pos - 2].e; - } else { - value = property.keyframes[pos - 2].s; - } - var valueProp = type === 'unidimensional' ? new Number(value) : Object.assign({}, value); // eslint-disable-line no-new-wrappers - valueProp.time = property.keyframes[pos - 1].t / property.elem.comp.globalData.frameRate; - valueProp.value = type === 'unidimensional' ? value[0] : value; - return valueProp; - }; - expressionValue.valueAtTime = property.getValueAtTime; - expressionValue.speedAtTime = property.getSpeedAtTime; - expressionValue.velocityAtTime = property.getVelocityAtTime; - expressionValue.propertyGroup = property.propertyGroup; - } - - function UnidimensionalPropertyInterface(property) { - if (!property || !('pv' in property)) { - property = defaultUnidimensionalValue; - } - var mult = 1 / property.mult; - var val = property.pv * mult; - var expressionValue = new Number(val); // eslint-disable-line no-new-wrappers - expressionValue.value = val; - completeProperty(expressionValue, property, 'unidimensional'); - - return function () { - if (property.k) { - property.getValue(); - } - val = property.v * mult; - if (expressionValue.value !== val) { - expressionValue = new Number(val); // eslint-disable-line no-new-wrappers - expressionValue.value = val; - completeProperty(expressionValue, property, 'unidimensional'); + // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. + function calcBezier(aT, aA1, aA2) { + return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } - return expressionValue; - }; - } - function MultidimensionalPropertyInterface(property) { - if (!property || !('pv' in property)) { - property = defaultMultidimensionalValue; - } - var mult = 1 / property.mult; - var len = (property.data && property.data.l) || property.pv.length; - var expressionValue = createTypedArray('float32', len); - var arrValue = createTypedArray('float32', len); - expressionValue.value = arrValue; - completeProperty(expressionValue, property, 'multidimensional'); + // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. + function getSlope(aT, aA1, aA2) { + return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); + } - return function () { - if (property.k) { - property.getValue(); + function binarySubdivide(aX, aA, aB, mX1, mX2) { + var currentX, + currentT, + i = 0; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); + return currentT; } - for (var i = 0; i < len; i += 1) { - arrValue[i] = property.v[i] * mult; - expressionValue[i] = arrValue[i]; + + function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { + for (var i = 0; i < NEWTON_ITERATIONS; ++i) { + var currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) return aGuessT; + var currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; } - return expressionValue; - }; - } - // TODO: try to avoid using this getter - function defaultGetter() { - return defaultUnidimensionalValue; - } + /** + * points is an array of [ mX1, mY1, mX2, mY2 ] + */ + function BezierEasing(points) { + this._p = points; + this._mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); + this._precomputed = false; + + this.get = this.get.bind(this); + } + + BezierEasing.prototype = { + + get: function (x) { + var mX1 = this._p[0], + mY1 = this._p[1], + mX2 = this._p[2], + mY2 = this._p[3]; + if (!this._precomputed) this._precompute(); + if (mX1 === mY1 && mX2 === mY2) return x; // linear + // Because JavaScript number are imprecise, we should guarantee the extremes are right. + if (x === 0) return 0; + if (x === 1) return 1; + return calcBezier(this._getTForX(x), mY1, mY2); + }, - return function (property) { - if (!property) { - return defaultGetter; - } if (property.propType === 'unidimensional') { - return UnidimensionalPropertyInterface(property); - } - return MultidimensionalPropertyInterface(property); - }; -}()); + // Private part -const TransformExpressionInterface = (function () { - return function (transform) { - function _thisFunction(name) { - switch (name) { - case 'scale': - case 'Scale': - case 'ADBE Scale': - case 6: - return _thisFunction.scale; - case 'rotation': - case 'Rotation': - case 'ADBE Rotation': - case 'ADBE Rotate Z': - case 10: - return _thisFunction.rotation; - case 'ADBE Rotate X': - return _thisFunction.xRotation; - case 'ADBE Rotate Y': - return _thisFunction.yRotation; - case 'position': - case 'Position': - case 'ADBE Position': - case 2: - return _thisFunction.position; - case 'ADBE Position_0': - return _thisFunction.xPosition; - case 'ADBE Position_1': - return _thisFunction.yPosition; - case 'ADBE Position_2': - return _thisFunction.zPosition; - case 'anchorPoint': - case 'AnchorPoint': - case 'Anchor Point': - case 'ADBE AnchorPoint': - case 1: - return _thisFunction.anchorPoint; - case 'opacity': - case 'Opacity': - case 11: - return _thisFunction.opacity; - default: - return null; - } - } - Object.defineProperty(_thisFunction, 'rotation', { - get: ExpressionPropertyInterface(transform.r || transform.rz), - }); - - Object.defineProperty(_thisFunction, 'zRotation', { - get: ExpressionPropertyInterface(transform.rz || transform.r), - }); - - Object.defineProperty(_thisFunction, 'xRotation', { - get: ExpressionPropertyInterface(transform.rx), - }); - - Object.defineProperty(_thisFunction, 'yRotation', { - get: ExpressionPropertyInterface(transform.ry), - }); - Object.defineProperty(_thisFunction, 'scale', { - get: ExpressionPropertyInterface(transform.s), - }); - var _px; - var _py; - var _pz; - var _transformFactory; - if (transform.p) { - _transformFactory = ExpressionPropertyInterface(transform.p); - } else { - _px = ExpressionPropertyInterface(transform.px); - _py = ExpressionPropertyInterface(transform.py); - if (transform.pz) { - _pz = ExpressionPropertyInterface(transform.pz); - } - } - Object.defineProperty(_thisFunction, 'position', { - get: function () { - if (transform.p) { - return _transformFactory(); - } - return [ - _px(), - _py(), - _pz ? _pz() : 0]; - }, - }); - - Object.defineProperty(_thisFunction, 'xPosition', { - get: ExpressionPropertyInterface(transform.px), - }); - - Object.defineProperty(_thisFunction, 'yPosition', { - get: ExpressionPropertyInterface(transform.py), - }); - - Object.defineProperty(_thisFunction, 'zPosition', { - get: ExpressionPropertyInterface(transform.pz), - }); - - Object.defineProperty(_thisFunction, 'anchorPoint', { - get: ExpressionPropertyInterface(transform.a), - }); - - Object.defineProperty(_thisFunction, 'opacity', { - get: ExpressionPropertyInterface(transform.o), - }); - - Object.defineProperty(_thisFunction, 'skew', { - get: ExpressionPropertyInterface(transform.sk), - }); - - Object.defineProperty(_thisFunction, 'skewAxis', { - get: ExpressionPropertyInterface(transform.sa), - }); - - Object.defineProperty(_thisFunction, 'orientation', { - get: ExpressionPropertyInterface(transform.or), - }); - - return _thisFunction; - }; -}()); - -const LayerExpressionInterface = (function () { - function getMatrix(time) { - var toWorldMat = new Matrix(); - if (time !== undefined) { - var propMatrix = this._elem.finalTransform.mProp.getValueAtTime(time); - propMatrix.clone(toWorldMat); - } else { - var transformMat = this._elem.finalTransform.mProp; - transformMat.applyToMatrix(toWorldMat); - } - return toWorldMat; - } - - function toWorldVec(arr, time) { - var toWorldMat = this.getMatrix(time); - toWorldMat.props[12] = 0; - toWorldMat.props[13] = 0; - toWorldMat.props[14] = 0; - return this.applyPoint(toWorldMat, arr); - } - - function toWorld(arr, time) { - var toWorldMat = this.getMatrix(time); - return this.applyPoint(toWorldMat, arr); - } - - function fromWorldVec(arr, time) { - var toWorldMat = this.getMatrix(time); - toWorldMat.props[12] = 0; - toWorldMat.props[13] = 0; - toWorldMat.props[14] = 0; - return this.invertPoint(toWorldMat, arr); - } - - function fromWorld(arr, time) { - var toWorldMat = this.getMatrix(time); - return this.invertPoint(toWorldMat, arr); - } - - function applyPoint(matrix, arr) { - if (this._elem.hierarchy && this._elem.hierarchy.length) { - var i; - var len = this._elem.hierarchy.length; - for (i = 0; i < len; i += 1) { - this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); - } - } - return matrix.applyToPointArray(arr[0], arr[1], arr[2] || 0); - } + _precompute: function () { + var mX1 = this._p[0], + mY1 = this._p[1], + mX2 = this._p[2], + mY2 = this._p[3]; + this._precomputed = true; + if (mX1 !== mY1 || mX2 !== mY2) { this._calcSampleValues(); } + }, - function invertPoint(matrix, arr) { - if (this._elem.hierarchy && this._elem.hierarchy.length) { - var i; - var len = this._elem.hierarchy.length; - for (i = 0; i < len; i += 1) { - this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); - } - } - return matrix.inversePoint(arr); - } - - function fromComp(arr) { - var toWorldMat = new Matrix(); - toWorldMat.reset(); - this._elem.finalTransform.mProp.applyToMatrix(toWorldMat); - if (this._elem.hierarchy && this._elem.hierarchy.length) { - var i; - var len = this._elem.hierarchy.length; - for (i = 0; i < len; i += 1) { - this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(toWorldMat); - } - return toWorldMat.inversePoint(arr); - } - return toWorldMat.inversePoint(arr); - } + _calcSampleValues: function () { + var mX1 = this._p[0], + mX2 = this._p[2]; + for (var i = 0; i < kSplineTableSize; ++i) { + this._mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + }, - function sampleImage() { - return [1, 1, 1, 1]; - } + /** + * getTForX chose the fastest heuristic to determine the percentage value precisely from a given X projection. + */ + _getTForX: function (aX) { + var mX1 = this._p[0], + mX2 = this._p[2], + mSampleValues = this._mSampleValues; - return function (elem) { - var transformInterface; + var intervalStart = 0.0; + var currentSample = 1; + var lastSample = kSplineTableSize - 1; - function _registerMaskInterface(maskManager) { - _thisLayerFunction.mask = new MaskManagerInterface(maskManager, elem); - } - function _registerEffectsInterface(effects) { - _thisLayerFunction.effect = effects; - } + for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { + intervalStart += kSampleStepSize; + } + --currentSample; - function _thisLayerFunction(name) { - switch (name) { - case 'ADBE Root Vectors Group': - case 'Contents': - case 2: - return _thisLayerFunction.shapeInterface; - case 1: - case 6: - case 'Transform': - case 'transform': - case 'ADBE Transform Group': - return transformInterface; - case 4: - case 'ADBE Effect Parade': - case 'effects': - case 'Effects': - return _thisLayerFunction.effect; - case 'ADBE Text Properties': - return _thisLayerFunction.textInterface; - default: - return null; - } - } - _thisLayerFunction.getMatrix = getMatrix; - _thisLayerFunction.invertPoint = invertPoint; - _thisLayerFunction.applyPoint = applyPoint; - _thisLayerFunction.toWorld = toWorld; - _thisLayerFunction.toWorldVec = toWorldVec; - _thisLayerFunction.fromWorld = fromWorld; - _thisLayerFunction.fromWorldVec = fromWorldVec; - _thisLayerFunction.toComp = toWorld; - _thisLayerFunction.fromComp = fromComp; - _thisLayerFunction.sampleImage = sampleImage; - _thisLayerFunction.sourceRectAtTime = elem.sourceRectAtTime.bind(elem); - _thisLayerFunction._elem = elem; - transformInterface = TransformExpressionInterface(elem.finalTransform.mProp); - var anchorPointDescriptor = getDescriptor(transformInterface, 'anchorPoint'); - Object.defineProperties(_thisLayerFunction, { - hasParent: { - get: function () { - return elem.hierarchy.length; - }, - }, - parent: { - get: function () { - return elem.hierarchy[0].layerInterface; - }, - }, - rotation: getDescriptor(transformInterface, 'rotation'), - scale: getDescriptor(transformInterface, 'scale'), - position: getDescriptor(transformInterface, 'position'), - opacity: getDescriptor(transformInterface, 'opacity'), - anchorPoint: anchorPointDescriptor, - anchor_point: anchorPointDescriptor, - transform: { - get: function () { - return transformInterface; - }, - }, - active: { - get: function () { - return elem.isInRange; + // Interpolate to provide an initial guess for t + var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]); + var guessForT = intervalStart + dist * kSampleStepSize; + + var initialSlope = getSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } if (initialSlope === 0.0) { + return guessForT; + } + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); }, - }, - }); - - _thisLayerFunction.startTime = elem.data.st; - _thisLayerFunction.index = elem.data.ind; - _thisLayerFunction.source = elem.data.refId; - _thisLayerFunction.height = elem.data.ty === 0 ? elem.data.h : 100; - _thisLayerFunction.width = elem.data.ty === 0 ? elem.data.w : 100; - _thisLayerFunction.inPoint = elem.data.ip / elem.comp.globalData.frameRate; - _thisLayerFunction.outPoint = elem.data.op / elem.comp.globalData.frameRate; - _thisLayerFunction._name = elem.data.nm; - - _thisLayerFunction.registerMaskInterface = _registerMaskInterface; - _thisLayerFunction.registerEffectsInterface = _registerEffectsInterface; - return _thisLayerFunction; - }; -}()); - -const propertyGroupFactory = (function () { - return function (interfaceFunction, parentPropertyGroup) { - return function (val) { - val = val === undefined ? 1 : val; - if (val <= 0) { - return interfaceFunction; - } - return parentPropertyGroup(val - 1); - }; - }; -}()); + }; -const PropertyInterface = (function () { - return function (propertyName, propertyGroup) { - var interfaceFunction = { - _name: propertyName, - }; + return ob; + }()); - function _propertyGroup(val) { - val = val === undefined ? 1 : val; - if (val <= 0) { - return interfaceFunction; + const pooling = (function () { + function double(arr) { + return arr.concat(createSizedArray(arr.length)); } - return propertyGroup(val - 1); - } - return _propertyGroup; - }; -}()); + return { + double: double, + }; + }()); -const EffectsExpressionInterface = (function () { - var ob = { - createEffectsInterface: createEffectsInterface, - }; + const poolFactory = (function () { + return function (initialLength, _create, _release) { + var _length = 0; + var _maxLength = initialLength; + var pool = createSizedArray(_maxLength); - function createEffectsInterface(elem, propertyGroup) { - if (elem.effectsManager) { - var effectElements = []; - var effectsData = elem.data.ef; - var i; - var len = elem.effectsManager.effectElements.length; - for (i = 0; i < len; i += 1) { - effectElements.push(createGroupInterface(effectsData[i], elem.effectsManager.effectElements[i], propertyGroup, elem)); - } + var ob = { + newElement: newElement, + release: release, + }; - var effects = elem.data.ef || []; - var groupInterface = function (name) { - i = 0; - len = effects.length; - while (i < len) { - if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { - return effectElements[i]; + function newElement() { + var element; + if (_length) { + _length -= 1; + element = pool[_length]; + } else { + element = _create(); } - i += 1; + return element; } - return null; - }; - Object.defineProperty(groupInterface, 'numProperties', { - get: function () { - return effects.length; - }, - }); - return groupInterface; - } - return null; - } - function createGroupInterface(data, elements, propertyGroup, elem) { - function groupInterface(name) { - var effects = data.ef; - var i = 0; - var len = effects.length; - while (i < len) { - if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { - if (effects[i].ty === 5) { - return effectElements[i]; + function release(element) { + if (_length === _maxLength) { + pool = pooling.double(pool); + _maxLength *= 2; } - return effectElements[i](); + if (_release) { + _release(element); + } + pool[_length] = element; + _length += 1; } - i += 1; - } - throw new Error(); - } - var _propertyGroup = propertyGroupFactory(groupInterface, propertyGroup); - - var effectElements = []; - var i; - var len = data.ef.length; - for (i = 0; i < len; i += 1) { - if (data.ef[i].ty === 5) { - effectElements.push(createGroupInterface(data.ef[i], elements.effectElements[i], elements.effectElements[i].propertyGroup, elem)); - } else { - effectElements.push(createValueInterface(elements.effectElements[i], data.ef[i].ty, elem, _propertyGroup)); + + return ob; + }; + }()); + + const bezierLengthPool = (function () { + function create() { + return { + addedLength: 0, + percents: createTypedArray('float32', getDefaultCurveSegments()), + lengths: createTypedArray('float32', getDefaultCurveSegments()), + }; } - } + return poolFactory(8, create); + }()); - if (data.mn === 'ADBE Color Control') { - Object.defineProperty(groupInterface, 'color', { - get: function () { - return effectElements[0](); - }, - }); - } - Object.defineProperties(groupInterface, { - numProperties: { - get: function () { - return data.np; - }, - }, - _name: { value: data.nm }, - propertyGroup: { value: _propertyGroup }, - }); - groupInterface.enabled = data.en !== 0; - groupInterface.active = groupInterface.enabled; - return groupInterface; - } - - function createValueInterface(element, type, elem, propertyGroup) { - var expressionProperty = ExpressionPropertyInterface(element.p); - function interfaceFunction() { - if (type === 10) { - return elem.comp.compInterface(element.p.v); - } - return expressionProperty(); - } + const segmentsLengthPool = (function () { + function create() { + return { + lengths: [], + totalLength: 0, + }; + } - if (element.p.setGroupProperty) { - element.p.setGroupProperty(PropertyInterface('', propertyGroup)); - } + function release(element) { + var i; + var len = element.lengths.length; + for (i = 0; i < len; i += 1) { + bezierLengthPool.release(element.lengths[i]); + } + element.lengths.length = 0; + } - return interfaceFunction; - } + return poolFactory(8, create, release); + }()); - return ob; -}()); + function bezFunction() { + var math = Math; -const CompExpressionInterface = (function () { - return function (comp) { - function _thisLayerFunction(name) { - var i = 0; - var len = comp.layers.length; - while (i < len) { - if (comp.layers[i].nm === name || comp.layers[i].ind === name) { - return comp.elements[i].layerInterface; - } - i += 1; - } - return null; - // return {active:false}; - } - Object.defineProperty(_thisLayerFunction, '_name', { value: comp.data.nm }); - _thisLayerFunction.layer = _thisLayerFunction; - _thisLayerFunction.pixelAspect = 1; - _thisLayerFunction.height = comp.data.h || comp.globalData.compSize.h; - _thisLayerFunction.width = comp.data.w || comp.globalData.compSize.w; - _thisLayerFunction.pixelAspect = 1; - _thisLayerFunction.frameDuration = 1 / comp.globalData.frameRate; - _thisLayerFunction.displayStartTime = 0; - _thisLayerFunction.numLayers = comp.layers.length; - return _thisLayerFunction; - }; -}()); - -const ShapePathInterface = ( - - function () { - return function pathInterfaceFactory(shape, view, propertyGroup) { - var prop = view.sh; - - function interfaceFunction(val) { - if (val === 'Shape' || val === 'shape' || val === 'Path' || val === 'path' || val === 'ADBE Vector Shape' || val === 2) { - return interfaceFunction.path; - } - return null; + function pointOnLine2D(x1, y1, x2, y2, x3, y3) { + var det1 = (x1 * y2) + (y1 * x3) + (x2 * y3) - (x3 * y2) - (y3 * x1) - (x2 * y1); + return det1 > -0.001 && det1 < 0.001; } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - prop.setGroupProperty(PropertyInterface('Path', _propertyGroup)); - Object.defineProperties(interfaceFunction, { - path: { - get: function () { - if (prop.k) { - prop.getValue(); + function pointOnLine3D(x1, y1, z1, x2, y2, z2, x3, y3, z3) { + if (z1 === 0 && z2 === 0 && z3 === 0) { + return pointOnLine2D(x1, y1, x2, y2, x3, y3); + } + var dist1 = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2)); + var dist2 = math.sqrt(math.pow(x3 - x1, 2) + math.pow(y3 - y1, 2) + math.pow(z3 - z1, 2)); + var dist3 = math.sqrt(math.pow(x3 - x2, 2) + math.pow(y3 - y2, 2) + math.pow(z3 - z2, 2)); + var diffDist; + if (dist1 > dist2) { + if (dist1 > dist3) { + diffDist = dist1 - dist2 - dist3; + } else { + diffDist = dist3 - dist2 - dist1; + } + } else if (dist3 > dist2) { + diffDist = dist3 - dist2 - dist1; + } else { + diffDist = dist2 - dist1 - dist3; + } + return diffDist > -0.0001 && diffDist < 0.0001; + } + + var getBezierLength = (function () { + return function (pt1, pt2, pt3, pt4) { + var curveSegments = getDefaultCurveSegments(); + var k; + var i; + var len; + var ptCoord; + var perc; + var addedLength = 0; + var ptDistance; + var point = []; + var lastPoint = []; + var lengthData = bezierLengthPool.newElement(); + len = pt3.length; + for (k = 0; k < curveSegments; k += 1) { + perc = k / (curveSegments - 1); + ptDistance = 0; + for (i = 0; i < len; i += 1) { + ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * pt3[i] + 3 * (1 - perc) * bmPow(perc, 2) * pt4[i] + bmPow(perc, 3) * pt2[i]; + point[i] = ptCoord; + if (lastPoint[i] !== null) { + ptDistance += bmPow(point[i] - lastPoint[i], 2); + } + lastPoint[i] = point[i]; } - return prop; - }, - }, - shape: { - get: function () { - if (prop.k) { - prop.getValue(); + if (ptDistance) { + ptDistance = bmSqrt(ptDistance); + addedLength += ptDistance; } - return prop; - }, - }, - _name: { value: shape.nm }, - ix: { value: shape.ix }, - propertyIndex: { value: shape.ix }, - mn: { value: shape.mn }, - propertyGroup: { value: propertyGroup }, - }); - return interfaceFunction; - }; - }() -); - -const ShapeExpressionInterface = (function () { - function iterateElements(shapes, view, propertyGroup) { - var arr = []; - var i; - var len = shapes ? shapes.length : 0; - for (i = 0; i < len; i += 1) { - if (shapes[i].ty === 'gr') { - arr.push(groupInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'fl') { - arr.push(fillInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'st') { - arr.push(strokeInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'tm') { - arr.push(trimInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'tr') { - // arr.push(transformInterfaceFactory(shapes[i],view[i],propertyGroup)); - } else if (shapes[i].ty === 'el') { - arr.push(ellipseInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'sr') { - arr.push(starInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'sh') { - arr.push(ShapePathInterface(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'rc') { - arr.push(rectInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'rd') { - arr.push(roundedInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'rp') { - arr.push(repeaterInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'gf') { - arr.push(gradientFillInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else { - arr.push(defaultInterfaceFactory(shapes[i], view[i], propertyGroup)); - } - } - return arr; - } - - function contentsInterfaceFactory(shape, view, propertyGroup) { - var interfaces; - var interfaceFunction = function _interfaceFunction(value) { - var i = 0; - var len = interfaces.length; - while (i < len) { - if (interfaces[i]._name === value || interfaces[i].mn === value || interfaces[i].propertyIndex === value || interfaces[i].ix === value || interfaces[i].ind === value) { - return interfaces[i]; + lengthData.percents[k] = perc; + lengthData.lengths[k] = addedLength; + } + lengthData.addedLength = addedLength; + return lengthData; + }; + }()); + + function getSegmentsLength(shapeData) { + var segmentsLength = segmentsLengthPool.newElement(); + var closed = shapeData.c; + var pathV = shapeData.v; + var pathO = shapeData.o; + var pathI = shapeData.i; + var i; + var len = shapeData._length; + var lengths = segmentsLength.lengths; + var totalLength = 0; + for (i = 0; i < len - 1; i += 1) { + lengths[i] = getBezierLength(pathV[i], pathV[i + 1], pathO[i], pathI[i + 1]); + totalLength += lengths[i].addedLength; } - i += 1; - } - if (typeof value === 'number') { - return interfaces[value - 1]; + if (closed && len) { + lengths[i] = getBezierLength(pathV[i], pathV[0], pathO[i], pathI[0]); + totalLength += lengths[i].addedLength; + } + segmentsLength.totalLength = totalLength; + return segmentsLength; } - return null; - }; - - interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - interfaces = iterateElements(shape.it, view.it, interfaceFunction.propertyGroup); - interfaceFunction.numProperties = interfaces.length; - var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); - interfaceFunction.transform = transformInterface; - interfaceFunction.propertyIndex = shape.cix; - interfaceFunction._name = shape.nm; - - return interfaceFunction; - } - function groupInterfaceFactory(shape, view, propertyGroup) { - var interfaceFunction = function _interfaceFunction(value) { - switch (value) { - case 'ADBE Vectors Group': - case 'Contents': - case 2: - return interfaceFunction.content; - // Not necessary for now. Keeping them here in case a new case appears - // case 'ADBE Vector Transform Group': - // case 3: - default: - return interfaceFunction.transform; + function BezierData(length) { + this.segmentLength = 0; + this.points = new Array(length); } - }; - interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var content = contentsInterfaceFactory(shape, view, interfaceFunction.propertyGroup); - var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); - interfaceFunction.content = content; - interfaceFunction.transform = transformInterface; - Object.defineProperty(interfaceFunction, '_name', { - get: function () { - return shape.nm; - }, - }); - // interfaceFunction.content = interfaceFunction; - interfaceFunction.numProperties = shape.np; - interfaceFunction.propertyIndex = shape.ix; - interfaceFunction.nm = shape.nm; - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function fillInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(val) { - if (val === 'Color' || val === 'color') { - return interfaceFunction.color; - } if (val === 'Opacity' || val === 'opacity') { - return interfaceFunction.opacity; + + function PointData(partial, point) { + this.partialLength = partial; + this.point = point; } - return null; - } - Object.defineProperties(interfaceFunction, { - color: { - get: ExpressionPropertyInterface(view.c), - }, - opacity: { - get: ExpressionPropertyInterface(view.o), - }, - _name: { value: shape.nm }, - mn: { value: shape.mn }, - }); - view.c.setGroupProperty(PropertyInterface('Color', propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); - return interfaceFunction; - } + var buildBezierData = (function () { + var storedData = {}; - function gradientFillInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(val) { - if (val === 'Start Point' || val === 'start point') { - return interfaceFunction.startPoint; - } - if (val === 'End Point' || val === 'end point') { - return interfaceFunction.endPoint; - } - if (val === 'Opacity' || val === 'opacity') { - return interfaceFunction.opacity; + return function (pt1, pt2, pt3, pt4) { + var bezierName = (pt1[0] + '_' + pt1[1] + '_' + pt2[0] + '_' + pt2[1] + '_' + pt3[0] + '_' + pt3[1] + '_' + pt4[0] + '_' + pt4[1]).replace(/\./g, 'p'); + if (!storedData[bezierName]) { + var curveSegments = getDefaultCurveSegments(); + var k; + var i; + var len; + var ptCoord; + var perc; + var addedLength = 0; + var ptDistance; + var point; + var lastPoint = null; + if (pt1.length === 2 && (pt1[0] !== pt2[0] || pt1[1] !== pt2[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt1[0] + pt3[0], pt1[1] + pt3[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt2[0] + pt4[0], pt2[1] + pt4[1])) { + curveSegments = 2; + } + var bezierData = new BezierData(curveSegments); + len = pt3.length; + for (k = 0; k < curveSegments; k += 1) { + point = createSizedArray(len); + perc = k / (curveSegments - 1); + ptDistance = 0; + for (i = 0; i < len; i += 1) { + ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * (pt1[i] + pt3[i]) + 3 * (1 - perc) * bmPow(perc, 2) * (pt2[i] + pt4[i]) + bmPow(perc, 3) * pt2[i]; + point[i] = ptCoord; + if (lastPoint !== null) { + ptDistance += bmPow(point[i] - lastPoint[i], 2); + } + } + ptDistance = bmSqrt(ptDistance); + addedLength += ptDistance; + bezierData.points[k] = new PointData(ptDistance, point); + lastPoint = point; + } + bezierData.segmentLength = addedLength; + storedData[bezierName] = bezierData; + } + return storedData[bezierName]; + }; + }()); + + function getDistancePerc(perc, bezierData) { + var percents = bezierData.percents; + var lengths = bezierData.lengths; + var len = percents.length; + var initPos = bmFloor((len - 1) * perc); + var lengthPos = perc * bezierData.addedLength; + var lPerc = 0; + if (initPos === len - 1 || initPos === 0 || lengthPos === lengths[initPos]) { + return percents[initPos]; + } + var dir = lengths[initPos] > lengthPos ? -1 : 1; + var flag = true; + while (flag) { + if (lengths[initPos] <= lengthPos && lengths[initPos + 1] > lengthPos) { + lPerc = (lengthPos - lengths[initPos]) / (lengths[initPos + 1] - lengths[initPos]); + flag = false; + } else { + initPos += dir; + } + if (initPos < 0 || initPos >= len - 1) { + // FIX for TypedArrays that don't store floating point values with enough accuracy + if (initPos === len - 1) { + return percents[initPos]; + } + flag = false; + } + } + return percents[initPos] + (percents[initPos + 1] - percents[initPos]) * lPerc; } - return null; - } - Object.defineProperties(interfaceFunction, { - startPoint: { - get: ExpressionPropertyInterface(view.s), - }, - endPoint: { - get: ExpressionPropertyInterface(view.e), - }, - opacity: { - get: ExpressionPropertyInterface(view.o), - }, - type: { - get: function () { - return 'a'; - }, - }, - _name: { value: shape.nm }, - mn: { value: shape.mn }, - }); - - view.s.setGroupProperty(PropertyInterface('Start Point', propertyGroup)); - view.e.setGroupProperty(PropertyInterface('End Point', propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); - return interfaceFunction; - } - function defaultInterfaceFactory() { - function interfaceFunction() { - return null; - } - return interfaceFunction; - } - - function strokeInterfaceFactory(shape, view, propertyGroup) { - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var _dashPropertyGroup = propertyGroupFactory(dashOb, _propertyGroup); - function addPropertyToDashOb(i) { - Object.defineProperty(dashOb, shape.d[i].nm, { - get: ExpressionPropertyInterface(view.d.dataProps[i].p), - }); - } - var i; - var len = shape.d ? shape.d.length : 0; - var dashOb = {}; - for (i = 0; i < len; i += 1) { - addPropertyToDashOb(i); - view.d.dataProps[i].p.setGroupProperty(_dashPropertyGroup); - } - function interfaceFunction(val) { - if (val === 'Color' || val === 'color') { - return interfaceFunction.color; - } if (val === 'Opacity' || val === 'opacity') { - return interfaceFunction.opacity; - } if (val === 'Stroke Width' || val === 'stroke width') { - return interfaceFunction.strokeWidth; + function getPointInSegment(pt1, pt2, pt3, pt4, percent, bezierData) { + var t1 = getDistancePerc(percent, bezierData); + var u1 = 1 - t1; + var ptX = math.round((u1 * u1 * u1 * pt1[0] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[0] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[0] + t1 * t1 * t1 * pt2[0]) * 1000) / 1000; + var ptY = math.round((u1 * u1 * u1 * pt1[1] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[1] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[1] + t1 * t1 * t1 * pt2[1]) * 1000) / 1000; + return [ptX, ptY]; } - return null; - } - Object.defineProperties(interfaceFunction, { - color: { - get: ExpressionPropertyInterface(view.c), - }, - opacity: { - get: ExpressionPropertyInterface(view.o), - }, - strokeWidth: { - get: ExpressionPropertyInterface(view.w), - }, - dash: { - get: function () { - return dashOb; - }, - }, - _name: { value: shape.nm }, - mn: { value: shape.mn }, - }); - view.c.setGroupProperty(PropertyInterface('Color', _propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); - view.w.setGroupProperty(PropertyInterface('Stroke Width', _propertyGroup)); - return interfaceFunction; - } + var bezierSegmentPoints = createTypedArray('float32', 8); - function trimInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(val) { - if (val === shape.e.ix || val === 'End' || val === 'end') { - return interfaceFunction.end; - } - if (val === shape.s.ix) { - return interfaceFunction.start; - } - if (val === shape.o.ix) { - return interfaceFunction.offset; + function getNewSegment(pt1, pt2, pt3, pt4, startPerc, endPerc, bezierData) { + if (startPerc < 0) { + startPerc = 0; + } else if (startPerc > 1) { + startPerc = 1; + } + var t0 = getDistancePerc(startPerc, bezierData); + endPerc = endPerc > 1 ? 1 : endPerc; + var t1 = getDistancePerc(endPerc, bezierData); + var i; + var len = pt1.length; + var u0 = 1 - t0; + var u1 = 1 - t1; + var u0u0u0 = u0 * u0 * u0; + var t0u0u0_3 = t0 * u0 * u0 * 3; // eslint-disable-line camelcase + var t0t0u0_3 = t0 * t0 * u0 * 3; // eslint-disable-line camelcase + var t0t0t0 = t0 * t0 * t0; + // + var u0u0u1 = u0 * u0 * u1; + var t0u0u1_3 = t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1; // eslint-disable-line camelcase + var t0t0u1_3 = t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1; // eslint-disable-line camelcase + var t0t0t1 = t0 * t0 * t1; + // + var u0u1u1 = u0 * u1 * u1; + var t0u1u1_3 = t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1; // eslint-disable-line camelcase + var t0t1u1_3 = t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1; // eslint-disable-line camelcase + var t0t1t1 = t0 * t1 * t1; + // + var u1u1u1 = u1 * u1 * u1; + var t1u1u1_3 = t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1; // eslint-disable-line camelcase + var t1t1u1_3 = t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1; // eslint-disable-line camelcase + var t1t1t1 = t1 * t1 * t1; + for (i = 0; i < len; i += 1) { + bezierSegmentPoints[i * 4] = math.round((u0u0u0 * pt1[i] + t0u0u0_3 * pt3[i] + t0t0u0_3 * pt4[i] + t0t0t0 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + bezierSegmentPoints[i * 4 + 1] = math.round((u0u0u1 * pt1[i] + t0u0u1_3 * pt3[i] + t0t0u1_3 * pt4[i] + t0t0t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + bezierSegmentPoints[i * 4 + 2] = math.round((u0u1u1 * pt1[i] + t0u1u1_3 * pt3[i] + t0t1u1_3 * pt4[i] + t0t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + bezierSegmentPoints[i * 4 + 3] = math.round((u1u1u1 * pt1[i] + t1u1u1_3 * pt3[i] + t1t1u1_3 * pt4[i] + t1t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + } + + return bezierSegmentPoints; } - return null; + + return { + getSegmentsLength: getSegmentsLength, + getNewSegment: getNewSegment, + getPointInSegment: getPointInSegment, + buildBezierData: buildBezierData, + pointOnLine2D: pointOnLine2D, + pointOnLine3D: pointOnLine3D, + }; } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - interfaceFunction.propertyIndex = shape.ix; + const bez = bezFunction(); - view.s.setGroupProperty(PropertyInterface('Start', _propertyGroup)); - view.e.setGroupProperty(PropertyInterface('End', _propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); - interfaceFunction.propertyIndex = shape.ix; - interfaceFunction.propertyGroup = propertyGroup; + const PropertyFactory = (function () { + var initFrame = initialDefaultFrame; + var mathAbs = Math.abs; - Object.defineProperties(interfaceFunction, { - start: { - get: ExpressionPropertyInterface(view.s), - }, - end: { - get: ExpressionPropertyInterface(view.e), - }, - offset: { - get: ExpressionPropertyInterface(view.o), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } + function interpolateValue(frameNum, caching) { + var offsetTime = this.offsetTime; + var newValue; + if (this.propType === 'multidimensional') { + newValue = createTypedArray('float32', this.pv.length); + } + var iterationIndex = caching.lastIndex; + var i = iterationIndex; + var len = this.keyframes.length - 1; + var flag = true; + var keyData; + var nextKeyData; + var keyframeMetadata; - function transformInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.a.ix === value || value === 'Anchor Point') { - return interfaceFunction.anchorPoint; - } - if (shape.o.ix === value || value === 'Opacity') { - return interfaceFunction.opacity; - } - if (shape.p.ix === value || value === 'Position') { - return interfaceFunction.position; - } - if (shape.r.ix === value || value === 'Rotation' || value === 'ADBE Vector Rotation') { - return interfaceFunction.rotation; - } - if (shape.s.ix === value || value === 'Scale') { - return interfaceFunction.scale; - } - if ((shape.sk && shape.sk.ix === value) || value === 'Skew') { - return interfaceFunction.skew; - } - if ((shape.sa && shape.sa.ix === value) || value === 'Skew Axis') { - return interfaceFunction.skewAxis; - } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - view.transform.mProps.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); - view.transform.mProps.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - view.transform.mProps.a.setGroupProperty(PropertyInterface('Anchor Point', _propertyGroup)); - view.transform.mProps.s.setGroupProperty(PropertyInterface('Scale', _propertyGroup)); - view.transform.mProps.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); - if (view.transform.mProps.sk) { - view.transform.mProps.sk.setGroupProperty(PropertyInterface('Skew', _propertyGroup)); - view.transform.mProps.sa.setGroupProperty(PropertyInterface('Skew Angle', _propertyGroup)); - } - view.transform.op.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); - Object.defineProperties(interfaceFunction, { - opacity: { - get: ExpressionPropertyInterface(view.transform.mProps.o), - }, - position: { - get: ExpressionPropertyInterface(view.transform.mProps.p), - }, - anchorPoint: { - get: ExpressionPropertyInterface(view.transform.mProps.a), - }, - scale: { - get: ExpressionPropertyInterface(view.transform.mProps.s), - }, - rotation: { - get: ExpressionPropertyInterface(view.transform.mProps.r), - }, - skew: { - get: ExpressionPropertyInterface(view.transform.mProps.sk), - }, - skewAxis: { - get: ExpressionPropertyInterface(view.transform.mProps.sa), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.ty = 'tr'; - interfaceFunction.mn = shape.mn; - interfaceFunction.propertyGroup = propertyGroup; - return interfaceFunction; - } - - function ellipseInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.p.ix === value) { - return interfaceFunction.position; - } - if (shape.s.ix === value) { - return interfaceFunction.size; - } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - interfaceFunction.propertyIndex = shape.ix; - var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; - prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); - prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - - Object.defineProperties(interfaceFunction, { - size: { - get: ExpressionPropertyInterface(prop.s), - }, - position: { - get: ExpressionPropertyInterface(prop.p), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } + while (flag) { + keyData = this.keyframes[i]; + nextKeyData = this.keyframes[i + 1]; + if (i === len - 1 && frameNum >= nextKeyData.t - offsetTime) { + if (keyData.h) { + keyData = nextKeyData; + } + iterationIndex = 0; + break; + } + if ((nextKeyData.t - offsetTime) > frameNum) { + iterationIndex = i; + break; + } + if (i < len - 1) { + i += 1; + } else { + iterationIndex = 0; + flag = false; + } + } + keyframeMetadata = this.keyframesMetadata[i] || {}; - function starInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.p.ix === value) { - return interfaceFunction.position; - } - if (shape.r.ix === value) { - return interfaceFunction.rotation; - } - if (shape.pt.ix === value) { - return interfaceFunction.points; - } - if (shape.or.ix === value || value === 'ADBE Vector Star Outer Radius') { - return interfaceFunction.outerRadius; - } - if (shape.os.ix === value) { - return interfaceFunction.outerRoundness; - } - if (shape.ir && (shape.ir.ix === value || value === 'ADBE Vector Star Inner Radius')) { - return interfaceFunction.innerRadius; - } - if (shape.is && shape.is.ix === value) { - return interfaceFunction.innerRoundness; + var k; + var kLen; + var perc; + var jLen; + var j; + var fnc; + var nextKeyTime = nextKeyData.t - offsetTime; + var keyTime = keyData.t - offsetTime; + var endValue; + if (keyData.to) { + if (!keyframeMetadata.bezierData) { + keyframeMetadata.bezierData = bez.buildBezierData(keyData.s, nextKeyData.s || keyData.e, keyData.to, keyData.ti); + } + var bezierData = keyframeMetadata.bezierData; + if (frameNum >= nextKeyTime || frameNum < keyTime) { + var ind = frameNum >= nextKeyTime ? bezierData.points.length - 1 : 0; + kLen = bezierData.points[ind].point.length; + for (k = 0; k < kLen; k += 1) { + newValue[k] = bezierData.points[ind].point[k]; + } + // caching._lastKeyframeIndex = -1; + } else { + if (keyframeMetadata.__fnct) { + fnc = keyframeMetadata.__fnct; + } else { + fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y, keyData.n).get; + keyframeMetadata.__fnct = fnc; + } + perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); + var distanceInLine = bezierData.segmentLength * perc; + + var segmentPerc; + var addedLength = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastAddedLength : 0; + j = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastPoint : 0; + flag = true; + jLen = bezierData.points.length; + while (flag) { + addedLength += bezierData.points[j].partialLength; + if (distanceInLine === 0 || perc === 0 || j === bezierData.points.length - 1) { + kLen = bezierData.points[j].point.length; + for (k = 0; k < kLen; k += 1) { + newValue[k] = bezierData.points[j].point[k]; + } + break; + } else if (distanceInLine >= addedLength && distanceInLine < addedLength + bezierData.points[j + 1].partialLength) { + segmentPerc = (distanceInLine - addedLength) / bezierData.points[j + 1].partialLength; + kLen = bezierData.points[j].point.length; + for (k = 0; k < kLen; k += 1) { + newValue[k] = bezierData.points[j].point[k] + (bezierData.points[j + 1].point[k] - bezierData.points[j].point[k]) * segmentPerc; + } + break; + } + if (j < jLen - 1) { + j += 1; + } else { + flag = false; + } + } + caching._lastPoint = j; + caching._lastAddedLength = addedLength - bezierData.points[j].partialLength; + caching._lastKeyframeIndex = i; + } + } else { + var outX; + var outY; + var inX; + var inY; + var keyValue; + len = keyData.s.length; + endValue = nextKeyData.s || keyData.e; + if (this.sh && keyData.h !== 1) { + if (frameNum >= nextKeyTime) { + newValue[0] = endValue[0]; + newValue[1] = endValue[1]; + newValue[2] = endValue[2]; + } else if (frameNum <= keyTime) { + newValue[0] = keyData.s[0]; + newValue[1] = keyData.s[1]; + newValue[2] = keyData.s[2]; + } else { + var quatStart = createQuaternion(keyData.s); + var quatEnd = createQuaternion(endValue); + var time = (frameNum - keyTime) / (nextKeyTime - keyTime); + quaternionToEuler(newValue, slerp(quatStart, quatEnd, time)); + } + } else { + for (i = 0; i < len; i += 1) { + if (keyData.h !== 1) { + if (frameNum >= nextKeyTime) { + perc = 1; + } else if (frameNum < keyTime) { + perc = 0; + } else { + if (keyData.o.x.constructor === Array) { + if (!keyframeMetadata.__fnct) { + keyframeMetadata.__fnct = []; + } + if (!keyframeMetadata.__fnct[i]) { + outX = keyData.o.x[i] === undefined ? keyData.o.x[0] : keyData.o.x[i]; + outY = keyData.o.y[i] === undefined ? keyData.o.y[0] : keyData.o.y[i]; + inX = keyData.i.x[i] === undefined ? keyData.i.x[0] : keyData.i.x[i]; + inY = keyData.i.y[i] === undefined ? keyData.i.y[0] : keyData.i.y[i]; + fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; + keyframeMetadata.__fnct[i] = fnc; + } else { + fnc = keyframeMetadata.__fnct[i]; + } + } else if (!keyframeMetadata.__fnct) { + outX = keyData.o.x; + outY = keyData.o.y; + inX = keyData.i.x; + inY = keyData.i.y; + fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; + keyData.keyframeMetadata = fnc; + } else { + fnc = keyframeMetadata.__fnct; + } + perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); + } + } + + endValue = nextKeyData.s || keyData.e; + keyValue = keyData.h === 1 ? keyData.s[i] : keyData.s[i] + (endValue[i] - keyData.s[i]) * perc; + + if (this.propType === 'multidimensional') { + newValue[i] = keyValue; + } else { + newValue = keyValue; + } + } + } + } + caching.lastIndex = iterationIndex; + return newValue; + } + + // based on @Toji's https://github.com/toji/gl-matrix/ + function slerp(a, b, t) { + var out = []; + var ax = a[0]; + var ay = a[1]; + var az = a[2]; + var aw = a[3]; + var bx = b[0]; + var by = b[1]; + var bz = b[2]; + var bw = b[3]; + + var omega; + var cosom; + var sinom; + var scale0; + var scale1; + + cosom = ax * bx + ay * by + az * bz + aw * bw; + if (cosom < 0.0) { + cosom = -cosom; + bx = -bx; + by = -by; + bz = -bz; + bw = -bw; + } + if ((1.0 - cosom) > 0.000001) { + omega = Math.acos(cosom); + sinom = Math.sin(omega); + scale0 = Math.sin((1.0 - t) * omega) / sinom; + scale1 = Math.sin(t * omega) / sinom; + } else { + scale0 = 1.0 - t; + scale1 = t; + } + out[0] = scale0 * ax + scale1 * bx; + out[1] = scale0 * ay + scale1 * by; + out[2] = scale0 * az + scale1 * bz; + out[3] = scale0 * aw + scale1 * bw; + + return out; + } + + function quaternionToEuler(out, quat) { + var qx = quat[0]; + var qy = quat[1]; + var qz = quat[2]; + var qw = quat[3]; + var heading = Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy * qy - 2 * qz * qz); + var attitude = Math.asin(2 * qx * qy + 2 * qz * qw); + var bank = Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx * qx - 2 * qz * qz); + out[0] = heading / degToRads; + out[1] = attitude / degToRads; + out[2] = bank / degToRads; + } + + function createQuaternion(values) { + var heading = values[0] * degToRads; + var attitude = values[1] * degToRads; + var bank = values[2] * degToRads; + var c1 = Math.cos(heading / 2); + var c2 = Math.cos(attitude / 2); + var c3 = Math.cos(bank / 2); + var s1 = Math.sin(heading / 2); + var s2 = Math.sin(attitude / 2); + var s3 = Math.sin(bank / 2); + var w = c1 * c2 * c3 - s1 * s2 * s3; + var x = s1 * s2 * c3 + c1 * c2 * s3; + var y = s1 * c2 * c3 + c1 * s2 * s3; + var z = c1 * s2 * c3 - s1 * c2 * s3; + + return [x, y, z, w]; + } + + function getValueAtCurrentTime() { + var frameNum = this.comp.renderedFrame - this.offsetTime; + var initTime = this.keyframes[0].t - this.offsetTime; + var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; + if (!(frameNum === this._caching.lastFrame || (this._caching.lastFrame !== initFrame && ((this._caching.lastFrame >= endTime && frameNum >= endTime) || (this._caching.lastFrame < initTime && frameNum < initTime))))) { + if (this._caching.lastFrame >= frameNum) { + this._caching._lastKeyframeIndex = -1; + this._caching.lastIndex = 0; + } + + var renderResult = this.interpolateValue(frameNum, this._caching); + this.pv = renderResult; + } + this._caching.lastFrame = frameNum; + return this.pv; } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; - interfaceFunction.propertyIndex = shape.ix; - prop.or.setGroupProperty(PropertyInterface('Outer Radius', _propertyGroup)); - prop.os.setGroupProperty(PropertyInterface('Outer Roundness', _propertyGroup)); - prop.pt.setGroupProperty(PropertyInterface('Points', _propertyGroup)); - prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); - if (shape.ir) { - prop.ir.setGroupProperty(PropertyInterface('Inner Radius', _propertyGroup)); - prop.is.setGroupProperty(PropertyInterface('Inner Roundness', _propertyGroup)); - } + function setVValue(val) { + var multipliedValue; + if (this.propType === 'unidimensional') { + multipliedValue = val * this.mult; + if (mathAbs(this.v - multipliedValue) > 0.00001) { + this.v = multipliedValue; + this._mdf = true; + } + } else { + var i = 0; + var len = this.v.length; + while (i < len) { + multipliedValue = val[i] * this.mult; + if (mathAbs(this.v[i] - multipliedValue) > 0.00001) { + this.v[i] = multipliedValue; + this._mdf = true; + } + i += 1; + } + } + } - Object.defineProperties(interfaceFunction, { - position: { - get: ExpressionPropertyInterface(prop.p), - }, - rotation: { - get: ExpressionPropertyInterface(prop.r), - }, - points: { - get: ExpressionPropertyInterface(prop.pt), - }, - outerRadius: { - get: ExpressionPropertyInterface(prop.or), - }, - outerRoundness: { - get: ExpressionPropertyInterface(prop.os), - }, - innerRadius: { - get: ExpressionPropertyInterface(prop.ir), - }, - innerRoundness: { - get: ExpressionPropertyInterface(prop.is), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } + function processEffectsSequence() { + if (this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) { + return; + } + if (this.lock) { + this.setVValue(this.pv); + return; + } + this.lock = true; + this._mdf = this._isFirstFrame; + var i; + var len = this.effectsSequence.length; + var finalValue = this.kf ? this.pv : this.data.k; + for (i = 0; i < len; i += 1) { + finalValue = this.effectsSequence[i](finalValue); + } + this.setVValue(finalValue); + this._isFirstFrame = false; + this.lock = false; + this.frameId = this.elem.globalData.frameId; + } - function rectInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.p.ix === value) { - return interfaceFunction.position; + function addEffect(effectFunction) { + this.effectsSequence.push(effectFunction); + this.container.addDynamicProperty(this); } - if (shape.r.ix === value) { - return interfaceFunction.roundness; + + function ValueProperty(elem, data, mult, container) { + this.propType = 'unidimensional'; + this.mult = mult || 1; + this.data = data; + this.v = mult ? data.k * mult : data.k; + this.pv = data.k; + this._mdf = false; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.k = false; + this.kf = false; + this.vel = 0; + this.effectsSequence = []; + this._isFirstFrame = true; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.addEffect = addEffect; + } + + function MultiDimensionalProperty(elem, data, mult, container) { + this.propType = 'multidimensional'; + this.mult = mult || 1; + this.data = data; + this._mdf = false; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.k = false; + this.kf = false; + this.frameId = -1; + var i; + var len = data.k.length; + this.v = createTypedArray('float32', len); + this.pv = createTypedArray('float32', len); + this.vel = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + this.v[i] = data.k[i] * this.mult; + this.pv[i] = data.k[i]; + } + this._isFirstFrame = true; + this.effectsSequence = []; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.addEffect = addEffect; + } + + function KeyframedValueProperty(elem, data, mult, container) { + this.propType = 'unidimensional'; + this.keyframes = data.k; + this.keyframesMetadata = []; + this.offsetTime = elem.data.st; + this.frameId = -1; + this._caching = { + lastFrame: initFrame, lastIndex: 0, value: 0, _lastKeyframeIndex: -1, + }; + this.k = true; + this.kf = true; + this.data = data; + this.mult = mult || 1; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.v = initFrame; + this.pv = initFrame; + this._isFirstFrame = true; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.interpolateValue = interpolateValue; + this.effectsSequence = [getValueAtCurrentTime.bind(this)]; + this.addEffect = addEffect; } - if (shape.s.ix === value || value === 'Size' || value === 'ADBE Vector Rect Size') { - return interfaceFunction.size; + + function KeyframedMultidimensionalProperty(elem, data, mult, container) { + this.propType = 'multidimensional'; + var i; + var len = data.k.length; + var s; + var e; + var to; + var ti; + for (i = 0; i < len - 1; i += 1) { + if (data.k[i].to && data.k[i].s && data.k[i + 1] && data.k[i + 1].s) { + s = data.k[i].s; + e = data.k[i + 1].s; + to = data.k[i].to; + ti = data.k[i].ti; + if ((s.length === 2 && !(s[0] === e[0] && s[1] === e[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], s[0] + to[0], s[1] + to[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], e[0] + ti[0], e[1] + ti[1])) || (s.length === 3 && !(s[0] === e[0] && s[1] === e[1] && s[2] === e[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], s[0] + to[0], s[1] + to[1], s[2] + to[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], e[0] + ti[0], e[1] + ti[1], e[2] + ti[2]))) { + data.k[i].to = null; + data.k[i].ti = null; + } + if (s[0] === e[0] && s[1] === e[1] && to[0] === 0 && to[1] === 0 && ti[0] === 0 && ti[1] === 0) { + if (s.length === 2 || (s[2] === e[2] && to[2] === 0 && ti[2] === 0)) { + data.k[i].to = null; + data.k[i].ti = null; + } + } + } + } + this.effectsSequence = [getValueAtCurrentTime.bind(this)]; + this.data = data; + this.keyframes = data.k; + this.keyframesMetadata = []; + this.offsetTime = elem.data.st; + this.k = true; + this.kf = true; + this._isFirstFrame = true; + this.mult = mult || 1; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.interpolateValue = interpolateValue; + this.frameId = -1; + var arrLen = data.k[0].s.length; + this.v = createTypedArray('float32', arrLen); + this.pv = createTypedArray('float32', arrLen); + for (i = 0; i < arrLen; i += 1) { + this.v[i] = initFrame; + this.pv[i] = initFrame; + } + this._caching = { lastFrame: initFrame, lastIndex: 0, value: createTypedArray('float32', arrLen) }; + this.addEffect = addEffect; + } + + function getProp(elem, data, type, mult, container) { + var p; + if (!data.k.length) { + p = new ValueProperty(elem, data, mult, container); + } else if (typeof (data.k[0]) === 'number') { + p = new MultiDimensionalProperty(elem, data, mult, container); + } else { + switch (type) { + case 0: + p = new KeyframedValueProperty(elem, data, mult, container); + break; + case 1: + p = new KeyframedMultidimensionalProperty(elem, data, mult, container); + break; + default: + break; + } + } + if (p.effectsSequence.length) { + container.addDynamicProperty(p); + } + return p; } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; - interfaceFunction.propertyIndex = shape.ix; - prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); - prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); + var ob = { + getProp: getProp, + }; + return ob; + }()); - Object.defineProperties(interfaceFunction, { - position: { - get: ExpressionPropertyInterface(prop.p), + function DynamicPropertyContainer() {} + DynamicPropertyContainer.prototype = { + addDynamicProperty: function (prop) { + if (this.dynamicProperties.indexOf(prop) === -1) { + this.dynamicProperties.push(prop); + this.container.addDynamicProperty(this); + this._isAnimated = true; + } }, - roundness: { - get: ExpressionPropertyInterface(prop.r), + iterateDynamicProperties: function () { + this._mdf = false; + var i; + var len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + this.dynamicProperties[i].getValue(); + if (this.dynamicProperties[i]._mdf) { + this._mdf = true; + } + } }, - size: { - get: ExpressionPropertyInterface(prop.s), + initDynamicPropertyContainer: function (container) { + this.container = container; + this.dynamicProperties = []; + this._mdf = false; + this._isAnimated = false; }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } + }; - function roundedInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.r.ix === value || value === 'Round Corners 1') { - return interfaceFunction.radius; + const pointPool = (function () { + function create() { + return createTypedArray('float32', 2); } - return null; - } - - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view; - interfaceFunction.propertyIndex = shape.ix; - prop.rd.setGroupProperty(PropertyInterface('Radius', _propertyGroup)); + return poolFactory(8, create); + }()); - Object.defineProperties(interfaceFunction, { - radius: { - get: ExpressionPropertyInterface(prop.rd), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function repeaterInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.c.ix === value || value === 'Copies') { - return interfaceFunction.copies; - } if (shape.o.ix === value || value === 'Offset') { - return interfaceFunction.offset; - } - return null; + function ShapePath() { + this.c = false; + this._length = 0; + this._maxLength = 8; + this.v = createSizedArray(this._maxLength); + this.o = createSizedArray(this._maxLength); + this.i = createSizedArray(this._maxLength); } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view; - interfaceFunction.propertyIndex = shape.ix; - prop.c.setGroupProperty(PropertyInterface('Copies', _propertyGroup)); - prop.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); - Object.defineProperties(interfaceFunction, { - copies: { - get: ExpressionPropertyInterface(prop.c), - }, - offset: { - get: ExpressionPropertyInterface(prop.o), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - return function (shapes, view, propertyGroup) { - var interfaces; - function _interfaceFunction(value) { - if (typeof value === 'number') { - value = value === undefined ? 1 : value; - if (value === 0) { - return propertyGroup; - } - return interfaces[value - 1]; - } + ShapePath.prototype.setPathData = function (closed, len) { + this.c = closed; + this.setLength(len); var i = 0; - var len = interfaces.length; while (i < len) { - if (interfaces[i]._name === value) { - return interfaces[i]; - } + this.v[i] = pointPool.newElement(); + this.o[i] = pointPool.newElement(); + this.i[i] = pointPool.newElement(); i += 1; } - return null; - } - function parentGroupWrapper() { - return propertyGroup; - } - _interfaceFunction.propertyGroup = propertyGroupFactory(_interfaceFunction, parentGroupWrapper); - interfaces = iterateElements(shapes, view, _interfaceFunction.propertyGroup); - _interfaceFunction.numProperties = interfaces.length; - _interfaceFunction._name = 'Contents'; - return _interfaceFunction; - }; -}()); - -const TextExpressionInterface = (function () { - return function (elem) { - var _prevValue; - var _sourceText; - function _thisLayerFunction(name) { - switch (name) { - case 'ADBE Text Document': - return _thisLayerFunction.sourceText; - default: - return null; + }; + + ShapePath.prototype.setLength = function (len) { + while (this._maxLength < len) { + this.doubleArrayLength(); } - } - Object.defineProperty(_thisLayerFunction, 'sourceText', { - get: function () { - elem.textProperty.getValue(); - var stringValue = elem.textProperty.currentData.t; - if (stringValue !== _prevValue) { - elem.textProperty.currentData.t = _prevValue; - _sourceText = new String(stringValue); // eslint-disable-line no-new-wrappers - // If stringValue is an empty string, eval returns undefined, so it has to be returned as a String primitive - _sourceText.value = stringValue || new String(stringValue); // eslint-disable-line no-new-wrappers - } - return _sourceText; - }, - }); - return _thisLayerFunction; - }; -}()); - -const getBlendMode = (function () { - var blendModeEnums = { - 0: 'source-over', - 1: 'multiply', - 2: 'screen', - 3: 'overlay', - 4: 'darken', - 5: 'lighten', - 6: 'color-dodge', - 7: 'color-burn', - 8: 'hard-light', - 9: 'soft-light', - 10: 'difference', - 11: 'exclusion', - 12: 'hue', - 13: 'saturation', - 14: 'color', - 15: 'luminosity', - }; - - return function (mode) { - return blendModeEnums[mode] || ''; - }; -}()); - -function SliderEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); -} -function AngleEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); -} -function ColorEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); -} -function PointEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); -} -function LayerIndexEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); -} -function MaskIndexEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); -} -function CheckboxEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); -} -function NoValueEffect() { - this.p = {}; -} - -function EffectsManager(data, element) { - var effects = data.ef || []; - this.effectElements = []; - var i; - var len = effects.length; - var effectItem; - for (i = 0; i < len; i += 1) { - effectItem = new GroupEffect(effects[i], element); - this.effectElements.push(effectItem); - } -} - -function GroupEffect(data, element) { - this.init(data, element); -} - -extendPrototype([DynamicPropertyContainer], GroupEffect); - -GroupEffect.prototype.getValue = GroupEffect.prototype.iterateDynamicProperties; - -GroupEffect.prototype.init = function (data, element) { - this.data = data; - this.effectElements = []; - this.initDynamicPropertyContainer(element); - var i; - var len = this.data.ef.length; - var eff; - var effects = this.data.ef; - for (i = 0; i < len; i += 1) { - eff = null; - switch (effects[i].ty) { - case 0: - eff = new SliderEffect(effects[i], element, this); - break; - case 1: - eff = new AngleEffect(effects[i], element, this); - break; - case 2: - eff = new ColorEffect(effects[i], element, this); - break; - case 3: - eff = new PointEffect(effects[i], element, this); - break; - case 4: - case 7: - eff = new CheckboxEffect(effects[i], element, this); - break; - case 10: - eff = new LayerIndexEffect(effects[i], element, this); - break; - case 11: - eff = new MaskIndexEffect(effects[i], element, this); - break; - case 5: - eff = new EffectsManager(effects[i], element, this); - break; - // case 6: - default: - eff = new NoValueEffect(effects[i], element, this); - break; - } - if (eff) { - this.effectElements.push(eff); - } - } -}; + this._length = len; + }; -function BaseElement() { -} + ShapePath.prototype.doubleArrayLength = function () { + this.v = this.v.concat(createSizedArray(this._maxLength)); + this.i = this.i.concat(createSizedArray(this._maxLength)); + this.o = this.o.concat(createSizedArray(this._maxLength)); + this._maxLength *= 2; + }; -BaseElement.prototype = { - checkMasks: function () { - if (!this.data.hasMask) { - return false; - } - var i = 0; - var len = this.data.masksProperties.length; - while (i < len) { - if ((this.data.masksProperties[i].mode !== 'n' && this.data.masksProperties[i].cl !== false)) { - return true; - } - i += 1; - } - return false; - }, - initExpressions: function () { - this.layerInterface = LayerExpressionInterface(this); - if (this.data.hasMask && this.maskManager) { - this.layerInterface.registerMaskInterface(this.maskManager); - } - var effectsInterface = EffectsExpressionInterface.createEffectsInterface(this, this.layerInterface); - this.layerInterface.registerEffectsInterface(effectsInterface); - - if (this.data.ty === 0 || this.data.xt) { - this.compInterface = CompExpressionInterface(this); - } else if (this.data.ty === 4) { - this.layerInterface.shapeInterface = ShapeExpressionInterface(this.shapesData, this.itemsData, this.layerInterface); - this.layerInterface.content = this.layerInterface.shapeInterface; - } else if (this.data.ty === 5) { - this.layerInterface.textInterface = TextExpressionInterface(this); - this.layerInterface.text = this.layerInterface.textInterface; - } - }, - setBlendMode: function () { - var blendModeValue = getBlendMode(this.data.bm); - var elem = this.baseElement || this.layerElement; - - elem.style['mix-blend-mode'] = blendModeValue; - }, - initBaseData: function (data, globalData, comp) { - this.globalData = globalData; - this.comp = comp; - this.data = data; - this.layerId = createElementID(); - - // Stretch factor for old animations missing this property. - if (!this.data.sr) { - this.data.sr = 1; - } - // effects manager - this.effectsManager = new EffectsManager(this.data, this, this.dynamicProperties); - }, - getType: function () { - return this.type; - }, - sourceRectAtTime: function () {}, -}; - -/** - * @file - * Handles element's layer frame update. - * Checks layer in point and out point - * - */ - -function FrameElement() {} - -FrameElement.prototype = { - /** - * @function - * Initializes frame related properties. - * - */ - initFrame: function () { - // set to true when inpoint is rendered - this._isFirstFrame = false; - // list of animated properties - this.dynamicProperties = []; - // If layer has been modified in current tick this will be true - this._mdf = false; - }, - /** - * @function - * Calculates all dynamic values - * - * @param {number} num - * current frame number in Layer's time - * @param {boolean} isVisible - * if layers is currently in range - * - */ - prepareProperties: function (num, isVisible) { - var i; - var len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - if (isVisible || (this._isParent && this.dynamicProperties[i].propType === 'transform')) { - this.dynamicProperties[i].getValue(); - if (this.dynamicProperties[i]._mdf) { - this.globalData._mdf = true; - this._mdf = true; - } - } - } - }, - addDynamicProperty: function (prop) { - if (this.dynamicProperties.indexOf(prop) === -1) { - this.dynamicProperties.push(prop); - } - }, -}; - -const FootageInterface = (function () { - var outlineInterfaceFactory = (function (elem) { - var currentPropertyName = ''; - var currentProperty = elem.getFootageData(); - function init() { - currentPropertyName = ''; - currentProperty = elem.getFootageData(); - return searchProperty; - } - function searchProperty(value) { - if (currentProperty[value]) { - currentPropertyName = value; - currentProperty = currentProperty[value]; - if (typeof currentProperty === 'object') { - return searchProperty; - } - return currentProperty; + ShapePath.prototype.setXYAt = function (x, y, type, pos, replace) { + var arr; + this._length = Math.max(this._length, pos + 1); + if (this._length >= this._maxLength) { + this.doubleArrayLength(); } - var propertyNameIndex = value.indexOf(currentPropertyName); - if (propertyNameIndex !== -1) { - var index = parseInt(value.substr(propertyNameIndex + currentPropertyName.length), 10); - currentProperty = currentProperty[index]; - if (typeof currentProperty === 'object') { - return searchProperty; - } - return currentProperty; + switch (type) { + case 'v': + arr = this.v; + break; + case 'i': + arr = this.i; + break; + case 'o': + arr = this.o; + break; + default: + arr = []; + break; } - return ''; - } - return init; - }); - - var dataInterfaceFactory = function (elem) { - function interfaceFunction(value) { - if (value === 'Outline') { - return interfaceFunction.outlineInterface(); + if (!arr[pos] || (arr[pos] && !replace)) { + arr[pos] = pointPool.newElement(); } - return null; - } + arr[pos][0] = x; + arr[pos][1] = y; + }; - interfaceFunction._name = 'Outline'; - interfaceFunction.outlineInterface = outlineInterfaceFactory(elem); - return interfaceFunction; - }; + ShapePath.prototype.setTripleAt = function (vX, vY, oX, oY, iX, iY, pos, replace) { + this.setXYAt(vX, vY, 'v', pos, replace); + this.setXYAt(oX, oY, 'o', pos, replace); + this.setXYAt(iX, iY, 'i', pos, replace); + }; - return function (elem) { - function _interfaceFunction(value) { - if (value === 'Data') { - return _interfaceFunction.dataInterface; + ShapePath.prototype.reverse = function () { + var newPath = new ShapePath(); + newPath.setPathData(this.c, this._length); + var vertices = this.v; + var outPoints = this.o; + var inPoints = this.i; + var init = 0; + if (this.c) { + newPath.setTripleAt(vertices[0][0], vertices[0][1], inPoints[0][0], inPoints[0][1], outPoints[0][0], outPoints[0][1], 0, false); + init = 1; } - return null; - } + var cnt = this._length - 1; + var len = this._length; - _interfaceFunction._name = 'Data'; - _interfaceFunction.dataInterface = dataInterfaceFactory(elem); - return _interfaceFunction; - }; -}()); - -function FootageElement(data, globalData, comp) { - this.initFrame(); - this.initRenderable(); - this.assetData = globalData.getAssetData(data.refId); - this.footageData = globalData.imageLoader.getAsset(this.assetData); - this.initBaseData(data, globalData, comp); -} - -FootageElement.prototype.prepareFrame = function () { -}; - -extendPrototype([RenderableElement, BaseElement, FrameElement], FootageElement); - -FootageElement.prototype.getBaseElement = function () { - return null; -}; - -FootageElement.prototype.renderFrame = function () { -}; - -FootageElement.prototype.destroy = function () { -}; - -FootageElement.prototype.initExpressions = function () { - this.layerInterface = FootageInterface(this); -}; - -FootageElement.prototype.getFootageData = function () { - return this.footageData; -}; - -function AudioElement(data, globalData, comp) { - this.initFrame(); - this.initRenderable(); - this.assetData = globalData.getAssetData(data.refId); - this.initBaseData(data, globalData, comp); - this._isPlaying = false; - this._canPlay = false; - var assetPath = this.globalData.getAssetsPath(this.assetData); - this.audio = this.globalData.audioController.createAudio(assetPath); - this._currentTime = 0; - this.globalData.audioController.addAudio(this); - this._volumeMultiplier = 1; - this._volume = 1; - this._previousVolume = null; - this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; - this.lv = PropertyFactory.getProp(this, data.au && data.au.lv ? data.au.lv : { k: [100] }, 1, 0.01, this); -} - -AudioElement.prototype.prepareFrame = function (num) { - this.prepareRenderableFrame(num, true); - this.prepareProperties(num, true); - if (!this.tm._placeholder) { - var timeRemapped = this.tm.v; - this._currentTime = timeRemapped; - } else { - this._currentTime = num / this.data.sr; - } - this._volume = this.lv.v[0]; - var totalVolume = this._volume * this._volumeMultiplier; - if (this._previousVolume !== totalVolume) { - this._previousVolume = totalVolume; - this.audio.volume(totalVolume); - } -}; - -extendPrototype([RenderableElement, BaseElement, FrameElement], AudioElement); - -AudioElement.prototype.renderFrame = function () { - if (this.isInRange && this._canPlay) { - if (!this._isPlaying) { - this.audio.play(); - this.audio.seek(this._currentTime / this.globalData.frameRate); - this._isPlaying = true; - } else if (!this.audio.playing() - || Math.abs(this._currentTime / this.globalData.frameRate - this.audio.seek()) > 0.1 - ) { - this.audio.seek(this._currentTime / this.globalData.frameRate); - } - } -}; - -AudioElement.prototype.show = function () { - // this.audio.play() -}; - -AudioElement.prototype.hide = function () { - this.audio.pause(); - this._isPlaying = false; -}; - -AudioElement.prototype.pause = function () { - this.audio.pause(); - this._isPlaying = false; - this._canPlay = false; -}; - -AudioElement.prototype.resume = function () { - this._canPlay = true; -}; - -AudioElement.prototype.setRate = function (rateValue) { - this.audio.rate(rateValue); -}; - -AudioElement.prototype.volume = function (volumeValue) { - this._volumeMultiplier = volumeValue; - this._previousVolume = volumeValue * this._volume; - this.audio.volume(this._previousVolume); -}; - -AudioElement.prototype.getBaseElement = function () { - return null; -}; - -AudioElement.prototype.destroy = function () { -}; - -AudioElement.prototype.sourceRectAtTime = function () { -}; - -AudioElement.prototype.initExpressions = function () { -}; - -function BaseRenderer() {} -BaseRenderer.prototype.checkLayers = function (num) { - var i; - var len = this.layers.length; - var data; - this.completeLayers = true; - for (i = len - 1; i >= 0; i -= 1) { - if (!this.elements[i]) { - data = this.layers[i]; - if (data.ip - data.st <= (num - this.layers[i].st) && data.op - data.st > (num - this.layers[i].st)) { - this.buildItem(i); - } - } - this.completeLayers = this.elements[i] ? this.completeLayers : false; - } - this.checkPendingElements(); -}; - -BaseRenderer.prototype.createItem = function (layer) { - switch (layer.ty) { - case 2: - return this.createImage(layer); - case 0: - return this.createComp(layer); - case 1: - return this.createSolid(layer); - case 3: - return this.createNull(layer); - case 4: - return this.createShape(layer); - case 5: - return this.createText(layer); - case 6: - return this.createAudio(layer); - case 13: - return this.createCamera(layer); - case 15: - return this.createFootage(layer); - default: - return this.createNull(layer); - } -}; - -BaseRenderer.prototype.createCamera = function () { - throw new Error('You\'re using a 3d camera. Try the html renderer.'); -}; - -BaseRenderer.prototype.createAudio = function (data) { - return new AudioElement(data, this.globalData, this); -}; - -BaseRenderer.prototype.createFootage = function (data) { - return new FootageElement(data, this.globalData, this); -}; - -BaseRenderer.prototype.buildAllItems = function () { - var i; - var len = this.layers.length; - for (i = 0; i < len; i += 1) { - this.buildItem(i); - } - this.checkPendingElements(); -}; - -BaseRenderer.prototype.includeLayers = function (newLayers) { - this.completeLayers = false; - var i; - var len = newLayers.length; - var j; - var jLen = this.layers.length; - for (i = 0; i < len; i += 1) { - j = 0; - while (j < jLen) { - if (this.layers[j].id === newLayers[i].id) { - this.layers[j] = newLayers[i]; - break; - } - j += 1; - } - } -}; - -BaseRenderer.prototype.setProjectInterface = function (pInterface) { - this.globalData.projectInterface = pInterface; -}; - -BaseRenderer.prototype.initItems = function () { - if (!this.globalData.progressiveLoad) { - this.buildAllItems(); - } -}; -BaseRenderer.prototype.buildElementParenting = function (element, parentName, hierarchy) { - var elements = this.elements; - var layers = this.layers; - var i = 0; - var len = layers.length; - while (i < len) { - if (layers[i].ind == parentName) { // eslint-disable-line eqeqeq - if (!elements[i] || elements[i] === true) { - this.buildItem(i); - this.addPendingElement(element); - } else { - hierarchy.push(elements[i]); - elements[i].setAsParent(); - if (layers[i].parent !== undefined) { - this.buildElementParenting(element, layers[i].parent, hierarchy); - } else { - element.setHierarchy(hierarchy); - } + var i; + for (i = init; i < len; i += 1) { + newPath.setTripleAt(vertices[cnt][0], vertices[cnt][1], inPoints[cnt][0], inPoints[cnt][1], outPoints[cnt][0], outPoints[cnt][1], i, false); + cnt -= 1; } - } - i += 1; - } -}; - -BaseRenderer.prototype.addPendingElement = function (element) { - this.pendingElements.push(element); -}; - -BaseRenderer.prototype.searchExtraCompositions = function (assets) { - var i; - var len = assets.length; - for (i = 0; i < len; i += 1) { - if (assets[i].xt) { - var comp = this.createComp(assets[i]); - comp.initExpressions(); - this.globalData.projectInterface.registerComposition(comp); - } - } -}; - -BaseRenderer.prototype.getElementByPath = function (path) { - var pathValue = path.shift(); - var element; - if (typeof pathValue === 'number') { - element = this.elements[pathValue]; - } else { - var i; - var len = this.elements.length; - for (i = 0; i < len; i += 1) { - if (this.elements[i].data.nm === pathValue) { - element = this.elements[i]; - break; + return newPath; + }; + + const shapePool = (function () { + function create() { + return new ShapePath(); } - } - } - if (path.length === 0) { - return element; - } - return element.getElementByPath(path); -}; - -BaseRenderer.prototype.setupGlobalData = function (animData, fontsContainer) { - this.globalData.fontManager = new FontManager(); - this.globalData.fontManager.addChars(animData.chars); - this.globalData.fontManager.addFonts(animData.fonts, fontsContainer); - this.globalData.getAssetData = this.animationItem.getAssetData.bind(this.animationItem); - this.globalData.getAssetsPath = this.animationItem.getAssetsPath.bind(this.animationItem); - this.globalData.imageLoader = this.animationItem.imagePreloader; - this.globalData.audioController = this.animationItem.audioController; - this.globalData.frameId = 0; - this.globalData.frameRate = animData.fr; - this.globalData.nm = animData.nm; - this.globalData.compSize = { - w: animData.w, - h: animData.h, - }; -}; - -function TransformElement() {} - -TransformElement.prototype = { - initTransform: function () { - this.finalTransform = { - mProp: this.data.ks ? TransformPropertyFactory.getTransformProperty(this, this.data.ks, this) : { o: 0 }, - _matMdf: false, - _opMdf: false, - mat: new Matrix(), - }; - if (this.data.ao) { - this.finalTransform.mProp.autoOriented = true; - } - // TODO: check TYPE 11: Guided elements - if (this.data.ty !== 11) { - // this.createElements(); - } - }, - renderTransform: function () { - this.finalTransform._opMdf = this.finalTransform.mProp.o._mdf || this._isFirstFrame; - this.finalTransform._matMdf = this.finalTransform.mProp._mdf || this._isFirstFrame; - - if (this.hierarchy) { - var mat; - var finalMat = this.finalTransform.mat; - var i = 0; - var len = this.hierarchy.length; - // Checking if any of the transformation matrices in the hierarchy chain has changed. - if (!this.finalTransform._matMdf) { - while (i < len) { - if (this.hierarchy[i].finalTransform.mProp._mdf) { - this.finalTransform._matMdf = true; - break; - } - i += 1; - } + function release(shapePath) { + var len = shapePath._length; + var i; + for (i = 0; i < len; i += 1) { + pointPool.release(shapePath.v[i]); + pointPool.release(shapePath.i[i]); + pointPool.release(shapePath.o[i]); + shapePath.v[i] = null; + shapePath.i[i] = null; + shapePath.o[i] = null; + } + shapePath._length = 0; + shapePath.c = false; } - if (this.finalTransform._matMdf) { - mat = this.finalTransform.mProp.v.props; - finalMat.cloneFromProps(mat); + function clone(shape) { + var cloned = factory.newElement(); + var i; + var len = shape._length === undefined ? shape.v.length : shape._length; + cloned.setLength(len); + cloned.c = shape.c; + for (i = 0; i < len; i += 1) { - mat = this.hierarchy[i].finalTransform.mProp.v.props; - finalMat.transform(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5], mat[6], mat[7], mat[8], mat[9], mat[10], mat[11], mat[12], mat[13], mat[14], mat[15]); + cloned.setTripleAt(shape.v[i][0], shape.v[i][1], shape.o[i][0], shape.o[i][1], shape.i[i][0], shape.i[i][1], i); } + return cloned; } + + var factory = poolFactory(4, create, release); + factory.clone = clone; + + return factory; + }()); + + function ShapeCollection() { + this._length = 0; + this._maxLength = 4; + this.shapes = createSizedArray(this._maxLength); } - }, - globalToLocal: function (pt) { - var transforms = []; - transforms.push(this.finalTransform); - var flag = true; - var comp = this.comp; - while (flag) { - if (comp.finalTransform) { - if (comp.data.hasMask) { - transforms.splice(0, 0, comp.finalTransform); - } - comp = comp.comp; - } else { - flag = false; + + ShapeCollection.prototype.addShape = function (shapeData) { + if (this._length === this._maxLength) { + this.shapes = this.shapes.concat(createSizedArray(this._maxLength)); + this._maxLength *= 2; } - } - var i; - var len = transforms.length; - var ptNew; - for (i = 0; i < len; i += 1) { - ptNew = transforms[i].mat.applyToPointArray(0, 0, 0); - // ptNew = transforms[i].mat.applyToPointArray(pt[0],pt[1],pt[2]); - pt = [pt[0] - ptNew[0], pt[1] - ptNew[1], 0]; - } - return pt; - }, - mHelper: new Matrix(), -}; - -function MaskElement(data, element, globalData) { - this.data = data; - this.element = element; - this.globalData = globalData; - this.storedData = []; - this.masksProperties = this.data.masksProperties || []; - this.maskElement = null; - var defs = this.globalData.defs; - var i; - var len = this.masksProperties ? this.masksProperties.length : 0; - this.viewData = createSizedArray(len); - this.solidPath = ''; - - var path; - var properties = this.masksProperties; - var count = 0; - var currentMasks = []; - var j; - var jLen; - var layerId = createElementID(); - var rect; - var expansor; - var feMorph; - var x; - var maskType = 'clipPath'; - var maskRef = 'clip-path'; - for (i = 0; i < len; i += 1) { - if ((properties[i].mode !== 'a' && properties[i].mode !== 'n') || properties[i].inv || properties[i].o.k !== 100 || properties[i].o.x) { - maskType = 'mask'; - maskRef = 'mask'; - } + this.shapes[this._length] = shapeData; + this._length += 1; + }; - if ((properties[i].mode === 's' || properties[i].mode === 'i') && count === 0) { - rect = createNS('rect'); - rect.setAttribute('fill', '#ffffff'); - rect.setAttribute('width', this.element.comp.data.w || 0); - rect.setAttribute('height', this.element.comp.data.h || 0); - currentMasks.push(rect); - } else { - rect = null; - } + ShapeCollection.prototype.releaseShapes = function () { + var i; + for (i = 0; i < this._length; i += 1) { + shapePool.release(this.shapes[i]); + } + this._length = 0; + }; - path = createNS('path'); - if (properties[i].mode === 'n') { - // TODO move this to a factory or to a constructor - this.viewData[i] = { - op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), - prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), - elem: path, - lastPath: '', - }; - defs.appendChild(path); - } else { - count += 1; - - path.setAttribute('fill', properties[i].mode === 's' ? '#000000' : '#ffffff'); - path.setAttribute('clip-rule', 'nonzero'); - var filterID; - - if (properties[i].x.k !== 0) { - maskType = 'mask'; - maskRef = 'mask'; - x = PropertyFactory.getProp(this.element, properties[i].x, 0, null, this.element); - filterID = createElementID(); - expansor = createNS('filter'); - expansor.setAttribute('id', filterID); - feMorph = createNS('feMorphology'); - feMorph.setAttribute('operator', 'erode'); - feMorph.setAttribute('in', 'SourceGraphic'); - feMorph.setAttribute('radius', '0'); - expansor.appendChild(feMorph); - defs.appendChild(expansor); - path.setAttribute('stroke', properties[i].mode === 's' ? '#000000' : '#ffffff'); - } else { - feMorph = null; - x = null; - } - - // TODO move this to a factory or to a constructor - this.storedData[i] = { - elem: path, - x: x, - expan: feMorph, - lastPath: '', - lastOperator: '', - filterId: filterID, - lastRadius: 0, + const shapeCollectionPool = (function () { + var ob = { + newShapeCollection: newShapeCollection, + release: release, }; - if (properties[i].mode === 'i') { - jLen = currentMasks.length; - var g = createNS('g'); - for (j = 0; j < jLen; j += 1) { - g.appendChild(currentMasks[j]); - } - var mask = createNS('mask'); - mask.setAttribute('mask-type', 'alpha'); - mask.setAttribute('id', layerId + '_' + count); - mask.appendChild(path); - defs.appendChild(mask); - g.setAttribute('mask', 'url(' + getLocationHref() + '#' + layerId + '_' + count + ')'); - - currentMasks.length = 0; - currentMasks.push(g); - } else { - currentMasks.push(path); - } - if (properties[i].inv && !this.solidPath) { - this.solidPath = this.createLayerSolidPath(); - } - // TODO move this to a factory or to a constructor - this.viewData[i] = { - elem: path, - lastPath: '', - op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), - prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), - invRect: rect, - }; - if (!this.viewData[i].prop.k) { - this.drawPath(properties[i], this.viewData[i].prop.v, this.viewData[i]); - } - } - } - - this.maskElement = createNS(maskType); - - len = currentMasks.length; - for (i = 0; i < len; i += 1) { - this.maskElement.appendChild(currentMasks[i]); - } - - if (count > 0) { - this.maskElement.setAttribute('id', layerId); - this.element.maskedElement.setAttribute(maskRef, 'url(' + getLocationHref() + '#' + layerId + ')'); - defs.appendChild(this.maskElement); - } - if (this.viewData.length) { - this.element.addRenderableComponent(this); - } -} - -MaskElement.prototype.getMaskProperty = function (pos) { - return this.viewData[pos].prop; -}; - -MaskElement.prototype.renderFrame = function (isFirstFrame) { - var finalMat = this.element.finalTransform.mat; - var i; - var len = this.masksProperties.length; - for (i = 0; i < len; i += 1) { - if (this.viewData[i].prop._mdf || isFirstFrame) { - this.drawPath(this.masksProperties[i], this.viewData[i].prop.v, this.viewData[i]); - } - if (this.viewData[i].op._mdf || isFirstFrame) { - this.viewData[i].elem.setAttribute('fill-opacity', this.viewData[i].op.v); - } - if (this.masksProperties[i].mode !== 'n') { - if (this.viewData[i].invRect && (this.element.finalTransform.mProp._mdf || isFirstFrame)) { - this.viewData[i].invRect.setAttribute('transform', finalMat.getInverseMatrix().to2dCSS()); - } - if (this.storedData[i].x && (this.storedData[i].x._mdf || isFirstFrame)) { - var feMorph = this.storedData[i].expan; - if (this.storedData[i].x.v < 0) { - if (this.storedData[i].lastOperator !== 'erode') { - this.storedData[i].lastOperator = 'erode'; - this.storedData[i].elem.setAttribute('filter', 'url(' + getLocationHref() + '#' + this.storedData[i].filterId + ')'); - } - feMorph.setAttribute('radius', -this.storedData[i].x.v); + + var _length = 0; + var _maxLength = 4; + var pool = createSizedArray(_maxLength); + + function newShapeCollection() { + var shapeCollection; + if (_length) { + _length -= 1; + shapeCollection = pool[_length]; } else { - if (this.storedData[i].lastOperator !== 'dilate') { - this.storedData[i].lastOperator = 'dilate'; - this.storedData[i].elem.setAttribute('filter', null); - } - this.storedData[i].elem.setAttribute('stroke-width', this.storedData[i].x.v * 2); + shapeCollection = new ShapeCollection(); } + return shapeCollection; } - } - } -}; - -MaskElement.prototype.getMaskelement = function () { - return this.maskElement; -}; - -MaskElement.prototype.createLayerSolidPath = function () { - var path = 'M0,0 '; - path += ' h' + this.globalData.compSize.w; - path += ' v' + this.globalData.compSize.h; - path += ' h-' + this.globalData.compSize.w; - path += ' v-' + this.globalData.compSize.h + ' '; - return path; -}; - -MaskElement.prototype.drawPath = function (pathData, pathNodes, viewData) { - var pathString = ' M' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; - var i; - var len; - len = pathNodes._length; - for (i = 1; i < len; i += 1) { - // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[i][0]+','+pathNodes.i[i][1] + " "+pathNodes.v[i][0]+','+pathNodes.v[i][1]; - pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[i][0] + ',' + pathNodes.i[i][1] + ' ' + pathNodes.v[i][0] + ',' + pathNodes.v[i][1]; - } - // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[0][0]+','+pathNodes.i[0][1] + " "+pathNodes.v[0][0]+','+pathNodes.v[0][1]; - if (pathNodes.c && len > 1) { - pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[0][0] + ',' + pathNodes.i[0][1] + ' ' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; - } - // pathNodes.__renderedString = pathString; - - if (viewData.lastPath !== pathString) { - var pathShapeValue = ''; - if (viewData.elem) { - if (pathNodes.c) { - pathShapeValue = pathData.inv ? this.solidPath + pathString : pathString; - } - viewData.elem.setAttribute('d', pathShapeValue); - } - viewData.lastPath = pathString; - } -}; - -MaskElement.prototype.destroy = function () { - this.element = null; - this.globalData = null; - this.maskElement = null; - this.data = null; - this.masksProperties = null; -}; - -const filtersFactory = (function () { - var ob = {}; - ob.createFilter = createFilter; - ob.createAlphaToLuminanceFilter = createAlphaToLuminanceFilter; - - function createFilter(filId, skipCoordinates) { - var fil = createNS('filter'); - fil.setAttribute('id', filId); - if (skipCoordinates !== true) { - fil.setAttribute('filterUnits', 'objectBoundingBox'); - fil.setAttribute('x', '0%'); - fil.setAttribute('y', '0%'); - fil.setAttribute('width', '100%'); - fil.setAttribute('height', '100%'); - } - return fil; - } - - function createAlphaToLuminanceFilter() { - var feColorMatrix = createNS('feColorMatrix'); - feColorMatrix.setAttribute('type', 'matrix'); - feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); - feColorMatrix.setAttribute('values', '0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1'); - return feColorMatrix; - } - - return ob; -}()); - -const featureSupport = (function () { - var ob = { - maskType: true, - }; - if (/MSIE 10/i.test(navigator.userAgent) || /MSIE 9/i.test(navigator.userAgent) || /rv:11.0/i.test(navigator.userAgent) || /Edge\/\d./i.test(navigator.userAgent)) { - ob.maskType = false; - } - return ob; -}()); - -var registeredEffects = {}; -var idPrefix = 'filter_result_'; - -function SVGEffects(elem) { - var i; - var source = 'SourceGraphic'; - var len = elem.data.ef ? elem.data.ef.length : 0; - var filId = createElementID(); - var fil = filtersFactory.createFilter(filId, true); - var count = 0; - this.filters = []; - var filterManager; - for (i = 0; i < len; i += 1) { - filterManager = null; - var type = elem.data.ef[i].ty; - if (registeredEffects[type]) { - var Effect = registeredEffects[type].effect; - filterManager = new Effect(fil, elem.effectsManager.effectElements[i], elem, idPrefix + count, source); - source = idPrefix + count; - if (registeredEffects[type].countsAsEffect) { - count += 1; + + function release(shapeCollection) { + var i; + var len = shapeCollection._length; + for (i = 0; i < len; i += 1) { + shapePool.release(shapeCollection.shapes[i]); + } + shapeCollection._length = 0; + + if (_length === _maxLength) { + pool = pooling.double(pool); + _maxLength *= 2; + } + pool[_length] = shapeCollection; + _length += 1; } - } - if (filterManager) { - this.filters.push(filterManager); - } - } - if (count) { - elem.globalData.defs.appendChild(fil); - elem.layerElement.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); - } - if (this.filters.length) { - elem.addRenderableComponent(this); - } -} - -SVGEffects.prototype.renderFrame = function (_isFirstFrame) { - var i; - var len = this.filters.length; - for (i = 0; i < len; i += 1) { - this.filters[i].renderFrame(_isFirstFrame); - } -}; - -function registerEffect(id, effect, countsAsEffect) { - registeredEffects[id] = { - effect, - countsAsEffect, - }; -} - -function SVGBaseElement() { -} - -SVGBaseElement.prototype = { - initRendererElement: function () { - this.layerElement = createNS('g'); - }, - createContainerElements: function () { - this.matteElement = createNS('g'); - this.transformedElement = this.layerElement; - this.maskedElement = this.layerElement; - this._sizeChanged = false; - var layerElementParent = null; - // If this layer acts as a mask for the following layer - var filId; - var fil; - var gg; - if (this.data.td) { - if (this.data.td == 3 || this.data.td == 1) { // eslint-disable-line eqeqeq - var masker = createNS('mask'); - masker.setAttribute('id', this.layerId); - masker.setAttribute('mask-type', this.data.td == 3 ? 'luminance' : 'alpha'); // eslint-disable-line eqeqeq - masker.appendChild(this.layerElement); - layerElementParent = masker; - this.globalData.defs.appendChild(masker); - // This is only for IE and Edge when mask if of type alpha - if (!featureSupport.maskType && this.data.td == 1) { // eslint-disable-line eqeqeq - masker.setAttribute('mask-type', 'luminance'); - filId = createElementID(); - fil = filtersFactory.createFilter(filId); - this.globalData.defs.appendChild(fil); - fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); - gg = createNS('g'); - gg.appendChild(this.layerElement); - layerElementParent = gg; - masker.appendChild(gg); - gg.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); - } - } else if (this.data.td == 2) { // eslint-disable-line eqeqeq - var maskGroup = createNS('mask'); - maskGroup.setAttribute('id', this.layerId); - maskGroup.setAttribute('mask-type', 'alpha'); - var maskGrouper = createNS('g'); - maskGroup.appendChild(maskGrouper); - filId = createElementID(); - fil = filtersFactory.createFilter(filId); - /// / - // This solution doesn't work on Android when meta tag with viewport attribute is set - /* var feColorMatrix = createNS('feColorMatrix'); - feColorMatrix.setAttribute('type', 'matrix'); - feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); - feColorMatrix.setAttribute('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 -1 1'); - fil.appendChild(feColorMatrix); */ - /// / - var feCTr = createNS('feComponentTransfer'); - feCTr.setAttribute('in', 'SourceGraphic'); - fil.appendChild(feCTr); - var feFunc = createNS('feFuncA'); - feFunc.setAttribute('type', 'table'); - feFunc.setAttribute('tableValues', '1.0 0.0'); - feCTr.appendChild(feFunc); - /// / - this.globalData.defs.appendChild(fil); - var alphaRect = createNS('rect'); - alphaRect.setAttribute('width', this.comp.data.w); - alphaRect.setAttribute('height', this.comp.data.h); - alphaRect.setAttribute('x', '0'); - alphaRect.setAttribute('y', '0'); - alphaRect.setAttribute('fill', '#ffffff'); - alphaRect.setAttribute('opacity', '0'); - maskGrouper.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); - maskGrouper.appendChild(alphaRect); - maskGrouper.appendChild(this.layerElement); - layerElementParent = maskGrouper; - if (!featureSupport.maskType) { - maskGroup.setAttribute('mask-type', 'luminance'); - fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); - gg = createNS('g'); - maskGrouper.appendChild(alphaRect); - gg.appendChild(this.layerElement); - layerElementParent = gg; - maskGrouper.appendChild(gg); - } - this.globalData.defs.appendChild(maskGroup); - } - } else if (this.data.tt) { - this.matteElement.appendChild(this.layerElement); - layerElementParent = this.matteElement; - this.baseElement = this.matteElement; - } else { - this.baseElement = this.layerElement; - } - if (this.data.ln) { - this.layerElement.setAttribute('id', this.data.ln); - } - if (this.data.cl) { - this.layerElement.setAttribute('class', this.data.cl); - } - // Clipping compositions to hide content that exceeds boundaries. If collapsed transformations is on, component should not be clipped - if (this.data.ty === 0 && !this.data.hd) { - var cp = createNS('clipPath'); - var pt = createNS('path'); - pt.setAttribute('d', 'M0,0 L' + this.data.w + ',0 L' + this.data.w + ',' + this.data.h + ' L0,' + this.data.h + 'z'); - var clipId = createElementID(); - cp.setAttribute('id', clipId); - cp.appendChild(pt); - this.globalData.defs.appendChild(cp); - - if (this.checkMasks()) { - var cpGroup = createNS('g'); - cpGroup.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); - cpGroup.appendChild(this.layerElement); - this.transformedElement = cpGroup; - if (layerElementParent) { - layerElementParent.appendChild(this.transformedElement); + return ob; + }()); + + const ShapePropertyFactory = (function () { + var initFrame = -999999; + + function interpolateShape(frameNum, previousValue, caching) { + var iterationIndex = caching.lastIndex; + var keyPropS; + var keyPropE; + var isHold; + var j; + var k; + var jLen; + var kLen; + var perc; + var vertexValue; + var kf = this.keyframes; + if (frameNum < kf[0].t - this.offsetTime) { + keyPropS = kf[0].s[0]; + isHold = true; + iterationIndex = 0; + } else if (frameNum >= kf[kf.length - 1].t - this.offsetTime) { + keyPropS = kf[kf.length - 1].s ? kf[kf.length - 1].s[0] : kf[kf.length - 2].e[0]; + /* if(kf[kf.length - 1].s){ + keyPropS = kf[kf.length - 1].s[0]; + }else{ + keyPropS = kf[kf.length - 2].e[0]; + } */ + isHold = true; } else { - this.baseElement = this.transformedElement; + var i = iterationIndex; + var len = kf.length - 1; + var flag = true; + var keyData; + var nextKeyData; + var keyframeMetadata; + while (flag) { + keyData = kf[i]; + nextKeyData = kf[i + 1]; + if ((nextKeyData.t - this.offsetTime) > frameNum) { + break; + } + if (i < len - 1) { + i += 1; + } else { + flag = false; + } + } + keyframeMetadata = this.keyframesMetadata[i] || {}; + isHold = keyData.h === 1; + iterationIndex = i; + if (!isHold) { + if (frameNum >= nextKeyData.t - this.offsetTime) { + perc = 1; + } else if (frameNum < keyData.t - this.offsetTime) { + perc = 0; + } else { + var fnc; + if (keyframeMetadata.__fnct) { + fnc = keyframeMetadata.__fnct; + } else { + fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y).get; + keyframeMetadata.__fnct = fnc; + } + perc = fnc((frameNum - (keyData.t - this.offsetTime)) / ((nextKeyData.t - this.offsetTime) - (keyData.t - this.offsetTime))); + } + keyPropE = nextKeyData.s ? nextKeyData.s[0] : keyData.e[0]; + } + keyPropS = keyData.s[0]; } - } else { - this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); - } - } - if (this.data.bm !== 0) { - this.setBlendMode(); - } - }, - renderElement: function () { - if (this.finalTransform._matMdf) { - this.transformedElement.setAttribute('transform', this.finalTransform.mat.to2dCSS()); - } - if (this.finalTransform._opMdf) { - this.transformedElement.setAttribute('opacity', this.finalTransform.mProp.o.v); - } - }, - destroyBaseElement: function () { - this.layerElement = null; - this.matteElement = null; - this.maskManager.destroy(); - }, - getBaseElement: function () { - if (this.data.hd) { - return null; - } - return this.baseElement; - }, - createRenderableComponents: function () { - this.maskManager = new MaskElement(this.data, this, this.globalData); - this.renderableEffectsManager = new SVGEffects(this); - }, - setMatte: function (id) { - if (!this.matteElement) { - return; - } - this.matteElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + id + ')'); - }, -}; - -/** - * @file - * Handles AE's layer parenting property. - * - */ - -function HierarchyElement() {} - -HierarchyElement.prototype = { - /** - * @function - * Initializes hierarchy properties - * - */ - initHierarchy: function () { - // element's parent list - this.hierarchy = []; - // if element is parent of another layer _isParent will be true - this._isParent = false; - this.checkParenting(); - }, - /** - * @function - * Sets layer's hierarchy. - * @param {array} hierarch - * layer's parent list - * - */ - setHierarchy: function (hierarchy) { - this.hierarchy = hierarchy; - }, - /** - * @function - * Sets layer as parent. - * - */ - setAsParent: function () { - this._isParent = true; - }, - /** - * @function - * Searches layer's parenting chain - * - */ - checkParenting: function () { - if (this.data.parent !== undefined) { - this.comp.buildElementParenting(this, this.data.parent, []); - } - }, -}; - -function RenderableDOMElement() {} + jLen = previousValue._length; + kLen = keyPropS.i[0].length; + caching.lastIndex = iterationIndex; -(function () { - var _prototype = { - initElement: function (data, globalData, comp) { - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.initTransform(data, globalData, comp); - this.initHierarchy(); - this.initRenderable(); - this.initRendererElement(); - this.createContainerElements(); - this.createRenderableComponents(); - this.createContent(); - this.hide(); - }, - hide: function () { - // console.log('HIDE', this); - if (!this.hidden && (!this.isInRange || this.isTransparent)) { - var elem = this.baseElement || this.layerElement; - elem.style.display = 'none'; - this.hidden = true; - } - }, - show: function () { - // console.log('SHOW', this); - if (this.isInRange && !this.isTransparent) { - if (!this.data.hd) { - var elem = this.baseElement || this.layerElement; - elem.style.display = 'block'; + for (j = 0; j < jLen; j += 1) { + for (k = 0; k < kLen; k += 1) { + vertexValue = isHold ? keyPropS.i[j][k] : keyPropS.i[j][k] + (keyPropE.i[j][k] - keyPropS.i[j][k]) * perc; + previousValue.i[j][k] = vertexValue; + vertexValue = isHold ? keyPropS.o[j][k] : keyPropS.o[j][k] + (keyPropE.o[j][k] - keyPropS.o[j][k]) * perc; + previousValue.o[j][k] = vertexValue; + vertexValue = isHold ? keyPropS.v[j][k] : keyPropS.v[j][k] + (keyPropE.v[j][k] - keyPropS.v[j][k]) * perc; + previousValue.v[j][k] = vertexValue; + } } - this.hidden = false; - this._isFirstFrame = true; } - }, - renderFrame: function () { - // If it is exported as hidden (data.hd === true) no need to render - // If it is not visible no need to render - if (this.data.hd || this.hidden) { - return; + + function interpolateShapeCurrentTime() { + var frameNum = this.comp.renderedFrame - this.offsetTime; + var initTime = this.keyframes[0].t - this.offsetTime; + var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; + var lastFrame = this._caching.lastFrame; + if (!(lastFrame !== initFrame && ((lastFrame < initTime && frameNum < initTime) || (lastFrame > endTime && frameNum > endTime)))) { + /// / + this._caching.lastIndex = lastFrame < frameNum ? this._caching.lastIndex : 0; + this.interpolateShape(frameNum, this.pv, this._caching); + /// / + } + this._caching.lastFrame = frameNum; + return this.pv; } - this.renderTransform(); - this.renderRenderable(); - this.renderElement(); - this.renderInnerContent(); - if (this._isFirstFrame) { - this._isFirstFrame = false; + + function resetShape() { + this.paths = this.localShapeCollection; } - }, - renderInnerContent: function () {}, - prepareFrame: function (num) { - this._mdf = false; - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - this.checkTransparency(); - }, - destroy: function () { - this.innerElem = null; - this.destroyBaseElement(); - }, - }; - extendPrototype([RenderableElement, createProxyFunction(_prototype)], RenderableDOMElement); -}()); - -function IImageElement(data, globalData, comp) { - this.assetData = globalData.getAssetData(data.refId); - this.initElement(data, globalData, comp); - this.sourceRect = { - top: 0, left: 0, width: this.assetData.w, height: this.assetData.h, - }; -} - -extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement], IImageElement); - -IImageElement.prototype.createContent = function () { - var assetPath = this.globalData.getAssetsPath(this.assetData); - - this.innerElem = createNS('image'); - this.innerElem.setAttribute('width', this.assetData.w + 'px'); - this.innerElem.setAttribute('height', this.assetData.h + 'px'); - this.innerElem.setAttribute('preserveAspectRatio', this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio); - this.innerElem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', assetPath); - - this.layerElement.appendChild(this.innerElem); -}; - -IImageElement.prototype.sourceRectAtTime = function () { - return this.sourceRect; -}; - -function ProcessedElement(element, position) { - this.elem = element; - this.pos = position; -} - -function IShapeElement() { -} - -IShapeElement.prototype = { - addShapeToModifiers: function (data) { - var i; - var len = this.shapeModifiers.length; - for (i = 0; i < len; i += 1) { - this.shapeModifiers[i].addShape(data); - } - }, - isShapeInAnimatedModifiers: function (data) { - var i = 0; - var len = this.shapeModifiers.length; - while (i < len) { - if (this.shapeModifiers[i].isAnimatedWithShape(data)) { + + function shapesEqual(shape1, shape2) { + if (shape1._length !== shape2._length || shape1.c !== shape2.c) { + return false; + } + var i; + var len = shape1._length; + for (i = 0; i < len; i += 1) { + if (shape1.v[i][0] !== shape2.v[i][0] + || shape1.v[i][1] !== shape2.v[i][1] + || shape1.o[i][0] !== shape2.o[i][0] + || shape1.o[i][1] !== shape2.o[i][1] + || shape1.i[i][0] !== shape2.i[i][0] + || shape1.i[i][1] !== shape2.i[i][1]) { + return false; + } + } return true; } - } - return false; - }, - renderModifiers: function () { - if (!this.shapeModifiers.length) { - return; - } - var i; - var len = this.shapes.length; - for (i = 0; i < len; i += 1) { - this.shapes[i].sh.reset(); - } - len = this.shapeModifiers.length; - var shouldBreakProcess; - for (i = len - 1; i >= 0; i -= 1) { - shouldBreakProcess = this.shapeModifiers[i].processShapes(this._isFirstFrame); - // workaround to fix cases where a repeater resets the shape so the following processes get called twice - // TODO: find a better solution for this - if (shouldBreakProcess) { - break; + function setVValue(newPath) { + if (!shapesEqual(this.v, newPath)) { + this.v = shapePool.clone(newPath); + this.localShapeCollection.releaseShapes(); + this.localShapeCollection.addShape(this.v); + this._mdf = true; + this.paths = this.localShapeCollection; + } } - } - }, - searchProcessedElement: function (elem) { - var elements = this.processedElements; - var i = 0; - var len = elements.length; - while (i < len) { - if (elements[i].elem === elem) { - return elements[i].pos; - } - i += 1; - } - return 0; - }, - addProcessedElement: function (elem, pos) { - var elements = this.processedElements; - var i = elements.length; - while (i) { - i -= 1; - if (elements[i].elem === elem) { - elements[i].pos = pos; - return; - } - } - elements.push(new ProcessedElement(elem, pos)); - }, - prepareFrame: function (num) { - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - }, -}; - -const lineCapEnum = { - 1: 'butt', - 2: 'round', - 3: 'square', -}; - -const lineJoinEnum = { - 1: 'miter', - 2: 'round', - 3: 'bevel', -}; - -function SVGShapeData(transformers, level, shape) { - this.caches = []; - this.styles = []; - this.transformers = transformers; - this.lStr = ''; - this.sh = shape; - this.lvl = level; - // TODO find if there are some cases where _isAnimated can be false. - // For now, since shapes add up with other shapes. They have to be calculated every time. - // One way of finding out is checking if all styles associated to this shape depend only of this shape - this._isAnimated = !!shape.k; - // TODO: commenting this for now since all shapes are animated - var i = 0; - var len = transformers.length; - while (i < len) { - if (transformers[i].mProps.dynamicProperties.length) { - this._isAnimated = true; - break; - } - i += 1; - } -} - -SVGShapeData.prototype.setAsAnimated = function () { - this._isAnimated = true; -}; - -function SVGStyleData(data, level) { - this.data = data; - this.type = data.ty; - this.d = ''; - this.lvl = level; - this._mdf = false; - this.closed = data.hd === true; - this.pElem = createNS('path'); - this.msElem = null; -} - -SVGStyleData.prototype.reset = function () { - this.d = ''; - this._mdf = false; -}; - -function DashProperty(elem, data, renderer, container) { - this.elem = elem; - this.frameId = -1; - this.dataProps = createSizedArray(data.length); - this.renderer = renderer; - this.k = false; - this.dashStr = ''; - this.dashArray = createTypedArray('float32', data.length ? data.length - 1 : 0); - this.dashoffset = createTypedArray('float32', 1); - this.initDynamicPropertyContainer(container); - var i; - var len = data.length || 0; - var prop; - for (i = 0; i < len; i += 1) { - prop = PropertyFactory.getProp(elem, data[i].v, 0, 0, this); - this.k = prop.k || this.k; - this.dataProps[i] = { n: data[i].n, p: prop }; - } - if (!this.k) { - this.getValue(true); - } - this._isAnimated = this.k; -} - -DashProperty.prototype.getValue = function (forceRender) { - if (this.elem.globalData.frameId === this.frameId && !forceRender) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - this._mdf = this._mdf || forceRender; - if (this._mdf) { - var i = 0; - var len = this.dataProps.length; - if (this.renderer === 'svg') { - this.dashStr = ''; - } - for (i = 0; i < len; i += 1) { - if (this.dataProps[i].n !== 'o') { - if (this.renderer === 'svg') { - this.dashStr += ' ' + this.dataProps[i].p.v; + function processEffectsSequence() { + if (this.elem.globalData.frameId === this.frameId) { + return; + } if (!this.effectsSequence.length) { + this._mdf = false; + return; + } + if (this.lock) { + this.setVValue(this.pv); + return; + } + this.lock = true; + this._mdf = false; + var finalValue; + if (this.kf) { + finalValue = this.pv; + } else if (this.data.ks) { + finalValue = this.data.ks.k; } else { - this.dashArray[i] = this.dataProps[i].p.v; + finalValue = this.data.pt.k; } - } else { - this.dashoffset[0] = this.dataProps[i].p.v; - } - } - } -}; -extendPrototype([DynamicPropertyContainer], DashProperty); - -function SVGStrokeStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); - this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); - this.d = new DashProperty(elem, data.d || {}, 'svg', this); - this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); - this.style = styleOb; - this._isAnimated = !!this._isAnimated; -} - -extendPrototype([DynamicPropertyContainer], SVGStrokeStyleData); - -function SVGFillStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); - this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); - this.style = styleOb; -} - -extendPrototype([DynamicPropertyContainer], SVGFillStyleData); - -function SVGNoStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.style = styleOb; -} - -extendPrototype([DynamicPropertyContainer], SVGNoStyleData); - -function GradientProperty(elem, data, container) { - this.data = data; - this.c = createTypedArray('uint8c', data.p * 4); - var cLength = data.k.k[0].s ? (data.k.k[0].s.length - data.p * 4) : data.k.k.length - data.p * 4; - this.o = createTypedArray('float32', cLength); - this._cmdf = false; - this._omdf = false; - this._collapsable = this.checkCollapsable(); - this._hasOpacity = cLength; - this.initDynamicPropertyContainer(container); - this.prop = PropertyFactory.getProp(elem, data.k, 1, null, this); - this.k = this.prop.k; - this.getValue(true); -} - -GradientProperty.prototype.comparePoints = function (values, points) { - var i = 0; - var len = this.o.length / 2; - var diff; - while (i < len) { - diff = Math.abs(values[i * 4] - values[points * 4 + i * 2]); - if (diff > 0.01) { - return false; - } - i += 1; - } - return true; -}; - -GradientProperty.prototype.checkCollapsable = function () { - if (this.o.length / 2 !== this.c.length / 4) { - return false; - } - if (this.data.k.k[0].s) { - var i = 0; - var len = this.data.k.k.length; - while (i < len) { - if (!this.comparePoints(this.data.k.k[i].s, this.data.p)) { - return false; - } - i += 1; - } - } else if (!this.comparePoints(this.data.k.k, this.data.p)) { - return false; - } - return true; -}; - -GradientProperty.prototype.getValue = function (forceRender) { - this.prop.getValue(); - this._mdf = false; - this._cmdf = false; - this._omdf = false; - if (this.prop._mdf || forceRender) { - var i; - var len = this.data.p * 4; - var mult; - var val; - for (i = 0; i < len; i += 1) { - mult = i % 4 === 0 ? 100 : 255; - val = Math.round(this.prop.v[i] * mult); - if (this.c[i] !== val) { - this.c[i] = val; - this._cmdf = !forceRender; + var i; + var len = this.effectsSequence.length; + for (i = 0; i < len; i += 1) { + finalValue = this.effectsSequence[i](finalValue); + } + this.setVValue(finalValue); + this.lock = false; + this.frameId = this.elem.globalData.frameId; } - } - if (this.o.length) { - len = this.prop.v.length; - for (i = this.data.p * 4; i < len; i += 1) { - mult = i % 2 === 0 ? 100 : 1; - val = i % 2 === 0 ? Math.round(this.prop.v[i] * 100) : this.prop.v[i]; - if (this.o[i - this.data.p * 4] !== val) { - this.o[i - this.data.p * 4] = val; - this._omdf = !forceRender; + + function ShapeProperty(elem, data, type) { + this.propType = 'shape'; + this.comp = elem.comp; + this.container = elem; + this.elem = elem; + this.data = data; + this.k = false; + this.kf = false; + this._mdf = false; + var pathData = type === 3 ? data.pt.k : data.ks.k; + this.v = shapePool.clone(pathData); + this.pv = shapePool.clone(this.v); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.paths = this.localShapeCollection; + this.paths.addShape(this.v); + this.reset = resetShape; + this.effectsSequence = []; + } + + function addEffect(effectFunction) { + this.effectsSequence.push(effectFunction); + this.container.addDynamicProperty(this); + } + + ShapeProperty.prototype.interpolateShape = interpolateShape; + ShapeProperty.prototype.getValue = processEffectsSequence; + ShapeProperty.prototype.setVValue = setVValue; + ShapeProperty.prototype.addEffect = addEffect; + + function KeyframedShapeProperty(elem, data, type) { + this.propType = 'shape'; + this.comp = elem.comp; + this.elem = elem; + this.container = elem; + this.offsetTime = elem.data.st; + this.keyframes = type === 3 ? data.pt.k : data.ks.k; + this.keyframesMetadata = []; + this.k = true; + this.kf = true; + var len = this.keyframes[0].s[0].i.length; + this.v = shapePool.newElement(); + this.v.setPathData(this.keyframes[0].s[0].c, len); + this.pv = shapePool.clone(this.v); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.paths = this.localShapeCollection; + this.paths.addShape(this.v); + this.lastFrame = initFrame; + this.reset = resetShape; + this._caching = { lastFrame: initFrame, lastIndex: 0 }; + this.effectsSequence = [interpolateShapeCurrentTime.bind(this)]; + } + KeyframedShapeProperty.prototype.getValue = processEffectsSequence; + KeyframedShapeProperty.prototype.interpolateShape = interpolateShape; + KeyframedShapeProperty.prototype.setVValue = setVValue; + KeyframedShapeProperty.prototype.addEffect = addEffect; + + var EllShapeProperty = (function () { + var cPoint = roundCorner; + + function EllShapePropertyFactory(elem, data) { + this.v = shapePool.newElement(); + this.v.setPathData(true, 4); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.paths = this.localShapeCollection; + this.localShapeCollection.addShape(this.v); + this.d = data.d; + this.elem = elem; + this.comp = elem.comp; + this.frameId = -1; + this.initDynamicPropertyContainer(elem); + this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); + this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.k = false; + this.convertEllToPath(); + } + } + + EllShapePropertyFactory.prototype = { + reset: resetShape, + getValue: function () { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + + if (this._mdf) { + this.convertEllToPath(); + } + }, + convertEllToPath: function () { + var p0 = this.p.v[0]; + var p1 = this.p.v[1]; + var s0 = this.s.v[0] / 2; + var s1 = this.s.v[1] / 2; + var _cw = this.d !== 3; + var _v = this.v; + _v.v[0][0] = p0; + _v.v[0][1] = p1 - s1; + _v.v[1][0] = _cw ? p0 + s0 : p0 - s0; + _v.v[1][1] = p1; + _v.v[2][0] = p0; + _v.v[2][1] = p1 + s1; + _v.v[3][0] = _cw ? p0 - s0 : p0 + s0; + _v.v[3][1] = p1; + _v.i[0][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; + _v.i[0][1] = p1 - s1; + _v.i[1][0] = _cw ? p0 + s0 : p0 - s0; + _v.i[1][1] = p1 - s1 * cPoint; + _v.i[2][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; + _v.i[2][1] = p1 + s1; + _v.i[3][0] = _cw ? p0 - s0 : p0 + s0; + _v.i[3][1] = p1 + s1 * cPoint; + _v.o[0][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; + _v.o[0][1] = p1 - s1; + _v.o[1][0] = _cw ? p0 + s0 : p0 - s0; + _v.o[1][1] = p1 + s1 * cPoint; + _v.o[2][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; + _v.o[2][1] = p1 + s1; + _v.o[3][0] = _cw ? p0 - s0 : p0 + s0; + _v.o[3][1] = p1 - s1 * cPoint; + }, + }; + + extendPrototype([DynamicPropertyContainer], EllShapePropertyFactory); + + return EllShapePropertyFactory; + }()); + + var StarShapeProperty = (function () { + function StarShapePropertyFactory(elem, data) { + this.v = shapePool.newElement(); + this.v.setPathData(true, 0); + this.elem = elem; + this.comp = elem.comp; + this.data = data; + this.frameId = -1; + this.d = data.d; + this.initDynamicPropertyContainer(elem); + if (data.sy === 1) { + this.ir = PropertyFactory.getProp(elem, data.ir, 0, 0, this); + this.is = PropertyFactory.getProp(elem, data.is, 0, 0.01, this); + this.convertToPath = this.convertStarToPath; + } else { + this.convertToPath = this.convertPolygonToPath; + } + this.pt = PropertyFactory.getProp(elem, data.pt, 0, 0, this); + this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); + this.r = PropertyFactory.getProp(elem, data.r, 0, degToRads, this); + this.or = PropertyFactory.getProp(elem, data.or, 0, 0, this); + this.os = PropertyFactory.getProp(elem, data.os, 0, 0.01, this); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.localShapeCollection.addShape(this.v); + this.paths = this.localShapeCollection; + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.k = false; + this.convertToPath(); + } + } + + StarShapePropertyFactory.prototype = { + reset: resetShape, + getValue: function () { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + if (this._mdf) { + this.convertToPath(); + } + }, + convertStarToPath: function () { + var numPts = Math.floor(this.pt.v) * 2; + var angle = (Math.PI * 2) / numPts; + /* this.v.v.length = numPts; + this.v.i.length = numPts; + this.v.o.length = numPts; */ + var longFlag = true; + var longRad = this.or.v; + var shortRad = this.ir.v; + var longRound = this.os.v; + var shortRound = this.is.v; + var longPerimSegment = (2 * Math.PI * longRad) / (numPts * 2); + var shortPerimSegment = (2 * Math.PI * shortRad) / (numPts * 2); + var i; + var rad; + var roundness; + var perimSegment; + var currentAng = -Math.PI / 2; + currentAng += this.r.v; + var dir = this.data.d === 3 ? -1 : 1; + this.v._length = 0; + for (i = 0; i < numPts; i += 1) { + rad = longFlag ? longRad : shortRad; + roundness = longFlag ? longRound : shortRound; + perimSegment = longFlag ? longPerimSegment : shortPerimSegment; + var x = rad * Math.cos(currentAng); + var y = rad * Math.sin(currentAng); + var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); + var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); + x += +this.p.v[0]; + y += +this.p.v[1]; + this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); + + /* this.v.v[i] = [x,y]; + this.v.i[i] = [x+ox*perimSegment*roundness*dir,y+oy*perimSegment*roundness*dir]; + this.v.o[i] = [x-ox*perimSegment*roundness*dir,y-oy*perimSegment*roundness*dir]; + this.v._length = numPts; */ + longFlag = !longFlag; + currentAng += angle * dir; + } + }, + convertPolygonToPath: function () { + var numPts = Math.floor(this.pt.v); + var angle = (Math.PI * 2) / numPts; + var rad = this.or.v; + var roundness = this.os.v; + var perimSegment = (2 * Math.PI * rad) / (numPts * 4); + var i; + var currentAng = -Math.PI * 0.5; + var dir = this.data.d === 3 ? -1 : 1; + currentAng += this.r.v; + this.v._length = 0; + for (i = 0; i < numPts; i += 1) { + var x = rad * Math.cos(currentAng); + var y = rad * Math.sin(currentAng); + var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); + var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); + x += +this.p.v[0]; + y += +this.p.v[1]; + this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); + currentAng += angle * dir; + } + this.paths.length = 0; + this.paths[0] = this.v; + }, + + }; + extendPrototype([DynamicPropertyContainer], StarShapePropertyFactory); + + return StarShapePropertyFactory; + }()); + + var RectShapeProperty = (function () { + function RectShapePropertyFactory(elem, data) { + this.v = shapePool.newElement(); + this.v.c = true; + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.localShapeCollection.addShape(this.v); + this.paths = this.localShapeCollection; + this.elem = elem; + this.comp = elem.comp; + this.frameId = -1; + this.d = data.d; + this.initDynamicPropertyContainer(elem); + this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); + this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); + this.r = PropertyFactory.getProp(elem, data.r, 0, 0, this); + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.k = false; + this.convertRectToPath(); + } + } + + RectShapePropertyFactory.prototype = { + convertRectToPath: function () { + var p0 = this.p.v[0]; + var p1 = this.p.v[1]; + var v0 = this.s.v[0] / 2; + var v1 = this.s.v[1] / 2; + var round = bmMin(v0, v1, this.r.v); + var cPoint = round * (1 - roundCorner); + this.v._length = 0; + + if (this.d === 2 || this.d === 1) { + this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, 0, true); + this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, p0 + v0, p1 + v1 - round, 1, true); + if (round !== 0) { + this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, 2, true); + this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0 + round, p1 + v1, 3, true); + this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, 4, true); + this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1 + round, 5, true); + this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, 6, true); + this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, p0 + v0 - round, p1 - v1, 7, true); + } else { + this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0, p1 + v1, 2); + this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1, 3); + } + } else { + this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, p0 + v0, p1 - v1 + round, 0, true); + if (round !== 0) { + this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, 1, true); + this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0 + round, p1 - v1, 2, true); + this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, 3, true); + this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1 - round, 4, true); + this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, 5, true); + this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0 - round, p1 + v1, 6, true); + this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, 7, true); + } else { + this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0, p1 - v1, 1, true); + this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1, 2, true); + this.v.setTripleAt(p0 + v0, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0, p1 + v1, 3, true); + } + } + }, + getValue: function () { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + if (this._mdf) { + this.convertRectToPath(); + } + }, + reset: resetShape, + }; + extendPrototype([DynamicPropertyContainer], RectShapePropertyFactory); + + return RectShapePropertyFactory; + }()); + + function getShapeProp(elem, data, type) { + var prop; + if (type === 3 || type === 4) { + var dataProp = type === 3 ? data.pt : data.ks; + var keys = dataProp.k; + if (keys.length) { + prop = new KeyframedShapeProperty(elem, data, type); + } else { + prop = new ShapeProperty(elem, data, type); + } + } else if (type === 5) { + prop = new RectShapeProperty(elem, data); + } else if (type === 6) { + prop = new EllShapeProperty(elem, data); + } else if (type === 7) { + prop = new StarShapeProperty(elem, data); + } + if (prop.k) { + elem.addDynamicProperty(prop); } + return prop; } - } - this._mdf = !forceRender; - } -}; - -extendPrototype([DynamicPropertyContainer], GradientProperty); - -function SVGGradientFillStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.initGradientData(elem, data, styleOb); -} - -SVGGradientFillStyleData.prototype.initGradientData = function (elem, data, styleOb) { - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); - this.s = PropertyFactory.getProp(elem, data.s, 1, null, this); - this.e = PropertyFactory.getProp(elem, data.e, 1, null, this); - this.h = PropertyFactory.getProp(elem, data.h || { k: 0 }, 0, 0.01, this); - this.a = PropertyFactory.getProp(elem, data.a || { k: 0 }, 0, degToRads, this); - this.g = new GradientProperty(elem, data.g, this); - this.style = styleOb; - this.stops = []; - this.setGradientData(styleOb.pElem, data); - this.setGradientOpacity(data, styleOb); - this._isAnimated = !!this._isAnimated; -}; - -SVGGradientFillStyleData.prototype.setGradientData = function (pathElement, data) { - var gradientId = createElementID(); - var gfill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); - gfill.setAttribute('id', gradientId); - gfill.setAttribute('spreadMethod', 'pad'); - gfill.setAttribute('gradientUnits', 'userSpaceOnUse'); - var stops = []; - var stop; - var j; - var jLen; - jLen = data.g.p * 4; - for (j = 0; j < jLen; j += 4) { - stop = createNS('stop'); - gfill.appendChild(stop); - stops.push(stop); - } - pathElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + gradientId + ')'); - this.gf = gfill; - this.cst = stops; -}; - -SVGGradientFillStyleData.prototype.setGradientOpacity = function (data, styleOb) { - if (this.g._hasOpacity && !this.g._collapsable) { - var stop; - var j; - var jLen; - var mask = createNS('mask'); - var maskElement = createNS('path'); - mask.appendChild(maskElement); - var opacityId = createElementID(); - var maskId = createElementID(); - mask.setAttribute('id', maskId); - var opFill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); - opFill.setAttribute('id', opacityId); - opFill.setAttribute('spreadMethod', 'pad'); - opFill.setAttribute('gradientUnits', 'userSpaceOnUse'); - jLen = data.g.k.k[0].s ? data.g.k.k[0].s.length : data.g.k.k.length; - var stops = this.stops; - for (j = data.g.p * 4; j < jLen; j += 2) { - stop = createNS('stop'); - stop.setAttribute('stop-color', 'rgb(255,255,255)'); - opFill.appendChild(stop); - stops.push(stop); - } - maskElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + opacityId + ')'); - if (data.ty === 'gs') { - maskElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); - maskElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); - if (data.lj === 1) { - maskElement.setAttribute('stroke-miterlimit', data.ml); + + function getConstructorFunction() { + return ShapeProperty; } - } - this.of = opFill; - this.ms = mask; - this.ost = stops; - this.maskId = maskId; - styleOb.msElem = maskElement; - } -}; - -extendPrototype([DynamicPropertyContainer], SVGGradientFillStyleData); - -function SVGGradientStrokeStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); - this.d = new DashProperty(elem, data.d || {}, 'svg', this); - this.initGradientData(elem, data, styleOb); - this._isAnimated = !!this._isAnimated; -} - -extendPrototype([SVGGradientFillStyleData, DynamicPropertyContainer], SVGGradientStrokeStyleData); - -function ShapeGroupData() { - this.it = []; - this.prevViewData = []; - this.gr = createNS('g'); -} - -function SVGTransformData(mProps, op, container) { - this.transform = { - mProps: mProps, - op: op, - container: container, - }; - this.elements = []; - this._isAnimated = this.transform.mProps.dynamicProperties.length || this.transform.op.effectsSequence.length; -} - -const buildShapeString = function (pathNodes, length, closed, mat) { - if (length === 0) { - return ''; - } - var _o = pathNodes.o; - var _i = pathNodes.i; - var _v = pathNodes.v; - var i; - var shapeString = ' M' + mat.applyToPointStringified(_v[0][0], _v[0][1]); - for (i = 1; i < length; i += 1) { - shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[i][0], _i[i][1]) + ' ' + mat.applyToPointStringified(_v[i][0], _v[i][1]); - } - if (closed && length) { - shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[0][0], _i[0][1]) + ' ' + mat.applyToPointStringified(_v[0][0], _v[0][1]); - shapeString += 'z'; - } - return shapeString; -}; - -const SVGElementsRenderer = (function () { - var _identityMatrix = new Matrix(); - var _matrixHelper = new Matrix(); - - var ob = { - createRenderFunction: createRenderFunction, - }; - - function createRenderFunction(data) { - switch (data.ty) { - case 'fl': - return renderFill; - case 'gf': - return renderGradient; - case 'gs': - return renderGradientStroke; - case 'st': - return renderStroke; - case 'sh': - case 'el': - case 'rc': - case 'sr': - return renderPath; - case 'tr': - return renderContentTransform; - case 'no': - return renderNoop; - default: - return null; - } - } - function renderContentTransform(styleData, itemData, isFirstFrame) { - if (isFirstFrame || itemData.transform.op._mdf) { - itemData.transform.container.setAttribute('opacity', itemData.transform.op.v); - } - if (isFirstFrame || itemData.transform.mProps._mdf) { - itemData.transform.container.setAttribute('transform', itemData.transform.mProps.v.to2dCSS()); - } - } - - function renderNoop() { - - } - - function renderPath(styleData, itemData, isFirstFrame) { - var j; - var jLen; - var pathStringTransformed; - var redraw; - var pathNodes; - var l; - var lLen = itemData.styles.length; - var lvl = itemData.lvl; - var paths; - var mat; - var props; - var iterations; - var k; - for (l = 0; l < lLen; l += 1) { - redraw = itemData.sh._mdf || isFirstFrame; - if (itemData.styles[l].lvl < lvl) { - mat = _matrixHelper.reset(); - iterations = lvl - itemData.styles[l].lvl; - k = itemData.transformers.length - 1; - while (!redraw && iterations > 0) { - redraw = itemData.transformers[k].mProps._mdf || redraw; - iterations -= 1; - k -= 1; - } - if (redraw) { - iterations = lvl - itemData.styles[l].lvl; - k = itemData.transformers.length - 1; - while (iterations > 0) { - props = itemData.transformers[k].mProps.v.props; - mat.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); - iterations -= 1; - k -= 1; + function getKeyframedConstructorFunction() { + return KeyframedShapeProperty; + } + + var ob = {}; + ob.getShapeProp = getShapeProp; + ob.getConstructorFunction = getConstructorFunction; + ob.getKeyframedConstructorFunction = getKeyframedConstructorFunction; + return ob; + }()); + + /*! + Transformation Matrix v2.0 + (c) Epistemex 2014-2015 + www.epistemex.com + By Ken Fyrstenberg + Contributions by leeoniya. + License: MIT, header required. + */ + + /** + * 2D transformation matrix object initialized with identity matrix. + * + * The matrix can synchronize a canvas context by supplying the context + * as an argument, or later apply current absolute transform to an + * existing context. + * + * All values are handled as floating point values. + * + * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix + * @prop {number} a - scale x + * @prop {number} b - shear y + * @prop {number} c - shear x + * @prop {number} d - scale y + * @prop {number} e - translate x + * @prop {number} f - translate y + * @prop {CanvasRenderingContext2D|null} [context=null] - set or get current canvas context + * @constructor + */ + + const Matrix = (function () { + var _cos = Math.cos; + var _sin = Math.sin; + var _tan = Math.tan; + var _rnd = Math.round; + + function reset() { + this.props[0] = 1; + this.props[1] = 0; + this.props[2] = 0; + this.props[3] = 0; + this.props[4] = 0; + this.props[5] = 1; + this.props[6] = 0; + this.props[7] = 0; + this.props[8] = 0; + this.props[9] = 0; + this.props[10] = 1; + this.props[11] = 0; + this.props[12] = 0; + this.props[13] = 0; + this.props[14] = 0; + this.props[15] = 1; + return this; + } + + function rotate(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + function rotateX(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(1, 0, 0, 0, 0, mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1); + } + + function rotateY(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, 0, mSin, 0, 0, 1, 0, 0, -mSin, 0, mCos, 0, 0, 0, 0, 1); + } + + function rotateZ(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + function shear(sx, sy) { + return this._t(1, sy, sx, 1, 0, 0); + } + + function skew(ax, ay) { + return this.shear(_tan(ax), _tan(ay)); + } + + function skewFromAxis(ax, angle) { + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, mSin, 0, 0, -mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) + ._t(1, 0, 0, 0, _tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) + ._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + // return this._t(mCos, mSin, -mSin, mCos, 0, 0)._t(1, 0, _tan(ax), 1, 0, 0)._t(mCos, -mSin, mSin, mCos, 0, 0); + } + + function scale(sx, sy, sz) { + if (!sz && sz !== 0) { + sz = 1; + } + if (sx === 1 && sy === 1 && sz === 1) { + return this; + } + return this._t(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1); + } + + function setTransform(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { + this.props[0] = a; + this.props[1] = b; + this.props[2] = c; + this.props[3] = d; + this.props[4] = e; + this.props[5] = f; + this.props[6] = g; + this.props[7] = h; + this.props[8] = i; + this.props[9] = j; + this.props[10] = k; + this.props[11] = l; + this.props[12] = m; + this.props[13] = n; + this.props[14] = o; + this.props[15] = p; + return this; + } + + function translate(tx, ty, tz) { + tz = tz || 0; + if (tx !== 0 || ty !== 0 || tz !== 0) { + return this._t(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1); + } + return this; + } + + function transform(a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2) { + var _p = this.props; + + if (a2 === 1 && b2 === 0 && c2 === 0 && d2 === 0 && e2 === 0 && f2 === 1 && g2 === 0 && h2 === 0 && i2 === 0 && j2 === 0 && k2 === 1 && l2 === 0) { + // NOTE: commenting this condition because TurboFan deoptimizes code when present + // if(m2 !== 0 || n2 !== 0 || o2 !== 0){ + _p[12] = _p[12] * a2 + _p[15] * m2; + _p[13] = _p[13] * f2 + _p[15] * n2; + _p[14] = _p[14] * k2 + _p[15] * o2; + _p[15] *= p2; + // } + this._identityCalculated = false; + return this; + } + + var a1 = _p[0]; + var b1 = _p[1]; + var c1 = _p[2]; + var d1 = _p[3]; + var e1 = _p[4]; + var f1 = _p[5]; + var g1 = _p[6]; + var h1 = _p[7]; + var i1 = _p[8]; + var j1 = _p[9]; + var k1 = _p[10]; + var l1 = _p[11]; + var m1 = _p[12]; + var n1 = _p[13]; + var o1 = _p[14]; + var p1 = _p[15]; + + /* matrix order (canvas compatible): + * ace + * bdf + * 001 + */ + _p[0] = a1 * a2 + b1 * e2 + c1 * i2 + d1 * m2; + _p[1] = a1 * b2 + b1 * f2 + c1 * j2 + d1 * n2; + _p[2] = a1 * c2 + b1 * g2 + c1 * k2 + d1 * o2; + _p[3] = a1 * d2 + b1 * h2 + c1 * l2 + d1 * p2; + + _p[4] = e1 * a2 + f1 * e2 + g1 * i2 + h1 * m2; + _p[5] = e1 * b2 + f1 * f2 + g1 * j2 + h1 * n2; + _p[6] = e1 * c2 + f1 * g2 + g1 * k2 + h1 * o2; + _p[7] = e1 * d2 + f1 * h2 + g1 * l2 + h1 * p2; + + _p[8] = i1 * a2 + j1 * e2 + k1 * i2 + l1 * m2; + _p[9] = i1 * b2 + j1 * f2 + k1 * j2 + l1 * n2; + _p[10] = i1 * c2 + j1 * g2 + k1 * k2 + l1 * o2; + _p[11] = i1 * d2 + j1 * h2 + k1 * l2 + l1 * p2; + + _p[12] = m1 * a2 + n1 * e2 + o1 * i2 + p1 * m2; + _p[13] = m1 * b2 + n1 * f2 + o1 * j2 + p1 * n2; + _p[14] = m1 * c2 + n1 * g2 + o1 * k2 + p1 * o2; + _p[15] = m1 * d2 + n1 * h2 + o1 * l2 + p1 * p2; + + this._identityCalculated = false; + return this; + } + + function isIdentity() { + if (!this._identityCalculated) { + this._identity = !(this.props[0] !== 1 || this.props[1] !== 0 || this.props[2] !== 0 || this.props[3] !== 0 || this.props[4] !== 0 || this.props[5] !== 1 || this.props[6] !== 0 || this.props[7] !== 0 || this.props[8] !== 0 || this.props[9] !== 0 || this.props[10] !== 1 || this.props[11] !== 0 || this.props[12] !== 0 || this.props[13] !== 0 || this.props[14] !== 0 || this.props[15] !== 1); + this._identityCalculated = true; + } + return this._identity; + } + + function equals(matr) { + var i = 0; + while (i < 16) { + if (matr.props[i] !== this.props[i]) { + return false; } + i += 1; } - } else { - mat = _identityMatrix; + return true; } - paths = itemData.sh.paths; - jLen = paths._length; - if (redraw) { - pathStringTransformed = ''; - for (j = 0; j < jLen; j += 1) { - pathNodes = paths.shapes[j]; - if (pathNodes && pathNodes._length) { - pathStringTransformed += buildShapeString(pathNodes, pathNodes._length, pathNodes.c, mat); - } + + function clone(matr) { + var i; + for (i = 0; i < 16; i += 1) { + matr.props[i] = this.props[i]; } - itemData.caches[l] = pathStringTransformed; - } else { - pathStringTransformed = itemData.caches[l]; + return matr; } - itemData.styles[l].d += styleData.hd === true ? '' : pathStringTransformed; - itemData.styles[l]._mdf = redraw || itemData.styles[l]._mdf; - } - } - function renderFill(styleData, itemData, isFirstFrame) { - var styleElem = itemData.style; + function cloneFromProps(props) { + var i; + for (i = 0; i < 16; i += 1) { + this.props[i] = props[i]; + } + } - if (itemData.c._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('fill', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); - } - if (itemData.o._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('fill-opacity', itemData.o.v); - } - } - - function renderGradientStroke(styleData, itemData, isFirstFrame) { - renderGradient(styleData, itemData, isFirstFrame); - renderStroke(styleData, itemData, isFirstFrame); - } - - function renderGradient(styleData, itemData, isFirstFrame) { - var gfill = itemData.gf; - var hasOpacity = itemData.g._hasOpacity; - var pt1 = itemData.s.v; - var pt2 = itemData.e.v; - - if (itemData.o._mdf || isFirstFrame) { - var attr = styleData.ty === 'gf' ? 'fill-opacity' : 'stroke-opacity'; - itemData.style.pElem.setAttribute(attr, itemData.o.v); - } - if (itemData.s._mdf || isFirstFrame) { - var attr1 = styleData.t === 1 ? 'x1' : 'cx'; - var attr2 = attr1 === 'x1' ? 'y1' : 'cy'; - gfill.setAttribute(attr1, pt1[0]); - gfill.setAttribute(attr2, pt1[1]); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute(attr1, pt1[0]); - itemData.of.setAttribute(attr2, pt1[1]); + function applyToPoint(x, y, z) { + return { + x: x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], + y: x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], + z: x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], + }; + /* return { + x: x * me.a + y * me.c + me.e, + y: x * me.b + y * me.d + me.f + }; */ } - } - var stops; - var i; - var len; - var stop; - if (itemData.g._cmdf || isFirstFrame) { - stops = itemData.cst; - var cValues = itemData.g.c; - len = stops.length; - for (i = 0; i < len; i += 1) { - stop = stops[i]; - stop.setAttribute('offset', cValues[i * 4] + '%'); - stop.setAttribute('stop-color', 'rgb(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ')'); + function applyToX(x, y, z) { + return x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12]; } - } - if (hasOpacity && (itemData.g._omdf || isFirstFrame)) { - var oValues = itemData.g.o; - if (itemData.g._collapsable) { - stops = itemData.cst; - } else { - stops = itemData.ost; + function applyToY(x, y, z) { + return x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13]; } - len = stops.length; - for (i = 0; i < len; i += 1) { - stop = stops[i]; - if (!itemData.g._collapsable) { - stop.setAttribute('offset', oValues[i * 2] + '%'); - } - stop.setAttribute('stop-opacity', oValues[i * 2 + 1]); + function applyToZ(x, y, z) { + return x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14]; } - } - if (styleData.t === 1) { - if (itemData.e._mdf || isFirstFrame) { - gfill.setAttribute('x2', pt2[0]); - gfill.setAttribute('y2', pt2[1]); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute('x2', pt2[0]); - itemData.of.setAttribute('y2', pt2[1]); - } - } - } else { - var rad; - if (itemData.s._mdf || itemData.e._mdf || isFirstFrame) { - rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); - gfill.setAttribute('r', rad); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute('r', rad); - } - } - if (itemData.e._mdf || itemData.h._mdf || itemData.a._mdf || isFirstFrame) { - if (!rad) { - rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); - } - var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); - - var percent = itemData.h.v; - if (percent >= 1) { - percent = 0.99; - } else if (percent <= -1) { - percent = -0.99; - } - var dist = rad * percent; - var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; - var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; - gfill.setAttribute('fx', x); - gfill.setAttribute('fy', y); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute('fx', x); - itemData.of.setAttribute('fy', y); - } - } - // gfill.setAttribute('fy','200'); - } - } - - function renderStroke(styleData, itemData, isFirstFrame) { - var styleElem = itemData.style; - var d = itemData.d; - if (d && (d._mdf || isFirstFrame) && d.dashStr) { - styleElem.pElem.setAttribute('stroke-dasharray', d.dashStr); - styleElem.pElem.setAttribute('stroke-dashoffset', d.dashoffset[0]); - } - if (itemData.c && (itemData.c._mdf || isFirstFrame)) { - styleElem.pElem.setAttribute('stroke', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); - } - if (itemData.o._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('stroke-opacity', itemData.o.v); - } - if (itemData.w._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('stroke-width', itemData.w.v); - if (styleElem.msElem) { - styleElem.msElem.setAttribute('stroke-width', itemData.w.v); + + function getInverseMatrix() { + var determinant = this.props[0] * this.props[5] - this.props[1] * this.props[4]; + var a = this.props[5] / determinant; + var b = -this.props[1] / determinant; + var c = -this.props[4] / determinant; + var d = this.props[0] / determinant; + var e = (this.props[4] * this.props[13] - this.props[5] * this.props[12]) / determinant; + var f = -(this.props[0] * this.props[13] - this.props[1] * this.props[12]) / determinant; + var inverseMatrix = new Matrix(); + inverseMatrix.props[0] = a; + inverseMatrix.props[1] = b; + inverseMatrix.props[4] = c; + inverseMatrix.props[5] = d; + inverseMatrix.props[12] = e; + inverseMatrix.props[13] = f; + return inverseMatrix; } - } - } - - return ob; -}()); - -function SVGShapeElement(data, globalData, comp) { - // List of drawable elements - this.shapes = []; - // Full shape data - this.shapesData = data.shapes; - // List of styles that will be applied to shapes - this.stylesList = []; - // List of modifiers that will be applied to shapes - this.shapeModifiers = []; - // List of items in shape tree - this.itemsData = []; - // List of items in previous shape tree - this.processedElements = []; - // List of animated components - this.animatedContents = []; - this.initElement(data, globalData, comp); - // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. - // List of elements that have been created - this.prevViewData = []; - // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. -} - -extendPrototype([BaseElement, TransformElement, SVGBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableDOMElement], SVGShapeElement); - -SVGShapeElement.prototype.initSecondaryElement = function () { -}; - -SVGShapeElement.prototype.identityMatrix = new Matrix(); - -SVGShapeElement.prototype.buildExpressionInterface = function () {}; - -SVGShapeElement.prototype.createContent = function () { - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); - this.filterUniqueShapes(); -}; - -/* -This method searches for multiple shapes that affect a single element and one of them is animated -*/ -SVGShapeElement.prototype.filterUniqueShapes = function () { - var i; - var len = this.shapes.length; - var shape; - var j; - var jLen = this.stylesList.length; - var style; - var tempShapes = []; - var areAnimated = false; - for (j = 0; j < jLen; j += 1) { - style = this.stylesList[j]; - areAnimated = false; - tempShapes.length = 0; - for (i = 0; i < len; i += 1) { - shape = this.shapes[i]; - if (shape.styles.indexOf(style) !== -1) { - tempShapes.push(shape); - areAnimated = shape._isAnimated || areAnimated; + + function inversePoint(pt) { + var inverseMatrix = this.getInverseMatrix(); + return inverseMatrix.applyToPointArray(pt[0], pt[1], pt[2] || 0); } - } - if (tempShapes.length > 1 && areAnimated) { - this.setShapesAsAnimated(tempShapes); - } - } -}; - -SVGShapeElement.prototype.setShapesAsAnimated = function (shapes) { - var i; - var len = shapes.length; - for (i = 0; i < len; i += 1) { - shapes[i].setAsAnimated(); - } -}; - -SVGShapeElement.prototype.createStyleElement = function (data, level) { - // TODO: prevent drawing of hidden styles - var elementData; - var styleOb = new SVGStyleData(data, level); - - var pathElement = styleOb.pElem; - if (data.ty === 'st') { - elementData = new SVGStrokeStyleData(this, data, styleOb); - } else if (data.ty === 'fl') { - elementData = new SVGFillStyleData(this, data, styleOb); - } else if (data.ty === 'gf' || data.ty === 'gs') { - var GradientConstructor = data.ty === 'gf' ? SVGGradientFillStyleData : SVGGradientStrokeStyleData; - elementData = new GradientConstructor(this, data, styleOb); - this.globalData.defs.appendChild(elementData.gf); - if (elementData.maskId) { - this.globalData.defs.appendChild(elementData.ms); - this.globalData.defs.appendChild(elementData.of); - pathElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + elementData.maskId + ')'); - } - } else if (data.ty === 'no') { - elementData = new SVGNoStyleData(this, data, styleOb); - } - - if (data.ty === 'st' || data.ty === 'gs') { - pathElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); - pathElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); - pathElement.setAttribute('fill-opacity', '0'); - if (data.lj === 1) { - pathElement.setAttribute('stroke-miterlimit', data.ml); - } - } - - if (data.r === 2) { - pathElement.setAttribute('fill-rule', 'evenodd'); - } - - if (data.ln) { - pathElement.setAttribute('id', data.ln); - } - if (data.cl) { - pathElement.setAttribute('class', data.cl); - } - if (data.bm) { - pathElement.style['mix-blend-mode'] = getBlendMode(data.bm); - } - this.stylesList.push(styleOb); - this.addToAnimatedContents(data, elementData); - return elementData; -}; - -SVGShapeElement.prototype.createGroupElement = function (data) { - var elementData = new ShapeGroupData(); - if (data.ln) { - elementData.gr.setAttribute('id', data.ln); - } - if (data.cl) { - elementData.gr.setAttribute('class', data.cl); - } - if (data.bm) { - elementData.gr.style['mix-blend-mode'] = getBlendMode(data.bm); - } - return elementData; -}; - -SVGShapeElement.prototype.createTransformElement = function (data, container) { - var transformProperty = TransformPropertyFactory.getTransformProperty(this, data, this); - var elementData = new SVGTransformData(transformProperty, transformProperty.o, container); - this.addToAnimatedContents(data, elementData); - return elementData; -}; - -SVGShapeElement.prototype.createShapeElement = function (data, ownTransformers, level) { - var ty = 4; - if (data.ty === 'rc') { - ty = 5; - } else if (data.ty === 'el') { - ty = 6; - } else if (data.ty === 'sr') { - ty = 7; - } - var shapeProperty = ShapePropertyFactory.getShapeProp(this, data, ty, this); - var elementData = new SVGShapeData(ownTransformers, level, shapeProperty); - this.shapes.push(elementData); - this.addShapeToModifiers(elementData); - this.addToAnimatedContents(data, elementData); - return elementData; -}; - -SVGShapeElement.prototype.addToAnimatedContents = function (data, element) { - var i = 0; - var len = this.animatedContents.length; - while (i < len) { - if (this.animatedContents[i].element === element) { - return; - } - i += 1; - } - this.animatedContents.push({ - fn: SVGElementsRenderer.createRenderFunction(data), - element: element, - data: data, - }); -}; - -SVGShapeElement.prototype.setElementStyles = function (elementData) { - var arr = elementData.styles; - var j; - var jLen = this.stylesList.length; - for (j = 0; j < jLen; j += 1) { - if (!this.stylesList[j].closed) { - arr.push(this.stylesList[j]); - } - } -}; - -SVGShapeElement.prototype.reloadShapes = function () { - this._isFirstFrame = true; - var i; - var len = this.itemsData.length; - for (i = 0; i < len; i += 1) { - this.prevViewData[i] = this.itemsData[i]; - } - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); - this.filterUniqueShapes(); - len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - this.dynamicProperties[i].getValue(); - } - this.renderModifiers(); -}; - -SVGShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, container, level, transformers, render) { - var ownTransformers = [].concat(transformers); - var i; - var len = arr.length - 1; - var j; - var jLen; - var ownStyles = []; - var ownModifiers = []; - var currentTransform; - var modifier; - var processedPos; - for (i = len; i >= 0; i -= 1) { - processedPos = this.searchProcessedElement(arr[i]); - if (!processedPos) { - arr[i]._render = render; - } else { - itemsData[i] = prevViewData[processedPos - 1]; - } - if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs' || arr[i].ty === 'no') { - if (!processedPos) { - itemsData[i] = this.createStyleElement(arr[i], level); - } else { - itemsData[i].style.closed = false; + + function inversePoints(pts) { + var i; + var len = pts.length; + var retPts = []; + for (i = 0; i < len; i += 1) { + retPts[i] = inversePoint(pts[i]); + } + return retPts; } - if (arr[i]._render) { - if (itemsData[i].style.pElem.parentNode !== container) { - container.appendChild(itemsData[i].style.pElem); + + function applyToTriplePoints(pt1, pt2, pt3) { + var arr = createTypedArray('float32', 6); + if (this.isIdentity()) { + arr[0] = pt1[0]; + arr[1] = pt1[1]; + arr[2] = pt2[0]; + arr[3] = pt2[1]; + arr[4] = pt3[0]; + arr[5] = pt3[1]; + } else { + var p0 = this.props[0]; + var p1 = this.props[1]; + var p4 = this.props[4]; + var p5 = this.props[5]; + var p12 = this.props[12]; + var p13 = this.props[13]; + arr[0] = pt1[0] * p0 + pt1[1] * p4 + p12; + arr[1] = pt1[0] * p1 + pt1[1] * p5 + p13; + arr[2] = pt2[0] * p0 + pt2[1] * p4 + p12; + arr[3] = pt2[0] * p1 + pt2[1] * p5 + p13; + arr[4] = pt3[0] * p0 + pt3[1] * p4 + p12; + arr[5] = pt3[0] * p1 + pt3[1] * p5 + p13; } + return arr; } - ownStyles.push(itemsData[i].style); - } else if (arr[i].ty === 'gr') { - if (!processedPos) { - itemsData[i] = this.createGroupElement(arr[i]); - } else { - jLen = itemsData[i].it.length; - for (j = 0; j < jLen; j += 1) { - itemsData[i].prevViewData[j] = itemsData[i].it[j]; + + function applyToPointArray(x, y, z) { + var arr; + if (this.isIdentity()) { + arr = [x, y, z]; + } else { + arr = [ + x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], + x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], + x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], + ]; } + return arr; } - this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, itemsData[i].gr, level + 1, ownTransformers, render); - if (arr[i]._render) { - if (itemsData[i].gr.parentNode !== container) { - container.appendChild(itemsData[i].gr); + + function applyToPointStringified(x, y) { + if (this.isIdentity()) { + return x + ',' + y; } + var _p = this.props; + return Math.round((x * _p[0] + y * _p[4] + _p[12]) * 100) / 100 + ',' + Math.round((x * _p[1] + y * _p[5] + _p[13]) * 100) / 100; } - } else if (arr[i].ty === 'tr') { - if (!processedPos) { - itemsData[i] = this.createTransformElement(arr[i], container); + + function toCSS() { + // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. + /* if(this.isIdentity()) { + return ''; + } */ + var i = 0; + var props = this.props; + var cssValue = 'matrix3d('; + var v = 10000; + while (i < 16) { + cssValue += _rnd(props[i] * v) / v; + cssValue += i === 15 ? ')' : ','; + i += 1; + } + return cssValue; } - currentTransform = itemsData[i].transform; - ownTransformers.push(currentTransform); - } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { - if (!processedPos) { - itemsData[i] = this.createShapeElement(arr[i], ownTransformers, level); + + function roundMatrixProperty(val) { + var v = 10000; + if ((val < 0.000001 && val > 0) || (val > -0.000001 && val < 0)) { + return _rnd(val * v) / v; + } + return val; } - this.setElementStyles(itemsData[i]); - } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'ms' || arr[i].ty === 'pb') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - modifier.init(this, arr[i]); - itemsData[i] = modifier; - this.shapeModifiers.push(modifier); - } else { - modifier = itemsData[i]; - modifier.closed = false; - } - ownModifiers.push(modifier); - } else if (arr[i].ty === 'rp') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - itemsData[i] = modifier; - modifier.init(this, arr, i, itemsData); - this.shapeModifiers.push(modifier); - render = false; + + function to2dCSS() { + // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. + /* if(this.isIdentity()) { + return ''; + } */ + var props = this.props; + var _a = roundMatrixProperty(props[0]); + var _b = roundMatrixProperty(props[1]); + var _c = roundMatrixProperty(props[4]); + var _d = roundMatrixProperty(props[5]); + var _e = roundMatrixProperty(props[12]); + var _f = roundMatrixProperty(props[13]); + return 'matrix(' + _a + ',' + _b + ',' + _c + ',' + _d + ',' + _e + ',' + _f + ')'; + } + + return function () { + this.reset = reset; + this.rotate = rotate; + this.rotateX = rotateX; + this.rotateY = rotateY; + this.rotateZ = rotateZ; + this.skew = skew; + this.skewFromAxis = skewFromAxis; + this.shear = shear; + this.scale = scale; + this.setTransform = setTransform; + this.translate = translate; + this.transform = transform; + this.applyToPoint = applyToPoint; + this.applyToX = applyToX; + this.applyToY = applyToY; + this.applyToZ = applyToZ; + this.applyToPointArray = applyToPointArray; + this.applyToTriplePoints = applyToTriplePoints; + this.applyToPointStringified = applyToPointStringified; + this.toCSS = toCSS; + this.to2dCSS = to2dCSS; + this.clone = clone; + this.cloneFromProps = cloneFromProps; + this.equals = equals; + this.inversePoints = inversePoints; + this.inversePoint = inversePoint; + this.getInverseMatrix = getInverseMatrix; + this._t = this.transform; + this.isIdentity = isIdentity; + this._identity = true; + this._identityCalculated = false; + + this.props = createTypedArray('float32', 16); + this.reset(); + }; + }()); + + const lottie = {}; + var standalone = '__[STANDALONE]__'; + var animationData = '__[ANIMATIONDATA]__'; + var renderer = ''; + + function setLocation(href) { + setLocationHref(href); + } + + function searchAnimations() { + if (standalone === true) { + animationManager.searchAnimations(animationData, standalone, renderer); } else { - modifier = itemsData[i]; - modifier.closed = true; + animationManager.searchAnimations(); } - ownModifiers.push(modifier); } - this.addProcessedElement(arr[i], i + 1); - } - len = ownStyles.length; - for (i = 0; i < len; i += 1) { - ownStyles[i].closed = true; - } - len = ownModifiers.length; - for (i = 0; i < len; i += 1) { - ownModifiers[i].closed = true; - } -}; - -SVGShapeElement.prototype.renderInnerContent = function () { - this.renderModifiers(); - var i; - var len = this.stylesList.length; - for (i = 0; i < len; i += 1) { - this.stylesList[i].reset(); - } - this.renderShape(); - for (i = 0; i < len; i += 1) { - if (this.stylesList[i]._mdf || this._isFirstFrame) { - if (this.stylesList[i].msElem) { - this.stylesList[i].msElem.setAttribute('d', this.stylesList[i].d); - // Adding M0 0 fixes same mask bug on all browsers - this.stylesList[i].d = 'M0 0' + this.stylesList[i].d; - } - this.stylesList[i].pElem.setAttribute('d', this.stylesList[i].d || 'M0 0'); - } - } -}; - -SVGShapeElement.prototype.renderShape = function () { - var i; - var len = this.animatedContents.length; - var animatedContent; - for (i = 0; i < len; i += 1) { - animatedContent = this.animatedContents[i]; - if ((this._isFirstFrame || animatedContent.element._isAnimated) && animatedContent.data !== true) { - animatedContent.fn(animatedContent.data, animatedContent.element, this._isFirstFrame); - } - } -}; - -SVGShapeElement.prototype.destroy = function () { - this.destroyBaseElement(); - this.shapesData = null; - this.itemsData = null; -}; - -function LetterProps(o, sw, sc, fc, m, p) { - this.o = o; - this.sw = sw; - this.sc = sc; - this.fc = fc; - this.m = m; - this.p = p; - this._mdf = { - o: true, - sw: !!sw, - sc: !!sc, - fc: !!fc, - m: true, - p: true, - }; -} - -LetterProps.prototype.update = function (o, sw, sc, fc, m, p) { - this._mdf.o = false; - this._mdf.sw = false; - this._mdf.sc = false; - this._mdf.fc = false; - this._mdf.m = false; - this._mdf.p = false; - var updated = false; - - if (this.o !== o) { - this.o = o; - this._mdf.o = true; - updated = true; - } - if (this.sw !== sw) { - this.sw = sw; - this._mdf.sw = true; - updated = true; - } - if (this.sc !== sc) { - this.sc = sc; - this._mdf.sc = true; - updated = true; - } - if (this.fc !== fc) { - this.fc = fc; - this._mdf.fc = true; - updated = true; - } - if (this.m !== m) { - this.m = m; - this._mdf.m = true; - updated = true; - } - if (p.length && (this.p[0] !== p[0] || this.p[1] !== p[1] || this.p[4] !== p[4] || this.p[5] !== p[5] || this.p[12] !== p[12] || this.p[13] !== p[13])) { - this.p = p; - this._mdf.p = true; - updated = true; - } - return updated; -}; - -function TextProperty(elem, data) { - this._frameId = initialDefaultFrame; - this.pv = ''; - this.v = ''; - this.kf = false; - this._isFirstFrame = true; - this._mdf = false; - this.data = data; - this.elem = elem; - this.comp = this.elem.comp; - this.keysIndex = 0; - this.canResize = false; - this.minimumFontSize = 1; - this.effectsSequence = []; - this.currentData = { - ascent: 0, - boxWidth: this.defaultBoxWidth, - f: '', - fStyle: '', - fWeight: '', - fc: '', - j: '', - justifyOffset: '', - l: [], - lh: 0, - lineWidths: [], - ls: '', - of: '', - s: '', - sc: '', - sw: 0, - t: 0, - tr: 0, - sz: 0, - ps: null, - fillColorAnim: false, - strokeColorAnim: false, - strokeWidthAnim: false, - yOffset: 0, - finalSize: 0, - finalText: [], - finalLineHeight: 0, - __complete: false, - - }; - this.copyData(this.currentData, this.data.d.k[0].s); - - if (!this.searchProperty()) { - this.completeTextData(this.currentData); - } -} - -TextProperty.prototype.defaultBoxWidth = [0, 0]; - -TextProperty.prototype.copyData = function (obj, data) { - for (var s in data) { - if (Object.prototype.hasOwnProperty.call(data, s)) { - obj[s] = data[s]; + + function setSubframeRendering(flag) { + setSubframeEnabled(flag); } - } - return obj; -}; - -TextProperty.prototype.setCurrentData = function (data) { - if (!data.__complete) { - this.completeTextData(data); - } - this.currentData = data; - this.currentData.boxWidth = this.currentData.boxWidth || this.defaultBoxWidth; - this._mdf = true; -}; - -TextProperty.prototype.searchProperty = function () { - return this.searchKeyframes(); -}; - -TextProperty.prototype.searchKeyframes = function () { - this.kf = this.data.d.k.length > 1; - if (this.kf) { - this.addEffect(this.getKeyframeValue.bind(this)); - } - return this.kf; -}; - -TextProperty.prototype.addEffect = function (effectFunction) { - this.effectsSequence.push(effectFunction); - this.elem.addDynamicProperty(this); -}; - -TextProperty.prototype.getValue = function (_finalValue) { - if ((this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) && !_finalValue) { - return; - } - this.currentData.t = this.data.d.k[this.keysIndex].s.t; - var currentValue = this.currentData; - var currentIndex = this.keysIndex; - if (this.lock) { - this.setCurrentData(this.currentData); - return; - } - this.lock = true; - this._mdf = false; - var i; var - len = this.effectsSequence.length; - var finalValue = _finalValue || this.data.d.k[this.keysIndex].s; - for (i = 0; i < len; i += 1) { - // Checking if index changed to prevent creating a new object every time the expression updates. - if (currentIndex !== this.keysIndex) { - finalValue = this.effectsSequence[i](finalValue, finalValue.t); - } else { - finalValue = this.effectsSequence[i](this.currentData, finalValue.t); + + function setPrefix(prefix) { + setIdPrefix(prefix); } - } - if (currentValue !== finalValue) { - this.setCurrentData(finalValue); - } - this.v = this.currentData; - this.pv = this.v; - this.lock = false; - this.frameId = this.elem.globalData.frameId; -}; - -TextProperty.prototype.getKeyframeValue = function () { - var textKeys = this.data.d.k; - var frameNum = this.elem.comp.renderedFrame; - var i = 0; var - len = textKeys.length; - while (i <= len - 1) { - if (i === len - 1 || textKeys[i + 1].t > frameNum) { - break; + + function loadAnimation(params) { + if (standalone === true) { + params.animationData = JSON.parse(animationData); + } + return animationManager.loadAnimation(params); } - i += 1; - } - if (this.keysIndex !== i) { - this.keysIndex = i; - } - return this.data.d.k[this.keysIndex].s; -}; - -TextProperty.prototype.buildFinalText = function (text) { - var charactersArray = []; - var i = 0; - var len = text.length; - var charCode; - var secondCharCode; - var shouldCombine = false; - while (i < len) { - charCode = text.charCodeAt(i); - if (FontManager.isCombinedCharacter(charCode)) { - charactersArray[charactersArray.length - 1] += text.charAt(i); - } else if (charCode >= 0xD800 && charCode <= 0xDBFF) { - secondCharCode = text.charCodeAt(i + 1); - if (secondCharCode >= 0xDC00 && secondCharCode <= 0xDFFF) { - if (shouldCombine || FontManager.isModifier(charCode, secondCharCode)) { - charactersArray[charactersArray.length - 1] += text.substr(i, 2); - shouldCombine = false; - } else { - charactersArray.push(text.substr(i, 2)); + + function setQuality(value) { + if (typeof value === 'string') { + switch (value) { + case 'high': + setDefaultCurveSegments(200); + break; + default: + case 'medium': + setDefaultCurveSegments(50); + break; + case 'low': + setDefaultCurveSegments(10); + break; } - i += 1; - } else { - charactersArray.push(text.charAt(i)); + } else if (!isNaN(value) && value > 1) { + setDefaultCurveSegments(value); } - } else if (charCode > 0xDBFF) { - secondCharCode = text.charCodeAt(i + 1); - if (FontManager.isZeroWidthJoiner(charCode, secondCharCode)) { - shouldCombine = true; - charactersArray[charactersArray.length - 1] += text.substr(i, 2); - i += 1; + if (getDefaultCurveSegments() >= 50) { + roundValues(false); } else { - charactersArray.push(text.charAt(i)); + roundValues(true); } - } else if (FontManager.isZeroWidthJoiner(charCode)) { - charactersArray[charactersArray.length - 1] += text.charAt(i); - shouldCombine = true; - } else { - charactersArray.push(text.charAt(i)); - } - i += 1; - } - return charactersArray; -}; - -TextProperty.prototype.completeTextData = function (documentData) { - documentData.__complete = true; - var fontManager = this.elem.globalData.fontManager; - var data = this.data; - var letters = []; - var i; var - len; - var newLineFlag; var index = 0; var - val; - var anchorGrouping = data.m.g; - var currentSize = 0; var currentPos = 0; var currentLine = 0; var - lineWidths = []; - var lineWidth = 0; - var maxLineWidth = 0; - var j; var - jLen; - var fontData = fontManager.getFontByName(documentData.f); - var charData; var - cLength = 0; - - var fontProps = getFontProperties(fontData); - documentData.fWeight = fontProps.weight; - documentData.fStyle = fontProps.style; - documentData.finalSize = documentData.s; - documentData.finalText = this.buildFinalText(documentData.t); - len = documentData.finalText.length; - documentData.finalLineHeight = documentData.lh; - var trackingOffset = (documentData.tr / 1000) * documentData.finalSize; - var charCode; - if (documentData.sz) { - var flag = true; - var boxWidth = documentData.sz[0]; - var boxHeight = documentData.sz[1]; - var currentHeight; var - finalText; - while (flag) { - finalText = this.buildFinalText(documentData.t); - currentHeight = 0; - lineWidth = 0; - len = finalText.length; - trackingOffset = (documentData.tr / 1000) * documentData.finalSize; - var lastSpaceIndex = -1; - for (i = 0; i < len; i += 1) { - charCode = finalText[i].charCodeAt(0); - newLineFlag = false; - if (finalText[i] === ' ') { - lastSpaceIndex = i; - } else if (charCode === 13 || charCode === 3) { - lineWidth = 0; - newLineFlag = true; - currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; - } - if (fontManager.chars) { - charData = fontManager.getCharData(finalText[i], fontData.fStyle, fontData.fFamily); - cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; - } else { - // tCanvasHelper.font = documentData.s + 'px '+ fontData.fFamily; - cLength = fontManager.measureText(finalText[i], documentData.f, documentData.finalSize); - } - if (lineWidth + cLength > boxWidth && finalText[i] !== ' ') { - if (lastSpaceIndex === -1) { - len += 1; - } else { - i = lastSpaceIndex; - } - currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; - finalText.splice(i, lastSpaceIndex === i ? 1 : 0, '\r'); - // finalText = finalText.substr(0,i) + "\r" + finalText.substr(i === lastSpaceIndex ? i + 1 : i); - lastSpaceIndex = -1; - lineWidth = 0; - } else { - lineWidth += cLength; - lineWidth += trackingOffset; - } - } - currentHeight += (fontData.ascent * documentData.finalSize) / 100; - if (this.canResize && documentData.finalSize > this.minimumFontSize && boxHeight < currentHeight) { - documentData.finalSize -= 1; - documentData.finalLineHeight = (documentData.finalSize * documentData.lh) / documentData.s; - } else { - documentData.finalText = finalText; - len = documentData.finalText.length; - flag = false; - } - } - } - lineWidth = -trackingOffset; - cLength = 0; - var uncollapsedSpaces = 0; - var currentChar; - for (i = 0; i < len; i += 1) { - newLineFlag = false; - currentChar = documentData.finalText[i]; - charCode = currentChar.charCodeAt(0); - if (charCode === 13 || charCode === 3) { - uncollapsedSpaces = 0; - lineWidths.push(lineWidth); - maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; - lineWidth = -2 * trackingOffset; - val = ''; - newLineFlag = true; - currentLine += 1; - } else { - val = currentChar; - } - if (fontManager.chars) { - charData = fontManager.getCharData(currentChar, fontData.fStyle, fontManager.getFontByName(documentData.f).fFamily); - cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; - } else { - // var charWidth = fontManager.measureText(val, documentData.f, documentData.finalSize); - // tCanvasHelper.font = documentData.finalSize + 'px '+ fontManager.getFontByName(documentData.f).fFamily; - cLength = fontManager.measureText(val, documentData.f, documentData.finalSize); } - // - if (currentChar === ' ') { - uncollapsedSpaces += cLength + trackingOffset; - } else { - lineWidth += cLength + trackingOffset + uncollapsedSpaces; - uncollapsedSpaces = 0; + function inBrowser() { + return typeof navigator !== 'undefined'; } - letters.push({ - l: cLength, an: cLength, add: currentSize, n: newLineFlag, anIndexes: [], val: val, line: currentLine, animatorJustifyOffset: 0, - }); - if (anchorGrouping == 2) { // eslint-disable-line eqeqeq - currentSize += cLength; - if (val === '' || val === ' ' || i === len - 1) { - if (val === '' || val === ' ') { - currentSize -= cLength; - } - while (currentPos <= i) { - letters[currentPos].an = currentSize; - letters[currentPos].ind = index; - letters[currentPos].extra = cLength; - currentPos += 1; - } - index += 1; - currentSize = 0; - } - } else if (anchorGrouping == 3) { // eslint-disable-line eqeqeq - currentSize += cLength; - if (val === '' || i === len - 1) { - if (val === '') { - currentSize -= cLength; - } - while (currentPos <= i) { - letters[currentPos].an = currentSize; - letters[currentPos].ind = index; - letters[currentPos].extra = cLength; - currentPos += 1; - } - currentSize = 0; - index += 1; - } - } else { - letters[index].ind = index; - letters[index].extra = 0; - index += 1; + + function installPlugin(type, plugin) { + if (type === 'expressions') { + setExpressionsPlugin(plugin); + } } - } - documentData.l = letters; - maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; - lineWidths.push(lineWidth); - if (documentData.sz) { - documentData.boxWidth = documentData.sz[0]; - documentData.justifyOffset = 0; - } else { - documentData.boxWidth = maxLineWidth; - switch (documentData.j) { - case 1: - documentData.justifyOffset = -documentData.boxWidth; - break; - case 2: - documentData.justifyOffset = -documentData.boxWidth / 2; - break; - default: - documentData.justifyOffset = 0; + + function getFactory(name) { + switch (name) { + case 'propertyFactory': + return PropertyFactory; + case 'shapePropertyFactory': + return ShapePropertyFactory; + case 'matrix': + return Matrix; + default: + return null; + } } - } - documentData.lineWidths = lineWidths; - - var animators = data.a; var animatorData; var - letterData; - jLen = animators.length; - var based; var ind; var - indexes = []; - for (j = 0; j < jLen; j += 1) { - animatorData = animators[j]; - if (animatorData.a.sc) { - documentData.strokeColorAnim = true; + + lottie.play = animationManager.play; + lottie.pause = animationManager.pause; + lottie.setLocationHref = setLocation; + lottie.togglePause = animationManager.togglePause; + lottie.setSpeed = animationManager.setSpeed; + lottie.setDirection = animationManager.setDirection; + lottie.stop = animationManager.stop; + lottie.searchAnimations = searchAnimations; + lottie.registerAnimation = animationManager.registerAnimation; + lottie.loadAnimation = loadAnimation; + lottie.setSubframeRendering = setSubframeRendering; + lottie.resize = animationManager.resize; + // lottie.start = start; + lottie.goToAndStop = animationManager.goToAndStop; + lottie.destroy = animationManager.destroy; + lottie.setQuality = setQuality; + lottie.inBrowser = inBrowser; + lottie.installPlugin = installPlugin; + lottie.freeze = animationManager.freeze; + lottie.unfreeze = animationManager.unfreeze; + lottie.setVolume = animationManager.setVolume; + lottie.mute = animationManager.mute; + lottie.unmute = animationManager.unmute; + lottie.getRegisteredAnimations = animationManager.getRegisteredAnimations; + lottie.useWebWorker = setWebWorker; + lottie.setIDPrefix = setPrefix; + lottie.__getFactory = getFactory; + lottie.version = '[[BM_VERSION]]'; + + function checkReady() { + if (document.readyState === 'complete') { + clearInterval(readyStateCheckInterval); + searchAnimations(); + } + } + + function getQueryVariable(variable) { + var vars = queryString.split('&'); + for (var i = 0; i < vars.length; i += 1) { + var pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) == variable) { // eslint-disable-line eqeqeq + return decodeURIComponent(pair[1]); + } + } + return null; } - if (animatorData.a.sw) { - documentData.strokeWidthAnim = true; + var queryString = ''; + if (standalone) { + var scripts = document.getElementsByTagName('script'); + var index = scripts.length - 1; + var myScript = scripts[index] || { + src: '', + }; + queryString = myScript.src ? myScript.src.replace(/^[^\?]+\??/, '') : ''; // eslint-disable-line no-useless-escape + renderer = getQueryVariable('renderer'); } - if (animatorData.a.fc || animatorData.a.fh || animatorData.a.fs || animatorData.a.fb) { - documentData.fillColorAnim = true; + var readyStateCheckInterval = setInterval(checkReady, 100); + + // this adds bodymovin to the window object for backwards compatibility + try { + if (!(typeof exports === 'object' && typeof module !== 'undefined') + && !(typeof define === 'function' && define.amd) // eslint-disable-line no-undef + ) { + window.bodymovin = lottie; + } + } catch (err) { + // } - ind = 0; - based = animatorData.s.b; - for (i = 0; i < len; i += 1) { - letterData = letters[i]; - letterData.anIndexes[j] = ind; - if ((based == 1 && letterData.val !== '') || (based == 2 && letterData.val !== '' && letterData.val !== ' ') || (based == 3 && (letterData.n || letterData.val == ' ' || i == len - 1)) || (based == 4 && (letterData.n || i == len - 1))) { // eslint-disable-line eqeqeq - if (animatorData.s.rn === 1) { - indexes.push(ind); + + const ShapeModifiers = (function () { + var ob = {}; + var modifiers = {}; + ob.registerModifier = registerModifier; + ob.getModifier = getModifier; + + function registerModifier(nm, factory) { + if (!modifiers[nm]) { + modifiers[nm] = factory; } - ind += 1; } - } - data.a[j].s.totalChars = ind; - var currentInd = -1; var - newInd; - if (animatorData.s.rn === 1) { - for (i = 0; i < len; i += 1) { - letterData = letters[i]; - if (currentInd != letterData.anIndexes[j]) { // eslint-disable-line eqeqeq - currentInd = letterData.anIndexes[j]; - newInd = indexes.splice(Math.floor(Math.random() * indexes.length), 1)[0]; + + function getModifier(nm, elem, data) { + return new modifiers[nm](elem, data); + } + + return ob; + }()); + + function ShapeModifier() {} + ShapeModifier.prototype.initModifierProperties = function () {}; + ShapeModifier.prototype.addShapeToModifier = function () {}; + ShapeModifier.prototype.addShape = function (data) { + if (!this.closed) { + // Adding shape to dynamic properties. It covers the case where a shape has no effects applied, to reset it's _mdf state on every tick. + data.sh.container.addDynamicProperty(data.sh); + var shapeData = { shape: data.sh, data: data, localShapeCollection: shapeCollectionPool.newShapeCollection() }; + this.shapes.push(shapeData); + this.addShapeToModifier(shapeData); + if (this._isAnimated) { + data.setAsAnimated(); } - letterData.anIndexes[j] = newInd; } - } - } - documentData.yOffset = documentData.finalLineHeight || documentData.finalSize * 1.2; - documentData.ls = documentData.ls || 0; - documentData.ascent = (fontData.ascent * documentData.finalSize) / 100; -}; - -TextProperty.prototype.updateDocumentData = function (newData, index) { - index = index === undefined ? this.keysIndex : index; - var dData = this.copyData({}, this.data.d.k[index].s); - dData = this.copyData(dData, newData); - this.data.d.k[index].s = dData; - this.recalculate(index); - this.elem.addDynamicProperty(this); -}; - -TextProperty.prototype.recalculate = function (index) { - var dData = this.data.d.k[index].s; - dData.__complete = false; - this.keysIndex = 0; - this._isFirstFrame = true; - this.getValue(dData); -}; - -TextProperty.prototype.canResizeFont = function (_canResize) { - this.canResize = _canResize; - this.recalculate(this.keysIndex); - this.elem.addDynamicProperty(this); -}; - -TextProperty.prototype.setMinimumFontSize = function (_fontValue) { - this.minimumFontSize = Math.floor(_fontValue) || 1; - this.recalculate(this.keysIndex); - this.elem.addDynamicProperty(this); -}; - -const TextSelectorProp = (function () { - var max = Math.max; - var min = Math.min; - var floor = Math.floor; - - function TextSelectorPropFactory(elem, data) { - this._currentTextLength = -1; - this.k = false; - this.data = data; - this.elem = elem; - this.comp = elem.comp; - this.finalS = 0; - this.finalE = 0; - this.initDynamicPropertyContainer(elem); - this.s = PropertyFactory.getProp(elem, data.s || { k: 0 }, 0, 0, this); - if ('e' in data) { - this.e = PropertyFactory.getProp(elem, data.e, 0, 0, this); - } else { - this.e = { v: 100 }; - } - this.o = PropertyFactory.getProp(elem, data.o || { k: 0 }, 0, 0, this); - this.xe = PropertyFactory.getProp(elem, data.xe || { k: 0 }, 0, 0, this); - this.ne = PropertyFactory.getProp(elem, data.ne || { k: 0 }, 0, 0, this); - this.sm = PropertyFactory.getProp(elem, data.sm || { k: 100 }, 0, 0, this); - this.a = PropertyFactory.getProp(elem, data.a, 0, 0.01, this); - if (!this.dynamicProperties.length) { - this.getValue(); - } - } - - TextSelectorPropFactory.prototype = { - getMult: function (ind) { - if (this._currentTextLength !== this.elem.textProperty.currentData.l.length) { - this.getValue(); - } - var x1 = 0; - var y1 = 0; - var x2 = 1; - var y2 = 1; - if (this.ne.v > 0) { - x1 = this.ne.v / 100.0; + }; + ShapeModifier.prototype.init = function (elem, data) { + this.shapes = []; + this.elem = elem; + this.initDynamicPropertyContainer(elem); + this.initModifierProperties(elem, data); + this.frameId = initialDefaultFrame; + this.closed = false; + this.k = false; + if (this.dynamicProperties.length) { + this.k = true; } else { - y1 = -this.ne.v / 100.0; + this.getValue(true); } - if (this.xe.v > 0) { - x2 = 1.0 - this.xe.v / 100.0; - } else { - y2 = 1.0 + this.xe.v / 100.0; + }; + ShapeModifier.prototype.processKeys = function () { + if (this.elem.globalData.frameId === this.frameId) { + return; } - var easer = BezierFactory.getBezierEasing(x1, y1, x2, y2).get; + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + }; - var mult = 0; - var s = this.finalS; - var e = this.finalE; - var type = this.data.sh; - if (type === 2) { - if (e === s) { - mult = ind >= e ? 1 : 0; - } else { - mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); - } - mult = easer(mult); - } else if (type === 3) { - if (e === s) { - mult = ind >= e ? 0 : 1; - } else { - mult = 1 - max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); - } + extendPrototype([DynamicPropertyContainer], ShapeModifier); - mult = easer(mult); - } else if (type === 4) { - if (e === s) { - mult = 0; - } else { - mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); - if (mult < 0.5) { - mult *= 2; + function TrimModifier() { + } + extendPrototype([ShapeModifier], TrimModifier); + TrimModifier.prototype.initModifierProperties = function (elem, data) { + this.s = PropertyFactory.getProp(elem, data.s, 0, 0.01, this); + this.e = PropertyFactory.getProp(elem, data.e, 0, 0.01, this); + this.o = PropertyFactory.getProp(elem, data.o, 0, 0, this); + this.sValue = 0; + this.eValue = 0; + this.getValue = this.processKeys; + this.m = data.m; + this._isAnimated = !!this.s.effectsSequence.length || !!this.e.effectsSequence.length || !!this.o.effectsSequence.length; + }; + + TrimModifier.prototype.addShapeToModifier = function (shapeData) { + shapeData.pathsData = []; + }; + + TrimModifier.prototype.calculateShapeEdges = function (s, e, shapeLength, addedLength, totalModifierLength) { + var segments = []; + if (e <= 1) { + segments.push({ + s: s, + e: e, + }); + } else if (s >= 1) { + segments.push({ + s: s - 1, + e: e - 1, + }); + } else { + segments.push({ + s: s, + e: 1, + }); + segments.push({ + s: 0, + e: e - 1, + }); + } + var shapeSegments = []; + var i; + var len = segments.length; + var segmentOb; + for (i = 0; i < len; i += 1) { + segmentOb = segments[i]; + if (!(segmentOb.e * totalModifierLength < addedLength || segmentOb.s * totalModifierLength > addedLength + shapeLength)) { + var shapeS; + var shapeE; + if (segmentOb.s * totalModifierLength <= addedLength) { + shapeS = 0; } else { - mult = 1 - 2 * (mult - 0.5); + shapeS = (segmentOb.s * totalModifierLength - addedLength) / shapeLength; } - } - mult = easer(mult); - } else if (type === 5) { - if (e === s) { - mult = 0; - } else { - var tot = e - s; - /* ind += 0.5; - mult = -4/(tot*tot)*(ind*ind)+(4/tot)*ind; */ - ind = min(max(0, ind + 0.5 - s), e - s); - var x = -tot / 2 + ind; - var a = tot / 2; - mult = Math.sqrt(1 - (x * x) / (a * a)); - } - mult = easer(mult); - } else if (type === 6) { - if (e === s) { - mult = 0; - } else { - ind = min(max(0, ind + 0.5 - s), e - s); - mult = (1 + (Math.cos((Math.PI + Math.PI * 2 * (ind) / (e - s))))) / 2; // eslint-disable-line - } - mult = easer(mult); - } else { - if (ind >= floor(s)) { - if (ind - s < 0) { - mult = max(0, min(min(e, 1) - (s - ind), 1)); + if (segmentOb.e * totalModifierLength >= addedLength + shapeLength) { + shapeE = 1; } else { - mult = max(0, min(e - ind, 1)); - } - } - mult = easer(mult); - } - // Smoothness implementation. - // The smoothness represents a reduced range of the original [0; 1] range. - // if smoothness is 25%, the new range will be [0.375; 0.625] - // Steps are: - // - find the lower value of the new range (threshold) - // - if multiplier is smaller than that value, floor it to 0 - // - if it is larger, - // - subtract the threshold - // - divide it by the smoothness (this will return the range to [0; 1]) - // Note: If it doesn't work on some scenarios, consider applying it before the easer. - if (this.sm.v !== 100) { - var smoothness = this.sm.v * 0.01; - if (smoothness === 0) { - smoothness = 0.00000001; - } - var threshold = 0.5 - smoothness * 0.5; - if (mult < threshold) { - mult = 0; - } else { - mult = (mult - threshold) / smoothness; - if (mult > 1) { - mult = 1; + shapeE = ((segmentOb.e * totalModifierLength - addedLength) / shapeLength); } + shapeSegments.push([shapeS, shapeE]); } } - return mult * this.a.v; - }, - getValue: function (newCharsFlag) { - this.iterateDynamicProperties(); - this._mdf = newCharsFlag || this._mdf; - this._currentTextLength = this.elem.textProperty.currentData.l.length || 0; - if (newCharsFlag && this.data.r === 2) { - this.e.v = this._currentTextLength; - } - var divisor = this.data.r === 2 ? 1 : 100 / this.data.totalChars; - var o = this.o.v / divisor; - var s = this.s.v / divisor + o; - var e = (this.e.v / divisor) + o; - if (s > e) { - var _s = s; - s = e; - e = _s; - } - this.finalS = s; - this.finalE = e; - }, - }; - extendPrototype([DynamicPropertyContainer], TextSelectorPropFactory); - - function getTextSelectorProp(elem, data, arr) { - return new TextSelectorPropFactory(elem, data, arr); - } - - return { - getTextSelectorProp: getTextSelectorProp, - }; -}()); - -function TextAnimatorDataProperty(elem, animatorProps, container) { - var defaultData = { propType: false }; - var getProp = PropertyFactory.getProp; - var textAnimatorAnimatables = animatorProps.a; - this.a = { - r: textAnimatorAnimatables.r ? getProp(elem, textAnimatorAnimatables.r, 0, degToRads, container) : defaultData, - rx: textAnimatorAnimatables.rx ? getProp(elem, textAnimatorAnimatables.rx, 0, degToRads, container) : defaultData, - ry: textAnimatorAnimatables.ry ? getProp(elem, textAnimatorAnimatables.ry, 0, degToRads, container) : defaultData, - sk: textAnimatorAnimatables.sk ? getProp(elem, textAnimatorAnimatables.sk, 0, degToRads, container) : defaultData, - sa: textAnimatorAnimatables.sa ? getProp(elem, textAnimatorAnimatables.sa, 0, degToRads, container) : defaultData, - s: textAnimatorAnimatables.s ? getProp(elem, textAnimatorAnimatables.s, 1, 0.01, container) : defaultData, - a: textAnimatorAnimatables.a ? getProp(elem, textAnimatorAnimatables.a, 1, 0, container) : defaultData, - o: textAnimatorAnimatables.o ? getProp(elem, textAnimatorAnimatables.o, 0, 0.01, container) : defaultData, - p: textAnimatorAnimatables.p ? getProp(elem, textAnimatorAnimatables.p, 1, 0, container) : defaultData, - sw: textAnimatorAnimatables.sw ? getProp(elem, textAnimatorAnimatables.sw, 0, 0, container) : defaultData, - sc: textAnimatorAnimatables.sc ? getProp(elem, textAnimatorAnimatables.sc, 1, 0, container) : defaultData, - fc: textAnimatorAnimatables.fc ? getProp(elem, textAnimatorAnimatables.fc, 1, 0, container) : defaultData, - fh: textAnimatorAnimatables.fh ? getProp(elem, textAnimatorAnimatables.fh, 0, 0, container) : defaultData, - fs: textAnimatorAnimatables.fs ? getProp(elem, textAnimatorAnimatables.fs, 0, 0.01, container) : defaultData, - fb: textAnimatorAnimatables.fb ? getProp(elem, textAnimatorAnimatables.fb, 0, 0.01, container) : defaultData, - t: textAnimatorAnimatables.t ? getProp(elem, textAnimatorAnimatables.t, 0, 0, container) : defaultData, - }; - - this.s = TextSelectorProp.getTextSelectorProp(elem, animatorProps.s, container); - this.s.t = animatorProps.s.t; -} - -function TextAnimatorProperty(textData, renderType, elem) { - this._isFirstFrame = true; - this._hasMaskedPath = false; - this._frameId = -1; - this._textData = textData; - this._renderType = renderType; - this._elem = elem; - this._animatorsData = createSizedArray(this._textData.a.length); - this._pathData = {}; - this._moreOptions = { - alignment: {}, - }; - this.renderedLetters = []; - this.lettersChangedFlag = false; - this.initDynamicPropertyContainer(elem); -} - -TextAnimatorProperty.prototype.searchProperties = function () { - var i; - var len = this._textData.a.length; - var animatorProps; - var getProp = PropertyFactory.getProp; - for (i = 0; i < len; i += 1) { - animatorProps = this._textData.a[i]; - this._animatorsData[i] = new TextAnimatorDataProperty(this._elem, animatorProps, this); - } - if (this._textData.p && 'm' in this._textData.p) { - this._pathData = { - a: getProp(this._elem, this._textData.p.a, 0, 0, this), - f: getProp(this._elem, this._textData.p.f, 0, 0, this), - l: getProp(this._elem, this._textData.p.l, 0, 0, this), - r: getProp(this._elem, this._textData.p.r, 0, 0, this), - p: getProp(this._elem, this._textData.p.p, 0, 0, this), - m: this._elem.maskManager.getMaskProperty(this._textData.p.m), - }; - this._hasMaskedPath = true; - } else { - this._hasMaskedPath = false; - } - this._moreOptions.alignment = getProp(this._elem, this._textData.m.a, 1, 0, this); -}; - -TextAnimatorProperty.prototype.getMeasures = function (documentData, lettersChangedFlag) { - this.lettersChangedFlag = lettersChangedFlag; - if (!this._mdf && !this._isFirstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m._mdf)) { - return; - } - this._isFirstFrame = false; - var alignment = this._moreOptions.alignment.v; - var animators = this._animatorsData; - var textData = this._textData; - var matrixHelper = this.mHelper; - var renderType = this._renderType; - var renderedLettersCount = this.renderedLetters.length; - var xPos; - var yPos; - var i; - var len; - var letters = documentData.l; - var pathInfo; - var currentLength; - var currentPoint; - var segmentLength; - var flag; - var pointInd; - var segmentInd; - var prevPoint; - var points; - var segments; - var partialLength; - var totalLength; - var perc; - var tanAngle; - var mask; - if (this._hasMaskedPath) { - mask = this._pathData.m; - if (!this._pathData.n || this._pathData._mdf) { - var paths = mask.v; - if (this._pathData.r.v) { - paths = paths.reverse(); - } - // TODO: release bezier data cached from previous pathInfo: this._pathData.pi - pathInfo = { - tLength: 0, - segments: [], - }; - len = paths._length - 1; - var bezierData; - totalLength = 0; + if (!shapeSegments.length) { + shapeSegments.push([0, 0]); + } + return shapeSegments; + }; + + TrimModifier.prototype.releasePathsData = function (pathsData) { + var i; + var len = pathsData.length; for (i = 0; i < len; i += 1) { - bezierData = bez.buildBezierData(paths.v[i], - paths.v[i + 1], - [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], - [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]); - pathInfo.tLength += bezierData.segmentLength; - pathInfo.segments.push(bezierData); - totalLength += bezierData.segmentLength; - } - i = len; - if (mask.v.c) { - bezierData = bez.buildBezierData(paths.v[i], - paths.v[0], - [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], - [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]); - pathInfo.tLength += bezierData.segmentLength; - pathInfo.segments.push(bezierData); - totalLength += bezierData.segmentLength; - } - this._pathData.pi = pathInfo; - } - pathInfo = this._pathData.pi; - - currentLength = this._pathData.f.v; - segmentInd = 0; - pointInd = 1; - segmentLength = 0; - flag = true; - segments = pathInfo.segments; - if (currentLength < 0 && mask.v.c) { - if (pathInfo.tLength < Math.abs(currentLength)) { - currentLength = -Math.abs(currentLength) % pathInfo.tLength; - } - segmentInd = segments.length - 1; - points = segments[segmentInd].points; - pointInd = points.length - 1; - while (currentLength < 0) { - currentLength += points[pointInd].partialLength; - pointInd -= 1; - if (pointInd < 0) { - segmentInd -= 1; - points = segments[segmentInd].points; - pointInd = points.length - 1; - } + segmentsLengthPool.release(pathsData[i]); } - } - points = segments[segmentInd].points; - prevPoint = points[pointInd - 1]; - currentPoint = points[pointInd]; - partialLength = currentPoint.partialLength; - } - - len = letters.length; - xPos = 0; - yPos = 0; - var yOff = documentData.finalSize * 1.2 * 0.714; - var firstLine = true; - var animatorProps; - var animatorSelector; - var j; - var jLen; - var letterValue; - - jLen = animators.length; - - var mult; - var ind = -1; - var offf; - var xPathPos; - var yPathPos; - var initPathPos = currentLength; - var initSegmentInd = segmentInd; - var initPointInd = pointInd; - var currentLine = -1; - var elemOpacity; - var sc; - var sw; - var fc; - var k; - var letterSw; - var letterSc; - var letterFc; - var letterM = ''; - var letterP = this.defaultPropsArray; - var letterO; - - // - if (documentData.j === 2 || documentData.j === 1) { - var animatorJustifyOffset = 0; - var animatorFirstCharOffset = 0; - var justifyOffsetMult = documentData.j === 2 ? -0.5 : -1; - var lastIndex = 0; - var isNewLine = true; - - for (i = 0; i < len; i += 1) { - if (letters[i].n) { - if (animatorJustifyOffset) { - animatorJustifyOffset += animatorFirstCharOffset; + pathsData.length = 0; + return pathsData; + }; + + TrimModifier.prototype.processShapes = function (_isFirstFrame) { + var s; + var e; + if (this._mdf || _isFirstFrame) { + var o = (this.o.v % 360) / 360; + if (o < 0) { + o += 1; + } + if (this.s.v > 1) { + s = 1 + o; + } else if (this.s.v < 0) { + s = 0 + o; + } else { + s = this.s.v + o; } - while (lastIndex < i) { - letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; - lastIndex += 1; + if (this.e.v > 1) { + e = 1 + o; + } else if (this.e.v < 0) { + e = 0 + o; + } else { + e = this.e.v + o; } - animatorJustifyOffset = 0; - isNewLine = true; - } else { - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.t.propType) { - if (isNewLine && documentData.j === 2) { - animatorFirstCharOffset += animatorProps.t.v * justifyOffsetMult; - } - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - animatorJustifyOffset += animatorProps.t.v * mult[0] * justifyOffsetMult; - } else { - animatorJustifyOffset += animatorProps.t.v * mult * justifyOffsetMult; - } - } + + if (s > e) { + var _s = s; + s = e; + e = _s; } - isNewLine = false; + s = Math.round(s * 10000) * 0.0001; + e = Math.round(e * 10000) * 0.0001; + this.sValue = s; + this.eValue = e; + } else { + s = this.sValue; + e = this.eValue; } - } - if (animatorJustifyOffset) { - animatorJustifyOffset += animatorFirstCharOffset; - } - while (lastIndex < i) { - letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; - lastIndex += 1; - } - } - // + var shapePaths; + var i; + var len = this.shapes.length; + var j; + var jLen; + var pathsData; + var pathData; + var totalShapeLength; + var totalModifierLength = 0; - for (i = 0; i < len; i += 1) { - matrixHelper.reset(); - elemOpacity = 1; - if (letters[i].n) { - xPos = 0; - yPos += documentData.yOffset; - yPos += firstLine ? 1 : 0; - currentLength = initPathPos; - firstLine = false; - if (this._hasMaskedPath) { - segmentInd = initSegmentInd; - pointInd = initPointInd; - points = segments[segmentInd].points; - prevPoint = points[pointInd - 1]; - currentPoint = points[pointInd]; - partialLength = currentPoint.partialLength; - segmentLength = 0; - } - letterM = ''; - letterFc = ''; - letterSw = ''; - letterO = ''; - letterP = this.defaultPropsArray; - } else { - if (this._hasMaskedPath) { - if (currentLine !== letters[i].line) { - switch (documentData.j) { - case 1: - currentLength += totalLength - documentData.lineWidths[letters[i].line]; - break; - case 2: - currentLength += (totalLength - documentData.lineWidths[letters[i].line]) / 2; - break; - default: - break; - } - currentLine = letters[i].line; - } - if (ind !== letters[i].ind) { - if (letters[ind]) { - currentLength += letters[ind].extra; + if (e === s) { + for (i = 0; i < len; i += 1) { + this.shapes[i].localShapeCollection.releaseShapes(); + this.shapes[i].shape._mdf = true; + this.shapes[i].shape.paths = this.shapes[i].localShapeCollection; + if (this._mdf) { + this.shapes[i].pathsData.length = 0; } - currentLength += letters[i].an / 2; - ind = letters[i].ind; } - currentLength += (alignment[0] * letters[i].an) * 0.005; - var animatorOffset = 0; - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.p.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - animatorOffset += animatorProps.p.v[0] * mult[0]; + } else if (!((e === 1 && s === 0) || (e === 0 && s === 1))) { + var segments = []; + var shapeData; + var localShapeCollection; + for (i = 0; i < len; i += 1) { + shapeData = this.shapes[i]; + // if shape hasn't changed and trim properties haven't changed, cached previous path can be used + if (!shapeData.shape._mdf && !this._mdf && !_isFirstFrame && this.m !== 2) { + shapeData.shape.paths = shapeData.localShapeCollection; + } else { + shapePaths = shapeData.shape.paths; + jLen = shapePaths._length; + totalShapeLength = 0; + if (!shapeData.shape._mdf && shapeData.pathsData.length) { + totalShapeLength = shapeData.totalShapeLength; } else { - animatorOffset += animatorProps.p.v[0] * mult; + pathsData = this.releasePathsData(shapeData.pathsData); + for (j = 0; j < jLen; j += 1) { + pathData = bez.getSegmentsLength(shapePaths.shapes[j]); + pathsData.push(pathData); + totalShapeLength += pathData.totalLength; + } + shapeData.totalShapeLength = totalShapeLength; + shapeData.pathsData = pathsData; } + + totalModifierLength += totalShapeLength; + shapeData.shape._mdf = true; } - if (animatorProps.a.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - animatorOffset += animatorProps.a.v[0] * mult[0]; + } + var shapeS = s; + var shapeE = e; + var addedLength = 0; + var edges; + for (i = len - 1; i >= 0; i -= 1) { + shapeData = this.shapes[i]; + if (shapeData.shape._mdf) { + localShapeCollection = shapeData.localShapeCollection; + localShapeCollection.releaseShapes(); + // if m === 2 means paths are trimmed individually so edges need to be found for this specific shape relative to whoel group + if (this.m === 2 && len > 1) { + edges = this.calculateShapeEdges(s, e, shapeData.totalShapeLength, addedLength, totalModifierLength); + addedLength += shapeData.totalShapeLength; } else { - animatorOffset += animatorProps.a.v[0] * mult; + edges = [[shapeS, shapeE]]; } - } - } - flag = true; - // Force alignment only works with a single line for now - if (this._pathData.a.v) { - currentLength = letters[0].an * 0.5 + ((totalLength - this._pathData.f.v - letters[0].an * 0.5 - letters[letters.length - 1].an * 0.5) * ind) / (len - 1); - currentLength += this._pathData.f.v; - } - while (flag) { - if (segmentLength + partialLength >= currentLength + animatorOffset || !points) { - perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength; - xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc; - yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc; - matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, -(alignment[1] * yOff) * 0.01); - flag = false; - } else if (points) { - segmentLength += currentPoint.partialLength; - pointInd += 1; - if (pointInd >= points.length) { - pointInd = 0; - segmentInd += 1; - if (!segments[segmentInd]) { - if (mask.v.c) { - pointInd = 0; - segmentInd = 0; - points = segments[segmentInd].points; - } else { - segmentLength -= currentPoint.partialLength; - points = null; - } + jLen = edges.length; + for (j = 0; j < jLen; j += 1) { + shapeS = edges[j][0]; + shapeE = edges[j][1]; + segments.length = 0; + if (shapeE <= 1) { + segments.push({ + s: shapeData.totalShapeLength * shapeS, + e: shapeData.totalShapeLength * shapeE, + }); + } else if (shapeS >= 1) { + segments.push({ + s: shapeData.totalShapeLength * (shapeS - 1), + e: shapeData.totalShapeLength * (shapeE - 1), + }); } else { - points = segments[segmentInd].points; + segments.push({ + s: shapeData.totalShapeLength * shapeS, + e: shapeData.totalShapeLength, + }); + segments.push({ + s: 0, + e: shapeData.totalShapeLength * (shapeE - 1), + }); } - } - if (points) { - prevPoint = currentPoint; - currentPoint = points[pointInd]; - partialLength = currentPoint.partialLength; - } - } - } - offf = letters[i].an / 2 - letters[i].add; - matrixHelper.translate(-offf, 0, 0); - } else { - offf = letters[i].an / 2 - letters[i].add; - matrixHelper.translate(-offf, 0, 0); - - // Grouping alignment - matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, (-alignment[1] * yOff) * 0.01, 0); - } - - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.t.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - // This condition is to prevent applying tracking to first character in each line. Might be better to use a boolean "isNewLine" - if (xPos !== 0 || documentData.j !== 0) { - if (this._hasMaskedPath) { - if (mult.length) { - currentLength += animatorProps.t.v * mult[0]; - } else { - currentLength += animatorProps.t.v * mult; + var newShapesData = this.addShapes(shapeData, segments[0]); + if (segments[0].s !== segments[0].e) { + if (segments.length > 1) { + var lastShapeInCollection = shapeData.shape.paths.shapes[shapeData.shape.paths._length - 1]; + if (lastShapeInCollection.c) { + var lastShape = newShapesData.pop(); + this.addPaths(newShapesData, localShapeCollection); + newShapesData = this.addShapes(shapeData, segments[1], lastShape); + } else { + this.addPaths(newShapesData, localShapeCollection); + newShapesData = this.addShapes(shapeData, segments[1]); + } + } + this.addPaths(newShapesData, localShapeCollection); } - } else if (mult.length) { - xPos += animatorProps.t.v * mult[0]; - } else { - xPos += animatorProps.t.v * mult; } + shapeData.shape.paths = localShapeCollection; } } + } else if (this._mdf) { + for (i = 0; i < len; i += 1) { + // Releasign Trim Cached paths data when no trim applied in case shapes are modified inbetween. + // Don't remove this even if it's losing cached info. + this.shapes[i].pathsData.length = 0; + this.shapes[i].shape._mdf = true; + } } - if (documentData.strokeWidthAnim) { - sw = documentData.sw || 0; + }; + + TrimModifier.prototype.addPaths = function (newPaths, localShapeCollection) { + var i; + var len = newPaths.length; + for (i = 0; i < len; i += 1) { + localShapeCollection.addShape(newPaths[i]); } - if (documentData.strokeColorAnim) { - if (documentData.sc) { - sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]]; - } else { - sc = [0, 0, 0]; - } + }; + + TrimModifier.prototype.addSegment = function (pt1, pt2, pt3, pt4, shapePath, pos, newShape) { + shapePath.setXYAt(pt2[0], pt2[1], 'o', pos); + shapePath.setXYAt(pt3[0], pt3[1], 'i', pos + 1); + if (newShape) { + shapePath.setXYAt(pt1[0], pt1[1], 'v', pos); } - if (documentData.fillColorAnim && documentData.fc) { - fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]]; + shapePath.setXYAt(pt4[0], pt4[1], 'v', pos + 1); + }; + + TrimModifier.prototype.addSegmentFromArray = function (points, shapePath, pos, newShape) { + shapePath.setXYAt(points[1], points[5], 'o', pos); + shapePath.setXYAt(points[2], points[6], 'i', pos + 1); + if (newShape) { + shapePath.setXYAt(points[0], points[4], 'v', pos); } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.a.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + shapePath.setXYAt(points[3], points[7], 'v', pos + 1); + }; - if (mult.length) { - matrixHelper.translate(-animatorProps.a.v[0] * mult[0], -animatorProps.a.v[1] * mult[1], animatorProps.a.v[2] * mult[2]); + TrimModifier.prototype.addShapes = function (shapeData, shapeSegment, shapePath) { + var pathsData = shapeData.pathsData; + var shapePaths = shapeData.shape.paths.shapes; + var i; + var len = shapeData.shape.paths._length; + var j; + var jLen; + var addedLength = 0; + var currentLengthData; + var segmentCount; + var lengths; + var segment; + var shapes = []; + var initPos; + var newShape = true; + if (!shapePath) { + shapePath = shapePool.newElement(); + segmentCount = 0; + initPos = 0; + } else { + segmentCount = shapePath._length; + initPos = shapePath._length; + } + shapes.push(shapePath); + for (i = 0; i < len; i += 1) { + lengths = pathsData[i].lengths; + shapePath.c = shapePaths[i].c; + jLen = shapePaths[i].c ? lengths.length : lengths.length + 1; + for (j = 1; j < jLen; j += 1) { + currentLengthData = lengths[j - 1]; + if (addedLength + currentLengthData.addedLength < shapeSegment.s) { + addedLength += currentLengthData.addedLength; + shapePath.c = false; + } else if (addedLength > shapeSegment.e) { + shapePath.c = false; + break; } else { - matrixHelper.translate(-animatorProps.a.v[0] * mult, -animatorProps.a.v[1] * mult, animatorProps.a.v[2] * mult); + if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + currentLengthData.addedLength) { + this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[j], shapePaths[i].v[j], shapePath, segmentCount, newShape); + newShape = false; + } else { + segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[j], shapePaths[i].o[j - 1], shapePaths[i].i[j], (shapeSegment.s - addedLength) / currentLengthData.addedLength, (shapeSegment.e - addedLength) / currentLengthData.addedLength, lengths[j - 1]); + this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); + // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); + newShape = false; + shapePath.c = false; + } + addedLength += currentLengthData.addedLength; + segmentCount += 1; } } - } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.s.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult[0]), 1 + ((animatorProps.s.v[1] - 1) * mult[1]), 1); + if (shapePaths[i].c && lengths.length) { + currentLengthData = lengths[j - 1]; + if (addedLength <= shapeSegment.e) { + var segmentLength = lengths[j - 1].addedLength; + if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + segmentLength) { + this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[0], shapePaths[i].v[0], shapePath, segmentCount, newShape); + newShape = false; + } else { + segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[0], shapePaths[i].o[j - 1], shapePaths[i].i[0], (shapeSegment.s - addedLength) / segmentLength, (shapeSegment.e - addedLength) / segmentLength, lengths[j - 1]); + this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); + // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); + newShape = false; + shapePath.c = false; + } } else { - matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult), 1 + ((animatorProps.s.v[1] - 1) * mult), 1); + shapePath.c = false; } + addedLength += currentLengthData.addedLength; + segmentCount += 1; + } + if (shapePath._length) { + shapePath.setXYAt(shapePath.v[initPos][0], shapePath.v[initPos][1], 'i', initPos); + shapePath.setXYAt(shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1], 'o', shapePath._length - 1); + } + if (addedLength > shapeSegment.e) { + break; + } + if (i < len - 1) { + shapePath = shapePool.newElement(); + newShape = true; + shapes.push(shapePath); + segmentCount = 0; } } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (animatorProps.sk.propType) { - if (mult.length) { - matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1]); - } else { - matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult); + return shapes; + }; + + function PuckerAndBloatModifier() {} + extendPrototype([ShapeModifier], PuckerAndBloatModifier); + PuckerAndBloatModifier.prototype.initModifierProperties = function (elem, data) { + this.getValue = this.processKeys; + this.amount = PropertyFactory.getProp(elem, data.a, 0, null, this); + this._isAnimated = !!this.amount.effectsSequence.length; + }; + + PuckerAndBloatModifier.prototype.processPath = function (path, amount) { + var percent = amount / 100; + var centerPoint = [0, 0]; + var pathLength = path._length; + var i = 0; + for (i = 0; i < pathLength; i += 1) { + centerPoint[0] += path.v[i][0]; + centerPoint[1] += path.v[i][1]; + } + centerPoint[0] /= pathLength; + centerPoint[1] /= pathLength; + var clonedPath = shapePool.newElement(); + clonedPath.c = path.c; + var vX; + var vY; + var oX; + var oY; + var iX; + var iY; + for (i = 0; i < pathLength; i += 1) { + vX = path.v[i][0] + (centerPoint[0] - path.v[i][0]) * percent; + vY = path.v[i][1] + (centerPoint[1] - path.v[i][1]) * percent; + oX = path.o[i][0] + (centerPoint[0] - path.o[i][0]) * -percent; + oY = path.o[i][1] + (centerPoint[1] - path.o[i][1]) * -percent; + iX = path.i[i][0] + (centerPoint[0] - path.i[i][0]) * -percent; + iY = path.i[i][1] + (centerPoint[1] - path.i[i][1]) * -percent; + clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, i); + } + return clonedPath; + }; + + PuckerAndBloatModifier.prototype.processShapes = function (_isFirstFrame) { + var shapePaths; + var i; + var len = this.shapes.length; + var j; + var jLen; + var amount = this.amount.v; + + if (amount !== 0) { + var shapeData; + var localShapeCollection; + for (i = 0; i < len; i += 1) { + shapeData = this.shapes[i]; + localShapeCollection = shapeData.localShapeCollection; + if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { + localShapeCollection.releaseShapes(); + shapeData.shape._mdf = true; + shapePaths = shapeData.shape.paths.shapes; + jLen = shapeData.shape.paths._length; + for (j = 0; j < jLen; j += 1) { + localShapeCollection.addShape(this.processPath(shapePaths[j], amount)); + } } + shapeData.shape.paths = shapeData.localShapeCollection; } - if (animatorProps.r.propType) { - if (mult.length) { - matrixHelper.rotateZ(-animatorProps.r.v * mult[2]); - } else { - matrixHelper.rotateZ(-animatorProps.r.v * mult); - } + } + if (!this.dynamicProperties.length) { + this._mdf = false; + } + }; + + const TransformPropertyFactory = (function () { + var defaultVector = [0, 0]; + + function applyToMatrix(mat) { + var _mdf = this._mdf; + this.iterateDynamicProperties(); + this._mdf = this._mdf || _mdf; + if (this.a) { + mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); } - if (animatorProps.ry.propType) { - if (mult.length) { - matrixHelper.rotateY(animatorProps.ry.v * mult[1]); - } else { - matrixHelper.rotateY(animatorProps.ry.v * mult); - } + if (this.s) { + mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]); } - if (animatorProps.rx.propType) { - if (mult.length) { - matrixHelper.rotateX(animatorProps.rx.v * mult[0]); - } else { - matrixHelper.rotateX(animatorProps.rx.v * mult); - } + if (this.sk) { + mat.skewFromAxis(-this.sk.v, this.sa.v); } - if (animatorProps.o.propType) { - if (mult.length) { - elemOpacity += ((animatorProps.o.v) * mult[0] - elemOpacity) * mult[0]; - } else { - elemOpacity += ((animatorProps.o.v) * mult - elemOpacity) * mult; - } + if (this.r) { + mat.rotate(-this.r.v); + } else { + mat.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) + .rotateY(this.or.v[1]) + .rotateX(this.or.v[0]); } - if (documentData.strokeWidthAnim && animatorProps.sw.propType) { - if (mult.length) { - sw += animatorProps.sw.v * mult[0]; + if (this.data.p.s) { + if (this.data.p.z) { + mat.translate(this.px.v, this.py.v, -this.pz.v); } else { - sw += animatorProps.sw.v * mult; + mat.translate(this.px.v, this.py.v, 0); } + } else { + mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); } - if (documentData.strokeColorAnim && animatorProps.sc.propType) { - for (k = 0; k < 3; k += 1) { - if (mult.length) { - sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult[0]; - } else { - sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult; - } - } + } + function processKeys(forceRender) { + if (this.elem.globalData.frameId === this.frameId) { + return; } - if (documentData.fillColorAnim && documentData.fc) { - if (animatorProps.fc.propType) { - for (k = 0; k < 3; k += 1) { - if (mult.length) { - fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult[0]; - } else { - fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult; - } - } + if (this._isDirty) { + this.precalculateMatrix(); + this._isDirty = false; + } + + this.iterateDynamicProperties(); + + if (this._mdf || forceRender) { + var frameRate; + this.v.cloneFromProps(this.pre.props); + if (this.appliedTransformations < 1) { + this.v.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); } - if (animatorProps.fh.propType) { - if (mult.length) { - fc = addHueToRGB(fc, animatorProps.fh.v * mult[0]); - } else { - fc = addHueToRGB(fc, animatorProps.fh.v * mult); - } + if (this.appliedTransformations < 2) { + this.v.scale(this.s.v[0], this.s.v[1], this.s.v[2]); + } + if (this.sk && this.appliedTransformations < 3) { + this.v.skewFromAxis(-this.sk.v, this.sa.v); } - if (animatorProps.fs.propType) { - if (mult.length) { - fc = addSaturationToRGB(fc, animatorProps.fs.v * mult[0]); + if (this.r && this.appliedTransformations < 4) { + this.v.rotate(-this.r.v); + } else if (!this.r && this.appliedTransformations < 4) { + this.v.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) + .rotateY(this.or.v[1]) + .rotateX(this.or.v[0]); + } + if (this.autoOriented) { + var v1; + var v2; + frameRate = this.elem.globalData.frameRate; + if (this.p && this.p.keyframes && this.p.getValueAtTime) { + if (this.p._caching.lastFrame + this.p.offsetTime <= this.p.keyframes[0].t) { + v1 = this.p.getValueAtTime((this.p.keyframes[0].t + 0.01) / frameRate, 0); + v2 = this.p.getValueAtTime(this.p.keyframes[0].t / frameRate, 0); + } else if (this.p._caching.lastFrame + this.p.offsetTime >= this.p.keyframes[this.p.keyframes.length - 1].t) { + v1 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t / frameRate), 0); + v2 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t - 0.05) / frameRate, 0); + } else { + v1 = this.p.pv; + v2 = this.p.getValueAtTime((this.p._caching.lastFrame + this.p.offsetTime - 0.01) / frameRate, this.p.offsetTime); + } + } else if (this.px && this.px.keyframes && this.py.keyframes && this.px.getValueAtTime && this.py.getValueAtTime) { + v1 = []; + v2 = []; + var px = this.px; + var py = this.py; + if (px._caching.lastFrame + px.offsetTime <= px.keyframes[0].t) { + v1[0] = px.getValueAtTime((px.keyframes[0].t + 0.01) / frameRate, 0); + v1[1] = py.getValueAtTime((py.keyframes[0].t + 0.01) / frameRate, 0); + v2[0] = px.getValueAtTime((px.keyframes[0].t) / frameRate, 0); + v2[1] = py.getValueAtTime((py.keyframes[0].t) / frameRate, 0); + } else if (px._caching.lastFrame + px.offsetTime >= px.keyframes[px.keyframes.length - 1].t) { + v1[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t / frameRate), 0); + v1[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t / frameRate), 0); + v2[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t - 0.01) / frameRate, 0); + v2[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t - 0.01) / frameRate, 0); + } else { + v1 = [px.pv, py.pv]; + v2[0] = px.getValueAtTime((px._caching.lastFrame + px.offsetTime - 0.01) / frameRate, px.offsetTime); + v2[1] = py.getValueAtTime((py._caching.lastFrame + py.offsetTime - 0.01) / frameRate, py.offsetTime); + } } else { - fc = addSaturationToRGB(fc, animatorProps.fs.v * mult); + v2 = defaultVector; + v1 = v2; } + this.v.rotate(-Math.atan2(v1[1] - v2[1], v1[0] - v2[0])); } - if (animatorProps.fb.propType) { - if (mult.length) { - fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult[0]); + if (this.data.p && this.data.p.s) { + if (this.data.p.z) { + this.v.translate(this.px.v, this.py.v, -this.pz.v); } else { - fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult); + this.v.translate(this.px.v, this.py.v, 0); } + } else { + this.v.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); } } + this.frameId = this.elem.globalData.frameId; } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - - if (animatorProps.p.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (this._hasMaskedPath) { - if (mult.length) { - matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1]); - } else { - matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); - } - } else if (mult.length) { - matrixHelper.translate(animatorProps.p.v[0] * mult[0], animatorProps.p.v[1] * mult[1], -animatorProps.p.v[2] * mult[2]); + function precalculateMatrix() { + if (!this.a.k) { + this.pre.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); + this.appliedTransformations = 1; + } else { + return; + } + if (!this.s.effectsSequence.length) { + this.pre.scale(this.s.v[0], this.s.v[1], this.s.v[2]); + this.appliedTransformations = 2; + } else { + return; + } + if (this.sk) { + if (!this.sk.effectsSequence.length && !this.sa.effectsSequence.length) { + this.pre.skewFromAxis(-this.sk.v, this.sa.v); + this.appliedTransformations = 3; } else { - matrixHelper.translate(animatorProps.p.v[0] * mult, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); + return; + } + } + if (this.r) { + if (!this.r.effectsSequence.length) { + this.pre.rotate(-this.r.v); + this.appliedTransformations = 4; + } + } else if (!this.rz.effectsSequence.length && !this.ry.effectsSequence.length && !this.rx.effectsSequence.length && !this.or.effectsSequence.length) { + this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) + .rotateY(this.or.v[1]) + .rotateX(this.or.v[0]); + this.appliedTransformations = 4; + } + } + + function autoOrient() { + // + // var prevP = this.getValueAtTime(); + } + + function addDynamicProperty(prop) { + this._addDynamicProperty(prop); + this.elem.addDynamicProperty(prop); + this._isDirty = true; + } + + function TransformProperty(elem, data, container) { + this.elem = elem; + this.frameId = -1; + this.propType = 'transform'; + this.data = data; + this.v = new Matrix(); + // Precalculated matrix with non animated properties + this.pre = new Matrix(); + this.appliedTransformations = 0; + this.initDynamicPropertyContainer(container || elem); + if (data.p && data.p.s) { + this.px = PropertyFactory.getProp(elem, data.p.x, 0, 0, this); + this.py = PropertyFactory.getProp(elem, data.p.y, 0, 0, this); + if (data.p.z) { + this.pz = PropertyFactory.getProp(elem, data.p.z, 0, 0, this); + } + } else { + this.p = PropertyFactory.getProp(elem, data.p || { k: [0, 0, 0] }, 1, 0, this); + } + if (data.rx) { + this.rx = PropertyFactory.getProp(elem, data.rx, 0, degToRads, this); + this.ry = PropertyFactory.getProp(elem, data.ry, 0, degToRads, this); + this.rz = PropertyFactory.getProp(elem, data.rz, 0, degToRads, this); + if (data.or.k[0].ti) { + var i; + var len = data.or.k.length; + for (i = 0; i < len; i += 1) { + data.or.k[i].to = null; + data.or.k[i].ti = null; + } } + this.or = PropertyFactory.getProp(elem, data.or, 1, degToRads, this); + // sh Indicates it needs to be capped between -180 and 180 + this.or.sh = true; + } else { + this.r = PropertyFactory.getProp(elem, data.r || { k: 0 }, 0, degToRads, this); + } + if (data.sk) { + this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this); + this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this); + } + this.a = PropertyFactory.getProp(elem, data.a || { k: [0, 0, 0] }, 1, 0, this); + this.s = PropertyFactory.getProp(elem, data.s || { k: [100, 100, 100] }, 1, 0.01, this); + // Opacity is not part of the transform properties, that's why it won't use this.dynamicProperties. That way transforms won't get updated if opacity changes. + if (data.o) { + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, elem); + } else { + this.o = { _mdf: false, v: 1 }; + } + this._isDirty = true; + if (!this.dynamicProperties.length) { + this.getValue(true); } } - if (documentData.strokeWidthAnim) { - letterSw = sw < 0 ? 0 : sw; + + TransformProperty.prototype = { + applyToMatrix: applyToMatrix, + getValue: processKeys, + precalculateMatrix: precalculateMatrix, + autoOrient: autoOrient, + }; + + extendPrototype([DynamicPropertyContainer], TransformProperty); + TransformProperty.prototype.addDynamicProperty = addDynamicProperty; + TransformProperty.prototype._addDynamicProperty = DynamicPropertyContainer.prototype.addDynamicProperty; + + function getTransformProperty(elem, data, container) { + return new TransformProperty(elem, data, container); + } + + return { + getTransformProperty: getTransformProperty, + }; + }()); + + function RepeaterModifier() {} + extendPrototype([ShapeModifier], RepeaterModifier); + + RepeaterModifier.prototype.initModifierProperties = function (elem, data) { + this.getValue = this.processKeys; + this.c = PropertyFactory.getProp(elem, data.c, 0, null, this); + this.o = PropertyFactory.getProp(elem, data.o, 0, null, this); + this.tr = TransformPropertyFactory.getTransformProperty(elem, data.tr, this); + this.so = PropertyFactory.getProp(elem, data.tr.so, 0, 0.01, this); + this.eo = PropertyFactory.getProp(elem, data.tr.eo, 0, 0.01, this); + this.data = data; + if (!this.dynamicProperties.length) { + this.getValue(true); + } + this._isAnimated = !!this.dynamicProperties.length; + this.pMatrix = new Matrix(); + this.rMatrix = new Matrix(); + this.sMatrix = new Matrix(); + this.tMatrix = new Matrix(); + this.matrix = new Matrix(); + }; + + RepeaterModifier.prototype.applyTransforms = function (pMatrix, rMatrix, sMatrix, transform, perc, inv) { + var dir = inv ? -1 : 1; + var scaleX = transform.s.v[0] + (1 - transform.s.v[0]) * (1 - perc); + var scaleY = transform.s.v[1] + (1 - transform.s.v[1]) * (1 - perc); + pMatrix.translate(transform.p.v[0] * dir * perc, transform.p.v[1] * dir * perc, transform.p.v[2]); + rMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); + rMatrix.rotate(-transform.r.v * dir * perc); + rMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); + sMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); + sMatrix.scale(inv ? 1 / scaleX : scaleX, inv ? 1 / scaleY : scaleY); + sMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); + }; + + RepeaterModifier.prototype.init = function (elem, arr, pos, elemsData) { + this.elem = elem; + this.arr = arr; + this.pos = pos; + this.elemsData = elemsData; + this._currentCopies = 0; + this._elements = []; + this._groups = []; + this.frameId = -1; + this.initDynamicPropertyContainer(elem); + this.initModifierProperties(elem, arr[pos]); + while (pos > 0) { + pos -= 1; + // this._elements.unshift(arr.splice(pos,1)[0]); + this._elements.unshift(arr[pos]); + } + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.getValue(true); } - if (documentData.strokeColorAnim) { - letterSc = 'rgb(' + Math.round(sc[0] * 255) + ',' + Math.round(sc[1] * 255) + ',' + Math.round(sc[2] * 255) + ')'; + }; + + RepeaterModifier.prototype.resetElements = function (elements) { + var i; + var len = elements.length; + for (i = 0; i < len; i += 1) { + elements[i]._processed = false; + if (elements[i].ty === 'gr') { + this.resetElements(elements[i].it); + } } - if (documentData.fillColorAnim && documentData.fc) { - letterFc = 'rgb(' + Math.round(fc[0] * 255) + ',' + Math.round(fc[1] * 255) + ',' + Math.round(fc[2] * 255) + ')'; + }; + + RepeaterModifier.prototype.cloneElements = function (elements) { + var newElements = JSON.parse(JSON.stringify(elements)); + this.resetElements(newElements); + return newElements; + }; + + RepeaterModifier.prototype.changeGroupRender = function (elements, renderFlag) { + var i; + var len = elements.length; + for (i = 0; i < len; i += 1) { + elements[i]._render = renderFlag; + if (elements[i].ty === 'gr') { + this.changeGroupRender(elements[i].it, renderFlag); + } } + }; - if (this._hasMaskedPath) { - matrixHelper.translate(0, -documentData.ls); + RepeaterModifier.prototype.processShapes = function (_isFirstFrame) { + var items; + var itemsTransform; + var i; + var dir; + var cont; + var hasReloaded = false; + if (this._mdf || _isFirstFrame) { + var copies = Math.ceil(this.c.v); + if (this._groups.length < copies) { + while (this._groups.length < copies) { + var group = { + it: this.cloneElements(this._elements), + ty: 'gr', + }; + group.it.push({ + a: { a: 0, ix: 1, k: [0, 0] }, nm: 'Transform', o: { a: 0, ix: 7, k: 100 }, p: { a: 0, ix: 2, k: [0, 0] }, r: { a: 1, ix: 6, k: [{ s: 0, e: 0, t: 0 }, { s: 0, e: 0, t: 1 }] }, s: { a: 0, ix: 3, k: [100, 100] }, sa: { a: 0, ix: 5, k: 0 }, sk: { a: 0, ix: 4, k: 0 }, ty: 'tr', + }); + + this.arr.splice(0, 0, group); + this._groups.splice(0, 0, group); + this._currentCopies += 1; + } + this.elem.reloadShapes(); + hasReloaded = true; + } + cont = 0; + var renderFlag; + for (i = 0; i <= this._groups.length - 1; i += 1) { + renderFlag = cont < copies; + this._groups[i]._render = renderFlag; + this.changeGroupRender(this._groups[i].it, renderFlag); + if (!renderFlag) { + var elems = this.elemsData[i].it; + var transformData = elems[elems.length - 1]; + if (transformData.transform.op.v !== 0) { + transformData.transform.op._mdf = true; + transformData.transform.op.v = 0; + } else { + transformData.transform.op._mdf = false; + } + } + cont += 1; + } + + this._currentCopies = copies; + /// / + + var offset = this.o.v; + var offsetModulo = offset % 1; + var roundOffset = offset > 0 ? Math.floor(offset) : Math.ceil(offset); + var pProps = this.pMatrix.props; + var rProps = this.rMatrix.props; + var sProps = this.sMatrix.props; + this.pMatrix.reset(); + this.rMatrix.reset(); + this.sMatrix.reset(); + this.tMatrix.reset(); + this.matrix.reset(); + var iteration = 0; - matrixHelper.translate(0, (alignment[1] * yOff) * 0.01 + yPos, 0); - if (this._pathData.p.v) { - tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0]); - var rot = (Math.atan(tanAngle) * 180) / Math.PI; - if (currentPoint.point[0] < prevPoint.point[0]) { - rot += 180; + if (offset > 0) { + while (iteration < roundOffset) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); + iteration += 1; + } + if (offsetModulo) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, offsetModulo, false); + iteration += offsetModulo; + } + } else if (offset < 0) { + while (iteration > roundOffset) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, true); + iteration -= 1; + } + if (offsetModulo) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, -offsetModulo, true); + iteration -= offsetModulo; } - matrixHelper.rotate((-rot * Math.PI) / 180); } - matrixHelper.translate(xPathPos, yPathPos, 0); - currentLength -= (alignment[0] * letters[i].an) * 0.005; - if (letters[i + 1] && ind !== letters[i + 1].ind) { - currentLength += letters[i].an / 2; - currentLength += (documentData.tr * 0.001) * documentData.finalSize; + i = this.data.m === 1 ? 0 : this._currentCopies - 1; + dir = this.data.m === 1 ? 1 : -1; + cont = this._currentCopies; + var j; + var jLen; + while (cont) { + items = this.elemsData[i].it; + itemsTransform = items[items.length - 1].transform.mProps.v.props; + jLen = itemsTransform.length; + items[items.length - 1].transform.mProps._mdf = true; + items[items.length - 1].transform.op._mdf = true; + items[items.length - 1].transform.op.v = this._currentCopies === 1 + ? this.so.v + : this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1)); + + if (iteration !== 0) { + if ((i !== 0 && dir === 1) || (i !== this._currentCopies - 1 && dir === -1)) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); + } + this.matrix.transform(rProps[0], rProps[1], rProps[2], rProps[3], rProps[4], rProps[5], rProps[6], rProps[7], rProps[8], rProps[9], rProps[10], rProps[11], rProps[12], rProps[13], rProps[14], rProps[15]); + this.matrix.transform(sProps[0], sProps[1], sProps[2], sProps[3], sProps[4], sProps[5], sProps[6], sProps[7], sProps[8], sProps[9], sProps[10], sProps[11], sProps[12], sProps[13], sProps[14], sProps[15]); + this.matrix.transform(pProps[0], pProps[1], pProps[2], pProps[3], pProps[4], pProps[5], pProps[6], pProps[7], pProps[8], pProps[9], pProps[10], pProps[11], pProps[12], pProps[13], pProps[14], pProps[15]); + + for (j = 0; j < jLen; j += 1) { + itemsTransform[j] = this.matrix.props[j]; + } + this.matrix.reset(); + } else { + this.matrix.reset(); + for (j = 0; j < jLen; j += 1) { + itemsTransform[j] = this.matrix.props[j]; + } + } + iteration += 1; + cont -= 1; + i += dir; } } else { - matrixHelper.translate(xPos, yPos, 0); + cont = this._currentCopies; + i = 0; + dir = 1; + while (cont) { + items = this.elemsData[i].it; + itemsTransform = items[items.length - 1].transform.mProps.v.props; + items[items.length - 1].transform.mProps._mdf = false; + items[items.length - 1].transform.op._mdf = false; + cont -= 1; + i += dir; + } + } + return hasReloaded; + }; + + RepeaterModifier.prototype.addShape = function () {}; + + function RoundCornersModifier() {} + extendPrototype([ShapeModifier], RoundCornersModifier); + RoundCornersModifier.prototype.initModifierProperties = function (elem, data) { + this.getValue = this.processKeys; + this.rd = PropertyFactory.getProp(elem, data.r, 0, null, this); + this._isAnimated = !!this.rd.effectsSequence.length; + }; - if (documentData.ps) { - // matrixHelper.translate(documentData.ps[0],documentData.ps[1],0); - matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); + RoundCornersModifier.prototype.processPath = function (path, round) { + var clonedPath = shapePool.newElement(); + clonedPath.c = path.c; + var i; + var len = path._length; + var currentV; + var currentI; + var currentO; + var closerV; + var distance; + var newPosPerc; + var index = 0; + var vX; + var vY; + var oX; + var oY; + var iX; + var iY; + for (i = 0; i < len; i += 1) { + currentV = path.v[i]; + currentO = path.o[i]; + currentI = path.i[i]; + if (currentV[0] === currentO[0] && currentV[1] === currentO[1] && currentV[0] === currentI[0] && currentV[1] === currentI[1]) { + if ((i === 0 || i === len - 1) && !path.c) { + clonedPath.setTripleAt(currentV[0], currentV[1], currentO[0], currentO[1], currentI[0], currentI[1], index); + /* clonedPath.v[index] = currentV; + clonedPath.o[index] = currentO; + clonedPath.i[index] = currentI; */ + index += 1; + } else { + if (i === 0) { + closerV = path.v[len - 1]; + } else { + closerV = path.v[i - 1]; + } + distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); + newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; + iX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; + vX = iX; + iY = currentV[1] - (currentV[1] - closerV[1]) * newPosPerc; + vY = iY; + oX = vX - (vX - currentV[0]) * roundCorner; + oY = vY - (vY - currentV[1]) * roundCorner; + clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); + index += 1; + + if (i === len - 1) { + closerV = path.v[0]; + } else { + closerV = path.v[i + 1]; + } + distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); + newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; + oX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; + vX = oX; + oY = currentV[1] + (closerV[1] - currentV[1]) * newPosPerc; + vY = oY; + iX = vX - (vX - currentV[0]) * roundCorner; + iY = vY - (vY - currentV[1]) * roundCorner; + clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); + index += 1; + } + } else { + clonedPath.setTripleAt(path.v[i][0], path.v[i][1], path.o[i][0], path.o[i][1], path.i[i][0], path.i[i][1], index); + index += 1; } - switch (documentData.j) { - case 1: - matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]), 0, 0); + } + return clonedPath; + }; + + RoundCornersModifier.prototype.processShapes = function (_isFirstFrame) { + var shapePaths; + var i; + var len = this.shapes.length; + var j; + var jLen; + var rd = this.rd.v; + + if (rd !== 0) { + var shapeData; + var localShapeCollection; + for (i = 0; i < len; i += 1) { + shapeData = this.shapes[i]; + localShapeCollection = shapeData.localShapeCollection; + if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { + localShapeCollection.releaseShapes(); + shapeData.shape._mdf = true; + shapePaths = shapeData.shape.paths.shapes; + jLen = shapeData.shape.paths._length; + for (j = 0; j < jLen; j += 1) { + localShapeCollection.addShape(this.processPath(shapePaths[j], rd)); + } + } + shapeData.shape.paths = shapeData.localShapeCollection; + } + } + if (!this.dynamicProperties.length) { + this._mdf = false; + } + }; + + function getFontProperties(fontData) { + var styles = fontData.fStyle ? fontData.fStyle.split(' ') : []; + + var fWeight = 'normal'; var + fStyle = 'normal'; + var len = styles.length; + var styleName; + for (var i = 0; i < len; i += 1) { + styleName = styles[i].toLowerCase(); + switch (styleName) { + case 'italic': + fStyle = 'italic'; break; - case 2: - matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]) / 2, 0, 0); + case 'bold': + fWeight = '700'; + break; + case 'black': + fWeight = '900'; + break; + case 'medium': + fWeight = '500'; + break; + case 'regular': + case 'normal': + fWeight = '400'; + break; + case 'light': + case 'thin': + fWeight = '200'; break; default: break; } - matrixHelper.translate(0, -documentData.ls); - matrixHelper.translate(offf, 0, 0); - matrixHelper.translate((alignment[0] * letters[i].an) * 0.005, (alignment[1] * yOff) * 0.01, 0); - xPos += letters[i].l + (documentData.tr * 0.001) * documentData.finalSize; - } - if (renderType === 'html') { - letterM = matrixHelper.toCSS(); - } else if (renderType === 'svg') { - letterM = matrixHelper.to2dCSS(); - } else { - letterP = [matrixHelper.props[0], matrixHelper.props[1], matrixHelper.props[2], matrixHelper.props[3], matrixHelper.props[4], matrixHelper.props[5], matrixHelper.props[6], matrixHelper.props[7], matrixHelper.props[8], matrixHelper.props[9], matrixHelper.props[10], matrixHelper.props[11], matrixHelper.props[12], matrixHelper.props[13], matrixHelper.props[14], matrixHelper.props[15]]; } - letterO = elemOpacity; - } - if (renderedLettersCount <= i) { - letterValue = new LetterProps(letterO, letterSw, letterSc, letterFc, letterM, letterP); - this.renderedLetters.push(letterValue); - renderedLettersCount += 1; - this.lettersChangedFlag = true; - } else { - letterValue = this.renderedLetters[i]; - this.lettersChangedFlag = letterValue.update(letterO, letterSw, letterSc, letterFc, letterM, letterP) || this.lettersChangedFlag; - } - } -}; - -TextAnimatorProperty.prototype.getValue = function () { - if (this._elem.globalData.frameId === this._frameId) { - return; - } - this._frameId = this._elem.globalData.frameId; - this.iterateDynamicProperties(); -}; - -TextAnimatorProperty.prototype.mHelper = new Matrix(); -TextAnimatorProperty.prototype.defaultPropsArray = []; -extendPrototype([DynamicPropertyContainer], TextAnimatorProperty); - -function ITextElement() { -} - -ITextElement.prototype.initElement = function (data, globalData, comp) { - this.lettersChangedFlag = true; - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.textProperty = new TextProperty(this, data.t, this.dynamicProperties); - this.textAnimator = new TextAnimatorProperty(data.t, this.renderType, this); - this.initTransform(data, globalData, comp); - this.initHierarchy(); - this.initRenderable(); - this.initRendererElement(); - this.createContainerElements(); - this.createRenderableComponents(); - this.createContent(); - this.hide(); - this.textAnimator.searchProperties(this.dynamicProperties); -}; - -ITextElement.prototype.prepareFrame = function (num) { - this._mdf = false; - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - if (this.textProperty._mdf || this.textProperty._isFirstFrame) { - this.buildNewText(); - this.textProperty._isFirstFrame = false; - this.textProperty._mdf = false; - } -}; - -ITextElement.prototype.createPathShape = function (matrixHelper, shapes) { - var j; - var jLen = shapes.length; - var pathNodes; - var shapeStr = ''; - for (j = 0; j < jLen; j += 1) { - if (shapes[j].ty === 'sh') { - pathNodes = shapes[j].ks.k; - shapeStr += buildShapeString(pathNodes, pathNodes.i.length, true, matrixHelper); - } - } - return shapeStr; -}; - -ITextElement.prototype.updateDocumentData = function (newData, index) { - this.textProperty.updateDocumentData(newData, index); -}; - -ITextElement.prototype.canResizeFont = function (_canResize) { - this.textProperty.canResizeFont(_canResize); -}; - -ITextElement.prototype.setMinimumFontSize = function (_fontSize) { - this.textProperty.setMinimumFontSize(_fontSize); -}; - -ITextElement.prototype.applyTextPropertiesToMatrix = function (documentData, matrixHelper, lineNumber, xPos, yPos) { - if (documentData.ps) { - matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); - } - matrixHelper.translate(0, -documentData.ls, 0); - switch (documentData.j) { - case 1: - matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]), 0, 0); - break; - case 2: - matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]) / 2, 0, 0); - break; - default: - break; - } - matrixHelper.translate(xPos, yPos, 0); -}; - -ITextElement.prototype.buildColor = function (colorData) { - return 'rgb(' + Math.round(colorData[0] * 255) + ',' + Math.round(colorData[1] * 255) + ',' + Math.round(colorData[2] * 255) + ')'; -}; - -ITextElement.prototype.emptyProp = new LetterProps(); - -ITextElement.prototype.destroy = function () { - -}; - -var emptyShapeData = { - shapes: [], -}; - -function SVGTextLottieElement(data, globalData, comp) { - this.textSpans = []; - this.renderType = 'svg'; - this.initElement(data, globalData, comp); -} - -extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement, ITextElement], SVGTextLottieElement); - -SVGTextLottieElement.prototype.createContent = function () { - if (this.data.singleShape && !this.globalData.fontManager.chars) { - this.textContainer = createNS('text'); - } -}; - -SVGTextLottieElement.prototype.buildTextContents = function (textArray) { - var i = 0; - var len = textArray.length; - var textContents = []; - var currentTextContent = ''; - while (i < len) { - if (textArray[i] === String.fromCharCode(13) || textArray[i] === String.fromCharCode(3)) { - textContents.push(currentTextContent); - currentTextContent = ''; - } else { - currentTextContent += textArray[i]; - } - i += 1; - } - textContents.push(currentTextContent); - return textContents; -}; - -SVGTextLottieElement.prototype.buildShapeData = function (data, scale) { - // data should probably be cloned to apply scale separately to each instance of a text on different layers - // but since text internal content gets only rendered once and then it's never rerendered, - // it's probably safe not to clone data and reuse always the same instance even if the object is mutated. - // Avoiding cloning is preferred since cloning each character shape data is expensive - if (data.shapes && data.shapes.length) { - var shape = data.shapes[0]; - if (shape.it) { - var shapeItem = shape.it[shape.it.length - 1]; - if (shapeItem.s) { - shapeItem.s.k[0] = scale; - shapeItem.s.k[1] = scale; - } - } - } - return data; -}; - -SVGTextLottieElement.prototype.buildNewText = function () { - this.addDynamicProperty(this); - var i; - var len; - - var documentData = this.textProperty.currentData; - this.renderedLetters = createSizedArray(documentData ? documentData.l.length : 0); - if (documentData.fc) { - this.layerElement.setAttribute('fill', this.buildColor(documentData.fc)); - } else { - this.layerElement.setAttribute('fill', 'rgba(0,0,0,0)'); - } - if (documentData.sc) { - this.layerElement.setAttribute('stroke', this.buildColor(documentData.sc)); - this.layerElement.setAttribute('stroke-width', documentData.sw); - } - this.layerElement.setAttribute('font-size', documentData.finalSize); - var fontData = this.globalData.fontManager.getFontByName(documentData.f); - if (fontData.fClass) { - this.layerElement.setAttribute('class', fontData.fClass); - } else { - this.layerElement.setAttribute('font-family', fontData.fFamily); - var fWeight = documentData.fWeight; - var fStyle = documentData.fStyle; - this.layerElement.setAttribute('font-style', fStyle); - this.layerElement.setAttribute('font-weight', fWeight); - } - this.layerElement.setAttribute('aria-label', documentData.t); - - var letters = documentData.l || []; - var usesGlyphs = !!this.globalData.fontManager.chars; - len = letters.length; - - var tSpan; - var matrixHelper = this.mHelper; - var shapeStr = ''; - var singleShape = this.data.singleShape; - var xPos = 0; - var yPos = 0; - var firstLine = true; - var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; - if (singleShape && !usesGlyphs && !documentData.sz) { - var tElement = this.textContainer; - var justify = 'start'; - switch (documentData.j) { - case 1: - justify = 'end'; - break; - case 2: - justify = 'middle'; - break; - default: - justify = 'start'; - break; - } - tElement.setAttribute('text-anchor', justify); - tElement.setAttribute('letter-spacing', trackingOffset); - var textContent = this.buildTextContents(documentData.finalText); - len = textContent.length; - yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0; - for (i = 0; i < len; i += 1) { - tSpan = this.textSpans[i].span || createNS('tspan'); - tSpan.textContent = textContent[i]; - tSpan.setAttribute('x', 0); - tSpan.setAttribute('y', yPos); - tSpan.style.display = 'inherit'; - tElement.appendChild(tSpan); - if (!this.textSpans[i]) { - this.textSpans[i] = { - span: null, - glyph: null, - }; - } - this.textSpans[i].span = tSpan; - yPos += documentData.finalLineHeight; + return { + style: fStyle, + weight: fontData.fWeight || fWeight, + }; } - this.layerElement.appendChild(tElement); - } else { - var cachedSpansLength = this.textSpans.length; - var charData; - for (i = 0; i < len; i += 1) { - if (!this.textSpans[i]) { - this.textSpans[i] = { - span: null, - childSpan: null, - glyph: null, - }; - } - if (!usesGlyphs || !singleShape || i === 0) { - tSpan = cachedSpansLength > i ? this.textSpans[i].span : createNS(usesGlyphs ? 'g' : 'text'); - if (cachedSpansLength <= i) { - tSpan.setAttribute('stroke-linecap', 'butt'); - tSpan.setAttribute('stroke-linejoin', 'round'); - tSpan.setAttribute('stroke-miterlimit', '4'); - this.textSpans[i].span = tSpan; - if (usesGlyphs) { - var childSpan = createNS('g'); - tSpan.appendChild(childSpan); - this.textSpans[i].childSpan = childSpan; + const FontManager = (function () { + var maxWaitingTime = 5000; + var emptyChar = { + w: 0, + size: 0, + shapes: [], + data: { + shapes: [], + }, + }; + var combinedCharacters = []; + // Hindi characters + combinedCharacters = combinedCharacters.concat([2304, 2305, 2306, 2307, 2362, 2363, 2364, 2364, 2366, + 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, + 2380, 2381, 2382, 2383, 2387, 2388, 2389, 2390, 2391, 2402, 2403]); + + var surrogateModifiers = [ + 'd83cdffb', + 'd83cdffc', + 'd83cdffd', + 'd83cdffe', + 'd83cdfff', + ]; + + var zeroWidthJoiner = [65039, 8205]; + + function trimFontOptions(font) { + var familyArray = font.split(','); + var i; + var len = familyArray.length; + var enabledFamilies = []; + for (i = 0; i < len; i += 1) { + if (familyArray[i] !== 'sans-serif' && familyArray[i] !== 'monospace') { + enabledFamilies.push(familyArray[i]); + } + } + return enabledFamilies.join(','); + } + + function setUpNode(font, family) { + var parentNode = createTag('span'); + // Node is invisible to screen readers. + parentNode.setAttribute('aria-hidden', true); + parentNode.style.fontFamily = family; + var node = createTag('span'); + // Characters that vary significantly among different fonts + node.innerText = 'giItT1WQy@!-/#'; + // Visible - so we can measure it - but not on the screen + parentNode.style.position = 'absolute'; + parentNode.style.left = '-10000px'; + parentNode.style.top = '-10000px'; + // Large font size makes even subtle changes obvious + parentNode.style.fontSize = '300px'; + // Reset any font properties + parentNode.style.fontVariant = 'normal'; + parentNode.style.fontStyle = 'normal'; + parentNode.style.fontWeight = 'normal'; + parentNode.style.letterSpacing = '0'; + parentNode.appendChild(node); + document.body.appendChild(parentNode); + + // Remember width with no applied web font + var width = node.offsetWidth; + node.style.fontFamily = trimFontOptions(font) + ', ' + family; + return { node: node, w: width, parent: parentNode }; + } + + function checkLoadedFonts() { + var i; + var len = this.fonts.length; + var node; + var w; + var loadedCount = len; + for (i = 0; i < len; i += 1) { + if (this.fonts[i].loaded) { + loadedCount -= 1; + } else if (this.fonts[i].fOrigin === 'n' || this.fonts[i].origin === 0) { + this.fonts[i].loaded = true; + } else { + node = this.fonts[i].monoCase.node; + w = this.fonts[i].monoCase.w; + if (node.offsetWidth !== w) { + loadedCount -= 1; + this.fonts[i].loaded = true; + } else { + node = this.fonts[i].sansCase.node; + w = this.fonts[i].sansCase.w; + if (node.offsetWidth !== w) { + loadedCount -= 1; + this.fonts[i].loaded = true; + } + } + if (this.fonts[i].loaded) { + this.fonts[i].sansCase.parent.parentNode.removeChild(this.fonts[i].sansCase.parent); + this.fonts[i].monoCase.parent.parentNode.removeChild(this.fonts[i].monoCase.parent); + } } - this.textSpans[i].span = tSpan; - this.layerElement.appendChild(tSpan); } - tSpan.style.display = 'inherit'; - } - matrixHelper.reset(); - if (singleShape) { - if (letters[i].n) { - xPos = -trackingOffset; - yPos += documentData.yOffset; - yPos += firstLine ? 1 : 0; - firstLine = false; + if (loadedCount !== 0 && Date.now() - this.initTime < maxWaitingTime) { + setTimeout(this.checkLoadedFontsBinded, 20); + } else { + setTimeout(this.setIsLoadedBinded, 10); + } + } + + function createHelper(fontData, def) { + var engine = (document.body && def) ? 'svg' : 'canvas'; + var helper; + var fontProps = getFontProperties(fontData); + if (engine === 'svg') { + var tHelper = createNS('text'); + tHelper.style.fontSize = '100px'; + // tHelper.style.fontFamily = fontData.fFamily; + tHelper.setAttribute('font-family', fontData.fFamily); + tHelper.setAttribute('font-style', fontProps.style); + tHelper.setAttribute('font-weight', fontProps.weight); + tHelper.textContent = '1'; + if (fontData.fClass) { + tHelper.style.fontFamily = 'inherit'; + tHelper.setAttribute('class', fontData.fClass); + } else { + tHelper.style.fontFamily = fontData.fFamily; + } + def.appendChild(tHelper); + helper = tHelper; + } else { + var tCanvasHelper = new OffscreenCanvas(500, 500).getContext('2d'); + tCanvasHelper.font = fontProps.style + ' ' + fontProps.weight + ' 100px ' + fontData.fFamily; + helper = tCanvasHelper; + } + function measure(text) { + if (engine === 'svg') { + helper.textContent = text; + return helper.getComputedTextLength(); + } + return helper.measureText(text).width; } - this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); - xPos += letters[i].l || 0; - // xPos += letters[i].val === ' ' ? 0 : trackingOffset; - xPos += trackingOffset; + return { + measureText: measure, + }; } - if (usesGlyphs) { - charData = this.globalData.fontManager.getCharData( - documentData.finalText[i], - fontData.fStyle, - this.globalData.fontManager.getFontByName(documentData.f).fFamily - ); - var glyphElement; - // t === 1 means the character has been replaced with an animated shaped - if (charData.t === 1) { - glyphElement = new SVGCompElement(charData.data, this.globalData, this); - } else { - var data = emptyShapeData; - if (charData.data && charData.data.shapes) { - data = this.buildShapeData(charData.data, documentData.finalSize); - } - glyphElement = new SVGShapeElement(data, this.globalData, this); - } - if (this.textSpans[i].glyph) { - var glyph = this.textSpans[i].glyph; - this.textSpans[i].childSpan.removeChild(glyph.layerElement); - glyph.destroy(); - } - this.textSpans[i].glyph = glyphElement; - glyphElement._debug = true; - glyphElement.prepareFrame(0); - glyphElement.renderFrame(); - this.textSpans[i].childSpan.appendChild(glyphElement.layerElement); - // when using animated shapes, the layer will be scaled instead of replacing the internal scale - // this might have issues with strokes and might need a different solution - if (charData.t === 1) { - this.textSpans[i].childSpan.setAttribute('transform', 'scale(' + documentData.finalSize / 100 + ',' + documentData.finalSize / 100 + ')'); + + function addFonts(fontData, defs) { + if (!fontData) { + this.isLoaded = true; + return; } - } else { - if (singleShape) { - tSpan.setAttribute('transform', 'translate(' + matrixHelper.props[12] + ',' + matrixHelper.props[13] + ')'); + if (this.chars) { + this.isLoaded = true; + this.fonts = fontData.list; + return; + } + if (!document.body) { + this.isLoaded = true; + fontData.list.forEach((data) => { + data.helper = createHelper(data); + data.cache = {}; + }); + this.fonts = fontData.list; + return; } - tSpan.textContent = letters[i].val; - tSpan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - } - // - } - if (singleShape && tSpan) { - tSpan.setAttribute('d', shapeStr); - } - } - while (i < this.textSpans.length) { - this.textSpans[i].span.style.display = 'none'; - i += 1; - } - - this._sizeChanged = true; -}; - -SVGTextLottieElement.prototype.sourceRectAtTime = function () { - this.prepareFrame(this.comp.renderedFrame - this.data.st); - this.renderInnerContent(); - if (this._sizeChanged) { - this._sizeChanged = false; - var textBox = this.layerElement.getBBox(); - this.bbox = { - top: textBox.y, - left: textBox.x, - width: textBox.width, - height: textBox.height, - }; - } - return this.bbox; -}; - -SVGTextLottieElement.prototype.getValue = function () { - var i; - var len = this.textSpans.length; - var glyphElement; - this.renderedFrame = this.comp.renderedFrame; - for (i = 0; i < len; i += 1) { - glyphElement = this.textSpans[i].glyph; - if (glyphElement) { - glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st); - if (glyphElement._mdf) { - this._mdf = true; - } - } - } -}; -SVGTextLottieElement.prototype.renderInnerContent = function () { - if (!this.data.singleShape || this._mdf) { - this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); - if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) { - this._sizeChanged = true; - var i; - var len; - var renderedLetters = this.textAnimator.renderedLetters; + var fontArr = fontData.list; + var i; + var len = fontArr.length; + var _pendingFonts = len; + for (i = 0; i < len; i += 1) { + var shouldLoadFont = true; + var loadedSelector; + var j; + fontArr[i].loaded = false; + fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace'); + fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif'); + if (!fontArr[i].fPath) { + fontArr[i].loaded = true; + _pendingFonts -= 1; + } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) { + loadedSelector = document.querySelectorAll('style[f-forigin="p"][f-family="' + fontArr[i].fFamily + '"], style[f-origin="3"][f-family="' + fontArr[i].fFamily + '"]'); + + if (loadedSelector.length > 0) { + shouldLoadFont = false; + } - var letters = this.textProperty.currentData.l; + if (shouldLoadFont) { + var s = createTag('style'); + s.setAttribute('f-forigin', fontArr[i].fOrigin); + s.setAttribute('f-origin', fontArr[i].origin); + s.setAttribute('f-family', fontArr[i].fFamily); + s.type = 'text/css'; + s.innerText = '@font-face {font-family: ' + fontArr[i].fFamily + "; font-style: normal; src: url('" + fontArr[i].fPath + "');}"; + defs.appendChild(s); + } + } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) { + loadedSelector = document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'); - len = letters.length; - var renderedLetter; - var textSpan; - var glyphElement; - for (i = 0; i < len; i += 1) { - if (!letters[i].n) { - renderedLetter = renderedLetters[i]; - textSpan = this.textSpans[i].span; - glyphElement = this.textSpans[i].glyph; - if (glyphElement) { - glyphElement.renderFrame(); - } - if (renderedLetter._mdf.m) { - textSpan.setAttribute('transform', renderedLetter.m); + for (j = 0; j < loadedSelector.length; j += 1) { + if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) { + // Font is already loaded + shouldLoadFont = false; + } + } + + if (shouldLoadFont) { + var l = createTag('link'); + l.setAttribute('f-forigin', fontArr[i].fOrigin); + l.setAttribute('f-origin', fontArr[i].origin); + l.type = 'text/css'; + l.rel = 'stylesheet'; + l.href = fontArr[i].fPath; + document.body.appendChild(l); + } + } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) { + loadedSelector = document.querySelectorAll('script[f-forigin="t"], script[f-origin="2"]'); + + for (j = 0; j < loadedSelector.length; j += 1) { + if (fontArr[i].fPath === loadedSelector[j].src) { + // Font is already loaded + shouldLoadFont = false; + } + } + + if (shouldLoadFont) { + var sc = createTag('link'); + sc.setAttribute('f-forigin', fontArr[i].fOrigin); + sc.setAttribute('f-origin', fontArr[i].origin); + sc.setAttribute('rel', 'stylesheet'); + sc.setAttribute('href', fontArr[i].fPath); + defs.appendChild(sc); + } } - if (renderedLetter._mdf.o) { - textSpan.setAttribute('opacity', renderedLetter.o); + fontArr[i].helper = createHelper(fontArr[i], defs); + fontArr[i].cache = {}; + this.fonts.push(fontArr[i]); + } + if (_pendingFonts === 0) { + this.isLoaded = true; + } else { + // On some cases even if the font is loaded, it won't load correctly when measuring text on canvas. + // Adding this timeout seems to fix it + setTimeout(this.checkLoadedFonts.bind(this), 100); + } + } + + function addChars(chars) { + if (!chars) { + return; + } + if (!this.chars) { + this.chars = []; + } + var i; + var len = chars.length; + var j; + var jLen = this.chars.length; + var found; + for (i = 0; i < len; i += 1) { + j = 0; + found = false; + while (j < jLen) { + if (this.chars[j].style === chars[i].style && this.chars[j].fFamily === chars[i].fFamily && this.chars[j].ch === chars[i].ch) { + found = true; + } + j += 1; } - if (renderedLetter._mdf.sw) { - textSpan.setAttribute('stroke-width', renderedLetter.sw); + if (!found) { + this.chars.push(chars[i]); + jLen += 1; } - if (renderedLetter._mdf.sc) { - textSpan.setAttribute('stroke', renderedLetter.sc); + } + } + + function getCharData(char, style, font) { + var i = 0; + var len = this.chars.length; + while (i < len) { + if (this.chars[i].ch === char && this.chars[i].style === style && this.chars[i].fFamily === font) { + return this.chars[i]; } - if (renderedLetter._mdf.fc) { - textSpan.setAttribute('fill', renderedLetter.fc); + i += 1; + } + if (((typeof char === 'string' && char.charCodeAt(0) !== 13) || !char) + && console + && console.warn // eslint-disable-line no-console + && !this._warned + ) { + this._warned = true; + console.warn('Missing character from exported characters list: ', char, style, font); // eslint-disable-line no-console + } + return emptyChar; + } + + function measureText(char, fontName, size) { + var fontData = this.getFontByName(fontName); + var index = char.charCodeAt(0); + if (!fontData.cache[index + 1]) { + var tHelper = fontData.helper; + if (char === ' ') { + var doubleSize = tHelper.measureText('|' + char + '|'); + var singleSize = tHelper.measureText('||'); + fontData.cache[index + 1] = (doubleSize - singleSize) / 100; + } else { + fontData.cache[index + 1] = tHelper.measureText(char) / 100; } } + return fontData.cache[index + 1] * size; } - } - } -}; - -function ISolidElement(data, globalData, comp) { - this.initElement(data, globalData, comp); -} -extendPrototype([IImageElement], ISolidElement); - -ISolidElement.prototype.createContent = function () { - var rect = createNS('rect'); - /// /rect.style.width = this.data.sw; - /// /rect.style.height = this.data.sh; - /// /rect.style.fill = this.data.sc; - rect.setAttribute('width', this.data.sw); - rect.setAttribute('height', this.data.sh); - rect.setAttribute('fill', this.data.sc); - this.layerElement.appendChild(rect); -}; - -function NullElement(data, globalData, comp) { - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.initFrame(); - this.initTransform(data, globalData, comp); - this.initHierarchy(); -} - -NullElement.prototype.prepareFrame = function (num) { - this.prepareProperties(num, true); -}; - -NullElement.prototype.renderFrame = function () { -}; - -NullElement.prototype.getBaseElement = function () { - return null; -}; - -NullElement.prototype.destroy = function () { -}; - -NullElement.prototype.sourceRectAtTime = function () { -}; - -NullElement.prototype.hide = function () { -}; - -extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement], NullElement); - -function SVGRendererBase() { -} - -extendPrototype([BaseRenderer], SVGRendererBase); - -SVGRendererBase.prototype.createNull = function (data) { - return new NullElement(data, this.globalData, this); -}; - -SVGRendererBase.prototype.createShape = function (data) { - return new SVGShapeElement(data, this.globalData, this); -}; - -SVGRendererBase.prototype.createText = function (data) { - return new SVGTextLottieElement(data, this.globalData, this); -}; - -SVGRendererBase.prototype.createImage = function (data) { - return new IImageElement(data, this.globalData, this); -}; - -SVGRendererBase.prototype.createSolid = function (data) { - return new ISolidElement(data, this.globalData, this); -}; - -SVGRendererBase.prototype.configAnimation = function (animData) { - this.svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); - if (this.renderConfig.viewBoxSize) { - this.svgElement.setAttribute('viewBox', this.renderConfig.viewBoxSize); - } else { - this.svgElement.setAttribute('viewBox', '0 0 ' + animData.w + ' ' + animData.h); - } - - if (!this.renderConfig.viewBoxOnly) { - this.svgElement.setAttribute('width', animData.w); - this.svgElement.setAttribute('height', animData.h); - this.svgElement.style.width = '100%'; - this.svgElement.style.height = '100%'; - this.svgElement.style.transform = 'translate3d(0,0,0)'; - this.svgElement.style.contentVisibility = this.renderConfig.contentVisibility; - } - if (this.renderConfig.width) { - this.svgElement.setAttribute('width', this.renderConfig.width); - } - if (this.renderConfig.height) { - this.svgElement.setAttribute('height', this.renderConfig.height); - } - if (this.renderConfig.className) { - this.svgElement.setAttribute('class', this.renderConfig.className); - } - if (this.renderConfig.id) { - this.svgElement.setAttribute('id', this.renderConfig.id); - } - if (this.renderConfig.focusable !== undefined) { - this.svgElement.setAttribute('focusable', this.renderConfig.focusable); - } - this.svgElement.setAttribute('preserveAspectRatio', this.renderConfig.preserveAspectRatio); - // this.layerElement.style.transform = 'translate3d(0,0,0)'; - // this.layerElement.style.transformOrigin = this.layerElement.style.mozTransformOrigin = this.layerElement.style.webkitTransformOrigin = this.layerElement.style['-webkit-transform'] = "0px 0px 0px"; - this.animationItem.wrapper.appendChild(this.svgElement); - // Mask animation - var defs = this.globalData.defs; - - this.setupGlobalData(animData, defs); - this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; - this.data = animData; - - var maskElement = createNS('clipPath'); - var rect = createNS('rect'); - rect.setAttribute('width', animData.w); - rect.setAttribute('height', animData.h); - rect.setAttribute('x', 0); - rect.setAttribute('y', 0); - var maskId = createElementID(); - maskElement.setAttribute('id', maskId); - maskElement.appendChild(rect); - this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + maskId + ')'); - - defs.appendChild(maskElement); - this.layers = animData.layers; - this.elements = createSizedArray(animData.layers.length); -}; - -SVGRendererBase.prototype.destroy = function () { - if (this.animationItem.wrapper) { - this.animationItem.wrapper.innerText = ''; - } - this.layerElement = null; - this.globalData.defs = null; - var i; - var len = this.layers ? this.layers.length : 0; - for (i = 0; i < len; i += 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - this.elements.length = 0; - this.destroyed = true; - this.animationItem = null; -}; - -SVGRendererBase.prototype.updateContainerSize = function () { -}; - -SVGRendererBase.prototype.buildItem = function (pos) { - var elements = this.elements; - if (elements[pos] || this.layers[pos].ty === 99) { - return; - } - elements[pos] = true; - var element = this.createItem(this.layers[pos]); - - elements[pos] = element; - if (getExpressionsPlugin()) { - if (this.layers[pos].ty === 0) { - this.globalData.projectInterface.registerComposition(element); - } - element.initExpressions(); - } - this.appendElementInPos(element, pos); - if (this.layers[pos].tt) { - if (!this.elements[pos - 1] || this.elements[pos - 1] === true) { - this.buildItem(pos - 1); - this.addPendingElement(element); - } else { - element.setMatte(elements[pos - 1].layerId); - } - } -}; - -SVGRendererBase.prototype.checkPendingElements = function () { - while (this.pendingElements.length) { - var element = this.pendingElements.pop(); - element.checkParenting(); - if (element.data.tt) { - var i = 0; - var len = this.elements.length; - while (i < len) { - if (this.elements[i] === element) { - element.setMatte(this.elements[i - 1].layerId); - break; + + function getFontByName(name) { + var i = 0; + var len = this.fonts.length; + while (i < len) { + if (this.fonts[i].fName === name) { + return this.fonts[i]; + } + i += 1; } - i += 1; + return this.fonts[0]; } - } - } -}; - -SVGRendererBase.prototype.renderFrame = function (num) { - if (this.renderedFrame === num || this.destroyed) { - return; - } - if (num === null) { - num = this.renderedFrame; - } else { - this.renderedFrame = num; - } - // console.log('-------'); - // console.log('FRAME ',num); - this.globalData.frameNum = num; - this.globalData.frameId += 1; - this.globalData.projectInterface.currentFrame = num; - this.globalData._mdf = false; - var i; - var len = this.layers.length; - if (!this.completeLayers) { - this.checkLayers(num); - } - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].prepareFrame(num - this.layers[i].st); - } - } - if (this.globalData._mdf) { - for (i = 0; i < len; i += 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); + + function isModifier(firstCharCode, secondCharCode) { + var sum = firstCharCode.toString(16) + secondCharCode.toString(16); + return surrogateModifiers.indexOf(sum) !== -1; } - } - } -}; - -SVGRendererBase.prototype.appendElementInPos = function (element, pos) { - var newElement = element.getBaseElement(); - if (!newElement) { - return; - } - var i = 0; - var nextElement; - while (i < pos) { - if (this.elements[i] && this.elements[i] !== true && this.elements[i].getBaseElement()) { - nextElement = this.elements[i].getBaseElement(); - } - i += 1; - } - if (nextElement) { - this.layerElement.insertBefore(newElement, nextElement); - } else { - this.layerElement.appendChild(newElement); - } -}; - -SVGRendererBase.prototype.hide = function () { - this.layerElement.style.display = 'none'; -}; - -SVGRendererBase.prototype.show = function () { - this.layerElement.style.display = 'block'; -}; - -function ICompElement() {} - -extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement, RenderableDOMElement], ICompElement); - -ICompElement.prototype.initElement = function (data, globalData, comp) { - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.initTransform(data, globalData, comp); - this.initRenderable(); - this.initHierarchy(); - this.initRendererElement(); - this.createContainerElements(); - this.createRenderableComponents(); - if (this.data.xt || !globalData.progressiveLoad) { - this.buildAllItems(); - } - this.hide(); -}; - -/* ICompElement.prototype.hide = function(){ - if(!this.hidden){ - this.hideElement(); - var i,len = this.elements.length; - for( i = 0; i < len; i+=1 ){ - if(this.elements[i]){ - this.elements[i].hide(); - } + + function isZeroWidthJoiner(firstCharCode, secondCharCode) { + if (!secondCharCode) { + return firstCharCode === zeroWidthJoiner[1]; } - } -}; */ - -ICompElement.prototype.prepareFrame = function (num) { - this._mdf = false; - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - if (!this.isInRange && !this.data.xt) { - return; - } - - if (!this.tm._placeholder) { - var timeRemapped = this.tm.v; - if (timeRemapped === this.data.op) { - timeRemapped = this.data.op - 1; - } - this.renderedFrame = timeRemapped; - } else { - this.renderedFrame = num / this.data.sr; - } - var i; - var len = this.elements.length; - if (!this.completeLayers) { - this.checkLayers(this.renderedFrame); - } - // This iteration needs to be backwards because of how expressions connect between each other - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].prepareFrame(this.renderedFrame - this.layers[i].st); - if (this.elements[i]._mdf) { - this._mdf = true; + return firstCharCode === zeroWidthJoiner[0] && secondCharCode === zeroWidthJoiner[1]; } + + function isCombinedCharacter(char) { + return combinedCharacters.indexOf(char) !== -1; + } + + function setIsLoaded() { + this.isLoaded = true; + } + + var Font = function () { + this.fonts = []; + this.chars = null; + this.typekitLoaded = 0; + this.isLoaded = false; + this._warned = false; + this.initTime = Date.now(); + this.setIsLoadedBinded = this.setIsLoaded.bind(this); + this.checkLoadedFontsBinded = this.checkLoadedFonts.bind(this); + }; + Font.isModifier = isModifier; + Font.isZeroWidthJoiner = isZeroWidthJoiner; + Font.isCombinedCharacter = isCombinedCharacter; + + var fontPrototype = { + addChars: addChars, + addFonts: addFonts, + getCharData: getCharData, + getFontByName: getFontByName, + measureText: measureText, + checkLoadedFonts: checkLoadedFonts, + setIsLoaded: setIsLoaded, + }; + + Font.prototype = fontPrototype; + + return Font; + }()); + + function RenderableElement() { + + } + + RenderableElement.prototype = { + initRenderable: function () { + // layer's visibility related to inpoint and outpoint. Rename isVisible to isInRange + this.isInRange = false; + // layer's display state + this.hidden = false; + // If layer's transparency equals 0, it can be hidden + this.isTransparent = false; + // list of animated components + this.renderableComponents = []; + }, + addRenderableComponent: function (component) { + if (this.renderableComponents.indexOf(component) === -1) { + this.renderableComponents.push(component); + } + }, + removeRenderableComponent: function (component) { + if (this.renderableComponents.indexOf(component) !== -1) { + this.renderableComponents.splice(this.renderableComponents.indexOf(component), 1); + } + }, + prepareRenderableFrame: function (num) { + this.checkLayerLimits(num); + }, + checkTransparency: function () { + if (this.finalTransform.mProp.o.v <= 0) { + if (!this.isTransparent && this.globalData.renderConfig.hideOnTransparent) { + this.isTransparent = true; + this.hide(); + } + } else if (this.isTransparent) { + this.isTransparent = false; + this.show(); + } + }, + /** + * @function + * Initializes frame related properties. + * + * @param {number} num + * current frame number in Layer's time + * + */ + checkLayerLimits: function (num) { + if (this.data.ip - this.data.st <= num && this.data.op - this.data.st > num) { + if (this.isInRange !== true) { + this.globalData._mdf = true; + this._mdf = true; + this.isInRange = true; + this.show(); + } + } else if (this.isInRange !== false) { + this.globalData._mdf = true; + this.isInRange = false; + this.hide(); + } + }, + renderRenderable: function () { + var i; + var len = this.renderableComponents.length; + for (i = 0; i < len; i += 1) { + this.renderableComponents[i].renderFrame(this._isFirstFrame); + } + /* this.maskManager.renderFrame(this.finalTransform.mat); + this.renderableEffectsManager.renderFrame(this._isFirstFrame); */ + }, + sourceRectAtTime: function () { + return { + top: 0, + left: 0, + width: 100, + height: 100, + }; + }, + getLayerSize: function () { + if (this.data.ty === 5) { + return { w: this.data.textData.width, h: this.data.textData.height }; + } + return { w: this.data.width, h: this.data.height }; + }, + }; + + const MaskManagerInterface = (function () { + function MaskInterface(mask, data) { + this._mask = mask; + this._data = data; + } + Object.defineProperty(MaskInterface.prototype, 'maskPath', { + get: function () { + if (this._mask.prop.k) { + this._mask.prop.getValue(); + } + return this._mask.prop; + }, + }); + Object.defineProperty(MaskInterface.prototype, 'maskOpacity', { + get: function () { + if (this._mask.op.k) { + this._mask.op.getValue(); + } + return this._mask.op.v * 100; + }, + }); + + var MaskManager = function (maskManager) { + var _masksInterfaces = createSizedArray(maskManager.viewData.length); + var i; + var len = maskManager.viewData.length; + for (i = 0; i < len; i += 1) { + _masksInterfaces[i] = new MaskInterface(maskManager.viewData[i], maskManager.masksProperties[i]); + } + + var maskFunction = function (name) { + i = 0; + while (i < len) { + if (maskManager.masksProperties[i].nm === name) { + return _masksInterfaces[i]; + } + i += 1; + } + return null; + }; + return maskFunction; + }; + return MaskManager; + }()); + + const ExpressionPropertyInterface = (function () { + var defaultUnidimensionalValue = { pv: 0, v: 0, mult: 1 }; + var defaultMultidimensionalValue = { pv: [0, 0, 0], v: [0, 0, 0], mult: 1 }; + + function completeProperty(expressionValue, property, type) { + Object.defineProperty(expressionValue, 'velocity', { + get: function () { + return property.getVelocityAtTime(property.comp.currentFrame); + }, + }); + expressionValue.numKeys = property.keyframes ? property.keyframes.length : 0; + expressionValue.key = function (pos) { + if (!expressionValue.numKeys) { + return 0; + } + var value = ''; + if ('s' in property.keyframes[pos - 1]) { + value = property.keyframes[pos - 1].s; + } else if ('e' in property.keyframes[pos - 2]) { + value = property.keyframes[pos - 2].e; + } else { + value = property.keyframes[pos - 2].s; + } + var valueProp = type === 'unidimensional' ? new Number(value) : Object.assign({}, value); // eslint-disable-line no-new-wrappers + valueProp.time = property.keyframes[pos - 1].t / property.elem.comp.globalData.frameRate; + valueProp.value = type === 'unidimensional' ? value[0] : value; + return valueProp; + }; + expressionValue.valueAtTime = property.getValueAtTime; + expressionValue.speedAtTime = property.getSpeedAtTime; + expressionValue.velocityAtTime = property.getVelocityAtTime; + expressionValue.propertyGroup = property.propertyGroup; + } + + function UnidimensionalPropertyInterface(property) { + if (!property || !('pv' in property)) { + property = defaultUnidimensionalValue; + } + var mult = 1 / property.mult; + var val = property.pv * mult; + var expressionValue = new Number(val); // eslint-disable-line no-new-wrappers + expressionValue.value = val; + completeProperty(expressionValue, property, 'unidimensional'); + + return function () { + if (property.k) { + property.getValue(); + } + val = property.v * mult; + if (expressionValue.value !== val) { + expressionValue = new Number(val); // eslint-disable-line no-new-wrappers + expressionValue.value = val; + completeProperty(expressionValue, property, 'unidimensional'); + } + return expressionValue; + }; + } + + function MultidimensionalPropertyInterface(property) { + if (!property || !('pv' in property)) { + property = defaultMultidimensionalValue; + } + var mult = 1 / property.mult; + var len = (property.data && property.data.l) || property.pv.length; + var expressionValue = createTypedArray('float32', len); + var arrValue = createTypedArray('float32', len); + expressionValue.value = arrValue; + completeProperty(expressionValue, property, 'multidimensional'); + + return function () { + if (property.k) { + property.getValue(); + } + for (var i = 0; i < len; i += 1) { + arrValue[i] = property.v[i] * mult; + expressionValue[i] = arrValue[i]; + } + return expressionValue; + }; + } + + // TODO: try to avoid using this getter + function defaultGetter() { + return defaultUnidimensionalValue; + } + + return function (property) { + if (!property) { + return defaultGetter; + } if (property.propType === 'unidimensional') { + return UnidimensionalPropertyInterface(property); + } + return MultidimensionalPropertyInterface(property); + }; + }()); + + const TransformExpressionInterface = (function () { + return function (transform) { + function _thisFunction(name) { + switch (name) { + case 'scale': + case 'Scale': + case 'ADBE Scale': + case 6: + return _thisFunction.scale; + case 'rotation': + case 'Rotation': + case 'ADBE Rotation': + case 'ADBE Rotate Z': + case 10: + return _thisFunction.rotation; + case 'ADBE Rotate X': + return _thisFunction.xRotation; + case 'ADBE Rotate Y': + return _thisFunction.yRotation; + case 'position': + case 'Position': + case 'ADBE Position': + case 2: + return _thisFunction.position; + case 'ADBE Position_0': + return _thisFunction.xPosition; + case 'ADBE Position_1': + return _thisFunction.yPosition; + case 'ADBE Position_2': + return _thisFunction.zPosition; + case 'anchorPoint': + case 'AnchorPoint': + case 'Anchor Point': + case 'ADBE AnchorPoint': + case 1: + return _thisFunction.anchorPoint; + case 'opacity': + case 'Opacity': + case 11: + return _thisFunction.opacity; + default: + return null; + } + } + Object.defineProperty(_thisFunction, 'rotation', { + get: ExpressionPropertyInterface(transform.r || transform.rz), + }); + + Object.defineProperty(_thisFunction, 'zRotation', { + get: ExpressionPropertyInterface(transform.rz || transform.r), + }); + + Object.defineProperty(_thisFunction, 'xRotation', { + get: ExpressionPropertyInterface(transform.rx), + }); + + Object.defineProperty(_thisFunction, 'yRotation', { + get: ExpressionPropertyInterface(transform.ry), + }); + Object.defineProperty(_thisFunction, 'scale', { + get: ExpressionPropertyInterface(transform.s), + }); + var _px; + var _py; + var _pz; + var _transformFactory; + if (transform.p) { + _transformFactory = ExpressionPropertyInterface(transform.p); + } else { + _px = ExpressionPropertyInterface(transform.px); + _py = ExpressionPropertyInterface(transform.py); + if (transform.pz) { + _pz = ExpressionPropertyInterface(transform.pz); + } + } + Object.defineProperty(_thisFunction, 'position', { + get: function () { + if (transform.p) { + return _transformFactory(); + } + return [ + _px(), + _py(), + _pz ? _pz() : 0]; + }, + }); + + Object.defineProperty(_thisFunction, 'xPosition', { + get: ExpressionPropertyInterface(transform.px), + }); + + Object.defineProperty(_thisFunction, 'yPosition', { + get: ExpressionPropertyInterface(transform.py), + }); + + Object.defineProperty(_thisFunction, 'zPosition', { + get: ExpressionPropertyInterface(transform.pz), + }); + + Object.defineProperty(_thisFunction, 'anchorPoint', { + get: ExpressionPropertyInterface(transform.a), + }); + + Object.defineProperty(_thisFunction, 'opacity', { + get: ExpressionPropertyInterface(transform.o), + }); + + Object.defineProperty(_thisFunction, 'skew', { + get: ExpressionPropertyInterface(transform.sk), + }); + + Object.defineProperty(_thisFunction, 'skewAxis', { + get: ExpressionPropertyInterface(transform.sa), + }); + + Object.defineProperty(_thisFunction, 'orientation', { + get: ExpressionPropertyInterface(transform.or), + }); + + return _thisFunction; + }; + }()); + + const LayerExpressionInterface = (function () { + function getMatrix(time) { + var toWorldMat = new Matrix(); + if (time !== undefined) { + var propMatrix = this._elem.finalTransform.mProp.getValueAtTime(time); + propMatrix.clone(toWorldMat); + } else { + var transformMat = this._elem.finalTransform.mProp; + transformMat.applyToMatrix(toWorldMat); + } + return toWorldMat; + } + + function toWorldVec(arr, time) { + var toWorldMat = this.getMatrix(time); + toWorldMat.props[12] = 0; + toWorldMat.props[13] = 0; + toWorldMat.props[14] = 0; + return this.applyPoint(toWorldMat, arr); + } + + function toWorld(arr, time) { + var toWorldMat = this.getMatrix(time); + return this.applyPoint(toWorldMat, arr); + } + + function fromWorldVec(arr, time) { + var toWorldMat = this.getMatrix(time); + toWorldMat.props[12] = 0; + toWorldMat.props[13] = 0; + toWorldMat.props[14] = 0; + return this.invertPoint(toWorldMat, arr); + } + + function fromWorld(arr, time) { + var toWorldMat = this.getMatrix(time); + return this.invertPoint(toWorldMat, arr); + } + + function applyPoint(matrix, arr) { + if (this._elem.hierarchy && this._elem.hierarchy.length) { + var i; + var len = this._elem.hierarchy.length; + for (i = 0; i < len; i += 1) { + this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); + } + } + return matrix.applyToPointArray(arr[0], arr[1], arr[2] || 0); + } + + function invertPoint(matrix, arr) { + if (this._elem.hierarchy && this._elem.hierarchy.length) { + var i; + var len = this._elem.hierarchy.length; + for (i = 0; i < len; i += 1) { + this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); + } + } + return matrix.inversePoint(arr); + } + + function fromComp(arr) { + var toWorldMat = new Matrix(); + toWorldMat.reset(); + this._elem.finalTransform.mProp.applyToMatrix(toWorldMat); + if (this._elem.hierarchy && this._elem.hierarchy.length) { + var i; + var len = this._elem.hierarchy.length; + for (i = 0; i < len; i += 1) { + this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(toWorldMat); + } + return toWorldMat.inversePoint(arr); + } + return toWorldMat.inversePoint(arr); + } + + function sampleImage() { + return [1, 1, 1, 1]; + } + + return function (elem) { + var transformInterface; + + function _registerMaskInterface(maskManager) { + _thisLayerFunction.mask = new MaskManagerInterface(maskManager, elem); + } + function _registerEffectsInterface(effects) { + _thisLayerFunction.effect = effects; + } + + function _thisLayerFunction(name) { + switch (name) { + case 'ADBE Root Vectors Group': + case 'Contents': + case 2: + return _thisLayerFunction.shapeInterface; + case 1: + case 6: + case 'Transform': + case 'transform': + case 'ADBE Transform Group': + return transformInterface; + case 4: + case 'ADBE Effect Parade': + case 'effects': + case 'Effects': + return _thisLayerFunction.effect; + case 'ADBE Text Properties': + return _thisLayerFunction.textInterface; + default: + return null; + } + } + _thisLayerFunction.getMatrix = getMatrix; + _thisLayerFunction.invertPoint = invertPoint; + _thisLayerFunction.applyPoint = applyPoint; + _thisLayerFunction.toWorld = toWorld; + _thisLayerFunction.toWorldVec = toWorldVec; + _thisLayerFunction.fromWorld = fromWorld; + _thisLayerFunction.fromWorldVec = fromWorldVec; + _thisLayerFunction.toComp = toWorld; + _thisLayerFunction.fromComp = fromComp; + _thisLayerFunction.sampleImage = sampleImage; + _thisLayerFunction.sourceRectAtTime = elem.sourceRectAtTime.bind(elem); + _thisLayerFunction._elem = elem; + transformInterface = TransformExpressionInterface(elem.finalTransform.mProp); + var anchorPointDescriptor = getDescriptor(transformInterface, 'anchorPoint'); + Object.defineProperties(_thisLayerFunction, { + hasParent: { + get: function () { + return elem.hierarchy.length; + }, + }, + parent: { + get: function () { + return elem.hierarchy[0].layerInterface; + }, + }, + rotation: getDescriptor(transformInterface, 'rotation'), + scale: getDescriptor(transformInterface, 'scale'), + position: getDescriptor(transformInterface, 'position'), + opacity: getDescriptor(transformInterface, 'opacity'), + anchorPoint: anchorPointDescriptor, + anchor_point: anchorPointDescriptor, + transform: { + get: function () { + return transformInterface; + }, + }, + active: { + get: function () { + return elem.isInRange; + }, + }, + }); + + _thisLayerFunction.startTime = elem.data.st; + _thisLayerFunction.index = elem.data.ind; + _thisLayerFunction.source = elem.data.refId; + _thisLayerFunction.height = elem.data.ty === 0 ? elem.data.h : 100; + _thisLayerFunction.width = elem.data.ty === 0 ? elem.data.w : 100; + _thisLayerFunction.inPoint = elem.data.ip / elem.comp.globalData.frameRate; + _thisLayerFunction.outPoint = elem.data.op / elem.comp.globalData.frameRate; + _thisLayerFunction._name = elem.data.nm; + + _thisLayerFunction.registerMaskInterface = _registerMaskInterface; + _thisLayerFunction.registerEffectsInterface = _registerEffectsInterface; + return _thisLayerFunction; + }; + }()); + + const propertyGroupFactory = (function () { + return function (interfaceFunction, parentPropertyGroup) { + return function (val) { + val = val === undefined ? 1 : val; + if (val <= 0) { + return interfaceFunction; + } + return parentPropertyGroup(val - 1); + }; + }; + }()); + + const PropertyInterface = (function () { + return function (propertyName, propertyGroup) { + var interfaceFunction = { + _name: propertyName, + }; + + function _propertyGroup(val) { + val = val === undefined ? 1 : val; + if (val <= 0) { + return interfaceFunction; + } + return propertyGroup(val - 1); + } + + return _propertyGroup; + }; + }()); + + const EffectsExpressionInterface = (function () { + var ob = { + createEffectsInterface: createEffectsInterface, + }; + + function createEffectsInterface(elem, propertyGroup) { + if (elem.effectsManager) { + var effectElements = []; + var effectsData = elem.data.ef; + var i; + var len = elem.effectsManager.effectElements.length; + for (i = 0; i < len; i += 1) { + effectElements.push(createGroupInterface(effectsData[i], elem.effectsManager.effectElements[i], propertyGroup, elem)); + } + + var effects = elem.data.ef || []; + var groupInterface = function (name) { + i = 0; + len = effects.length; + while (i < len) { + if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { + return effectElements[i]; + } + i += 1; + } + return null; + }; + Object.defineProperty(groupInterface, 'numProperties', { + get: function () { + return effects.length; + }, + }); + return groupInterface; + } + return null; + } + + function createGroupInterface(data, elements, propertyGroup, elem) { + function groupInterface(name) { + var effects = data.ef; + var i = 0; + var len = effects.length; + while (i < len) { + if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { + if (effects[i].ty === 5) { + return effectElements[i]; + } + return effectElements[i](); + } + i += 1; + } + throw new Error(); + } + var _propertyGroup = propertyGroupFactory(groupInterface, propertyGroup); + + var effectElements = []; + var i; + var len = data.ef.length; + for (i = 0; i < len; i += 1) { + if (data.ef[i].ty === 5) { + effectElements.push(createGroupInterface(data.ef[i], elements.effectElements[i], elements.effectElements[i].propertyGroup, elem)); + } else { + effectElements.push(createValueInterface(elements.effectElements[i], data.ef[i].ty, elem, _propertyGroup)); + } + } + + if (data.mn === 'ADBE Color Control') { + Object.defineProperty(groupInterface, 'color', { + get: function () { + return effectElements[0](); + }, + }); + } + Object.defineProperties(groupInterface, { + numProperties: { + get: function () { + return data.np; + }, + }, + _name: { value: data.nm }, + propertyGroup: { value: _propertyGroup }, + }); + groupInterface.enabled = data.en !== 0; + groupInterface.active = groupInterface.enabled; + return groupInterface; + } + + function createValueInterface(element, type, elem, propertyGroup) { + var expressionProperty = ExpressionPropertyInterface(element.p); + function interfaceFunction() { + if (type === 10) { + return elem.comp.compInterface(element.p.v); + } + return expressionProperty(); + } + + if (element.p.setGroupProperty) { + element.p.setGroupProperty(PropertyInterface('', propertyGroup)); + } + + return interfaceFunction; + } + + return ob; + }()); + + const CompExpressionInterface = (function () { + return function (comp) { + function _thisLayerFunction(name) { + var i = 0; + var len = comp.layers.length; + while (i < len) { + if (comp.layers[i].nm === name || comp.layers[i].ind === name) { + return comp.elements[i].layerInterface; + } + i += 1; + } + return null; + // return {active:false}; + } + Object.defineProperty(_thisLayerFunction, '_name', { value: comp.data.nm }); + _thisLayerFunction.layer = _thisLayerFunction; + _thisLayerFunction.pixelAspect = 1; + _thisLayerFunction.height = comp.data.h || comp.globalData.compSize.h; + _thisLayerFunction.width = comp.data.w || comp.globalData.compSize.w; + _thisLayerFunction.pixelAspect = 1; + _thisLayerFunction.frameDuration = 1 / comp.globalData.frameRate; + _thisLayerFunction.displayStartTime = 0; + _thisLayerFunction.numLayers = comp.layers.length; + return _thisLayerFunction; + }; + }()); + + const ShapePathInterface = ( + + function () { + return function pathInterfaceFactory(shape, view, propertyGroup) { + var prop = view.sh; + + function interfaceFunction(val) { + if (val === 'Shape' || val === 'shape' || val === 'Path' || val === 'path' || val === 'ADBE Vector Shape' || val === 2) { + return interfaceFunction.path; + } + return null; + } + + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + prop.setGroupProperty(PropertyInterface('Path', _propertyGroup)); + Object.defineProperties(interfaceFunction, { + path: { + get: function () { + if (prop.k) { + prop.getValue(); + } + return prop; + }, + }, + shape: { + get: function () { + if (prop.k) { + prop.getValue(); + } + return prop; + }, + }, + _name: { value: shape.nm }, + ix: { value: shape.ix }, + propertyIndex: { value: shape.ix }, + mn: { value: shape.mn }, + propertyGroup: { value: propertyGroup }, + }); + return interfaceFunction; + }; + }() + ); + + const ShapeExpressionInterface = (function () { + function iterateElements(shapes, view, propertyGroup) { + var arr = []; + var i; + var len = shapes ? shapes.length : 0; + for (i = 0; i < len; i += 1) { + if (shapes[i].ty === 'gr') { + arr.push(groupInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'fl') { + arr.push(fillInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'st') { + arr.push(strokeInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'tm') { + arr.push(trimInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'tr') { + // arr.push(transformInterfaceFactory(shapes[i],view[i],propertyGroup)); + } else if (shapes[i].ty === 'el') { + arr.push(ellipseInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'sr') { + arr.push(starInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'sh') { + arr.push(ShapePathInterface(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'rc') { + arr.push(rectInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'rd') { + arr.push(roundedInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'rp') { + arr.push(repeaterInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'gf') { + arr.push(gradientFillInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else { + arr.push(defaultInterfaceFactory(shapes[i], view[i], propertyGroup)); + } + } + return arr; + } + + function contentsInterfaceFactory(shape, view, propertyGroup) { + var interfaces; + var interfaceFunction = function _interfaceFunction(value) { + var i = 0; + var len = interfaces.length; + while (i < len) { + if (interfaces[i]._name === value || interfaces[i].mn === value || interfaces[i].propertyIndex === value || interfaces[i].ix === value || interfaces[i].ind === value) { + return interfaces[i]; + } + i += 1; + } + if (typeof value === 'number') { + return interfaces[value - 1]; + } + return null; + }; + + interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + interfaces = iterateElements(shape.it, view.it, interfaceFunction.propertyGroup); + interfaceFunction.numProperties = interfaces.length; + var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); + interfaceFunction.transform = transformInterface; + interfaceFunction.propertyIndex = shape.cix; + interfaceFunction._name = shape.nm; + + return interfaceFunction; + } + + function groupInterfaceFactory(shape, view, propertyGroup) { + var interfaceFunction = function _interfaceFunction(value) { + switch (value) { + case 'ADBE Vectors Group': + case 'Contents': + case 2: + return interfaceFunction.content; + // Not necessary for now. Keeping them here in case a new case appears + // case 'ADBE Vector Transform Group': + // case 3: + default: + return interfaceFunction.transform; + } + }; + interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var content = contentsInterfaceFactory(shape, view, interfaceFunction.propertyGroup); + var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); + interfaceFunction.content = content; + interfaceFunction.transform = transformInterface; + Object.defineProperty(interfaceFunction, '_name', { + get: function () { + return shape.nm; + }, + }); + // interfaceFunction.content = interfaceFunction; + interfaceFunction.numProperties = shape.np; + interfaceFunction.propertyIndex = shape.ix; + interfaceFunction.nm = shape.nm; + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function fillInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(val) { + if (val === 'Color' || val === 'color') { + return interfaceFunction.color; + } if (val === 'Opacity' || val === 'opacity') { + return interfaceFunction.opacity; + } + return null; + } + Object.defineProperties(interfaceFunction, { + color: { + get: ExpressionPropertyInterface(view.c), + }, + opacity: { + get: ExpressionPropertyInterface(view.o), + }, + _name: { value: shape.nm }, + mn: { value: shape.mn }, + }); + + view.c.setGroupProperty(PropertyInterface('Color', propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); + return interfaceFunction; + } + + function gradientFillInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(val) { + if (val === 'Start Point' || val === 'start point') { + return interfaceFunction.startPoint; + } + if (val === 'End Point' || val === 'end point') { + return interfaceFunction.endPoint; + } + if (val === 'Opacity' || val === 'opacity') { + return interfaceFunction.opacity; + } + return null; + } + Object.defineProperties(interfaceFunction, { + startPoint: { + get: ExpressionPropertyInterface(view.s), + }, + endPoint: { + get: ExpressionPropertyInterface(view.e), + }, + opacity: { + get: ExpressionPropertyInterface(view.o), + }, + type: { + get: function () { + return 'a'; + }, + }, + _name: { value: shape.nm }, + mn: { value: shape.mn }, + }); + + view.s.setGroupProperty(PropertyInterface('Start Point', propertyGroup)); + view.e.setGroupProperty(PropertyInterface('End Point', propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); + return interfaceFunction; + } + function defaultInterfaceFactory() { + function interfaceFunction() { + return null; + } + return interfaceFunction; + } + + function strokeInterfaceFactory(shape, view, propertyGroup) { + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var _dashPropertyGroup = propertyGroupFactory(dashOb, _propertyGroup); + function addPropertyToDashOb(i) { + Object.defineProperty(dashOb, shape.d[i].nm, { + get: ExpressionPropertyInterface(view.d.dataProps[i].p), + }); + } + var i; + var len = shape.d ? shape.d.length : 0; + var dashOb = {}; + for (i = 0; i < len; i += 1) { + addPropertyToDashOb(i); + view.d.dataProps[i].p.setGroupProperty(_dashPropertyGroup); + } + + function interfaceFunction(val) { + if (val === 'Color' || val === 'color') { + return interfaceFunction.color; + } if (val === 'Opacity' || val === 'opacity') { + return interfaceFunction.opacity; + } if (val === 'Stroke Width' || val === 'stroke width') { + return interfaceFunction.strokeWidth; + } + return null; + } + Object.defineProperties(interfaceFunction, { + color: { + get: ExpressionPropertyInterface(view.c), + }, + opacity: { + get: ExpressionPropertyInterface(view.o), + }, + strokeWidth: { + get: ExpressionPropertyInterface(view.w), + }, + dash: { + get: function () { + return dashOb; + }, + }, + _name: { value: shape.nm }, + mn: { value: shape.mn }, + }); + + view.c.setGroupProperty(PropertyInterface('Color', _propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); + view.w.setGroupProperty(PropertyInterface('Stroke Width', _propertyGroup)); + return interfaceFunction; + } + + function trimInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(val) { + if (val === shape.e.ix || val === 'End' || val === 'end') { + return interfaceFunction.end; + } + if (val === shape.s.ix) { + return interfaceFunction.start; + } + if (val === shape.o.ix) { + return interfaceFunction.offset; + } + return null; + } + + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + interfaceFunction.propertyIndex = shape.ix; + + view.s.setGroupProperty(PropertyInterface('Start', _propertyGroup)); + view.e.setGroupProperty(PropertyInterface('End', _propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); + interfaceFunction.propertyIndex = shape.ix; + interfaceFunction.propertyGroup = propertyGroup; + + Object.defineProperties(interfaceFunction, { + start: { + get: ExpressionPropertyInterface(view.s), + }, + end: { + get: ExpressionPropertyInterface(view.e), + }, + offset: { + get: ExpressionPropertyInterface(view.o), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function transformInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.a.ix === value || value === 'Anchor Point') { + return interfaceFunction.anchorPoint; + } + if (shape.o.ix === value || value === 'Opacity') { + return interfaceFunction.opacity; + } + if (shape.p.ix === value || value === 'Position') { + return interfaceFunction.position; + } + if (shape.r.ix === value || value === 'Rotation' || value === 'ADBE Vector Rotation') { + return interfaceFunction.rotation; + } + if (shape.s.ix === value || value === 'Scale') { + return interfaceFunction.scale; + } + if ((shape.sk && shape.sk.ix === value) || value === 'Skew') { + return interfaceFunction.skew; + } + if ((shape.sa && shape.sa.ix === value) || value === 'Skew Axis') { + return interfaceFunction.skewAxis; + } + return null; + } + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + view.transform.mProps.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); + view.transform.mProps.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + view.transform.mProps.a.setGroupProperty(PropertyInterface('Anchor Point', _propertyGroup)); + view.transform.mProps.s.setGroupProperty(PropertyInterface('Scale', _propertyGroup)); + view.transform.mProps.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); + if (view.transform.mProps.sk) { + view.transform.mProps.sk.setGroupProperty(PropertyInterface('Skew', _propertyGroup)); + view.transform.mProps.sa.setGroupProperty(PropertyInterface('Skew Angle', _propertyGroup)); + } + view.transform.op.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); + Object.defineProperties(interfaceFunction, { + opacity: { + get: ExpressionPropertyInterface(view.transform.mProps.o), + }, + position: { + get: ExpressionPropertyInterface(view.transform.mProps.p), + }, + anchorPoint: { + get: ExpressionPropertyInterface(view.transform.mProps.a), + }, + scale: { + get: ExpressionPropertyInterface(view.transform.mProps.s), + }, + rotation: { + get: ExpressionPropertyInterface(view.transform.mProps.r), + }, + skew: { + get: ExpressionPropertyInterface(view.transform.mProps.sk), + }, + skewAxis: { + get: ExpressionPropertyInterface(view.transform.mProps.sa), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.ty = 'tr'; + interfaceFunction.mn = shape.mn; + interfaceFunction.propertyGroup = propertyGroup; + return interfaceFunction; + } + + function ellipseInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.p.ix === value) { + return interfaceFunction.position; + } + if (shape.s.ix === value) { + return interfaceFunction.size; + } + return null; + } + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + interfaceFunction.propertyIndex = shape.ix; + var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; + prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); + prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + + Object.defineProperties(interfaceFunction, { + size: { + get: ExpressionPropertyInterface(prop.s), + }, + position: { + get: ExpressionPropertyInterface(prop.p), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function starInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.p.ix === value) { + return interfaceFunction.position; + } + if (shape.r.ix === value) { + return interfaceFunction.rotation; + } + if (shape.pt.ix === value) { + return interfaceFunction.points; + } + if (shape.or.ix === value || value === 'ADBE Vector Star Outer Radius') { + return interfaceFunction.outerRadius; + } + if (shape.os.ix === value) { + return interfaceFunction.outerRoundness; + } + if (shape.ir && (shape.ir.ix === value || value === 'ADBE Vector Star Inner Radius')) { + return interfaceFunction.innerRadius; + } + if (shape.is && shape.is.ix === value) { + return interfaceFunction.innerRoundness; + } + return null; + } + + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; + interfaceFunction.propertyIndex = shape.ix; + prop.or.setGroupProperty(PropertyInterface('Outer Radius', _propertyGroup)); + prop.os.setGroupProperty(PropertyInterface('Outer Roundness', _propertyGroup)); + prop.pt.setGroupProperty(PropertyInterface('Points', _propertyGroup)); + prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); + if (shape.ir) { + prop.ir.setGroupProperty(PropertyInterface('Inner Radius', _propertyGroup)); + prop.is.setGroupProperty(PropertyInterface('Inner Roundness', _propertyGroup)); + } + + Object.defineProperties(interfaceFunction, { + position: { + get: ExpressionPropertyInterface(prop.p), + }, + rotation: { + get: ExpressionPropertyInterface(prop.r), + }, + points: { + get: ExpressionPropertyInterface(prop.pt), + }, + outerRadius: { + get: ExpressionPropertyInterface(prop.or), + }, + outerRoundness: { + get: ExpressionPropertyInterface(prop.os), + }, + innerRadius: { + get: ExpressionPropertyInterface(prop.ir), + }, + innerRoundness: { + get: ExpressionPropertyInterface(prop.is), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function rectInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.p.ix === value) { + return interfaceFunction.position; + } + if (shape.r.ix === value) { + return interfaceFunction.roundness; + } + if (shape.s.ix === value || value === 'Size' || value === 'ADBE Vector Rect Size') { + return interfaceFunction.size; + } + return null; + } + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + + var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; + interfaceFunction.propertyIndex = shape.ix; + prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); + prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); + + Object.defineProperties(interfaceFunction, { + position: { + get: ExpressionPropertyInterface(prop.p), + }, + roundness: { + get: ExpressionPropertyInterface(prop.r), + }, + size: { + get: ExpressionPropertyInterface(prop.s), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function roundedInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.r.ix === value || value === 'Round Corners 1') { + return interfaceFunction.radius; + } + return null; + } + + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var prop = view; + interfaceFunction.propertyIndex = shape.ix; + prop.rd.setGroupProperty(PropertyInterface('Radius', _propertyGroup)); + + Object.defineProperties(interfaceFunction, { + radius: { + get: ExpressionPropertyInterface(prop.rd), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function repeaterInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.c.ix === value || value === 'Copies') { + return interfaceFunction.copies; + } if (shape.o.ix === value || value === 'Offset') { + return interfaceFunction.offset; + } + return null; + } + + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var prop = view; + interfaceFunction.propertyIndex = shape.ix; + prop.c.setGroupProperty(PropertyInterface('Copies', _propertyGroup)); + prop.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); + Object.defineProperties(interfaceFunction, { + copies: { + get: ExpressionPropertyInterface(prop.c), + }, + offset: { + get: ExpressionPropertyInterface(prop.o), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + return function (shapes, view, propertyGroup) { + var interfaces; + function _interfaceFunction(value) { + if (typeof value === 'number') { + value = value === undefined ? 1 : value; + if (value === 0) { + return propertyGroup; + } + return interfaces[value - 1]; + } + var i = 0; + var len = interfaces.length; + while (i < len) { + if (interfaces[i]._name === value) { + return interfaces[i]; + } + i += 1; + } + return null; + } + function parentGroupWrapper() { + return propertyGroup; + } + _interfaceFunction.propertyGroup = propertyGroupFactory(_interfaceFunction, parentGroupWrapper); + interfaces = iterateElements(shapes, view, _interfaceFunction.propertyGroup); + _interfaceFunction.numProperties = interfaces.length; + _interfaceFunction._name = 'Contents'; + return _interfaceFunction; + }; + }()); + + const TextExpressionInterface = (function () { + return function (elem) { + var _prevValue; + var _sourceText; + function _thisLayerFunction(name) { + switch (name) { + case 'ADBE Text Document': + return _thisLayerFunction.sourceText; + default: + return null; + } + } + Object.defineProperty(_thisLayerFunction, 'sourceText', { + get: function () { + elem.textProperty.getValue(); + var stringValue = elem.textProperty.currentData.t; + if (stringValue !== _prevValue) { + elem.textProperty.currentData.t = _prevValue; + _sourceText = new String(stringValue); // eslint-disable-line no-new-wrappers + // If stringValue is an empty string, eval returns undefined, so it has to be returned as a String primitive + _sourceText.value = stringValue || new String(stringValue); // eslint-disable-line no-new-wrappers + } + return _sourceText; + }, + }); + return _thisLayerFunction; + }; + }()); + + const getBlendMode = (function () { + var blendModeEnums = { + 0: 'source-over', + 1: 'multiply', + 2: 'screen', + 3: 'overlay', + 4: 'darken', + 5: 'lighten', + 6: 'color-dodge', + 7: 'color-burn', + 8: 'hard-light', + 9: 'soft-light', + 10: 'difference', + 11: 'exclusion', + 12: 'hue', + 13: 'saturation', + 14: 'color', + 15: 'luminosity', + }; + + return function (mode) { + return blendModeEnums[mode] || ''; + }; + }()); + + function SliderEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); + } + function AngleEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); + } + function ColorEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); + } + function PointEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); + } + function LayerIndexEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); + } + function MaskIndexEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); + } + function CheckboxEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); + } + function NoValueEffect() { + this.p = {}; + } + + function EffectsManager(data, element) { + var effects = data.ef || []; + this.effectElements = []; + var i; + var len = effects.length; + var effectItem; + for (i = 0; i < len; i += 1) { + effectItem = new GroupEffect(effects[i], element); + this.effectElements.push(effectItem); + } + } + + function GroupEffect(data, element) { + this.init(data, element); + } + + extendPrototype([DynamicPropertyContainer], GroupEffect); + + GroupEffect.prototype.getValue = GroupEffect.prototype.iterateDynamicProperties; + + GroupEffect.prototype.init = function (data, element) { + this.data = data; + this.effectElements = []; + this.initDynamicPropertyContainer(element); + var i; + var len = this.data.ef.length; + var eff; + var effects = this.data.ef; + for (i = 0; i < len; i += 1) { + eff = null; + switch (effects[i].ty) { + case 0: + eff = new SliderEffect(effects[i], element, this); + break; + case 1: + eff = new AngleEffect(effects[i], element, this); + break; + case 2: + eff = new ColorEffect(effects[i], element, this); + break; + case 3: + eff = new PointEffect(effects[i], element, this); + break; + case 4: + case 7: + eff = new CheckboxEffect(effects[i], element, this); + break; + case 10: + eff = new LayerIndexEffect(effects[i], element, this); + break; + case 11: + eff = new MaskIndexEffect(effects[i], element, this); + break; + case 5: + eff = new EffectsManager(effects[i], element, this); + break; + // case 6: + default: + eff = new NoValueEffect(effects[i], element, this); + break; + } + if (eff) { + this.effectElements.push(eff); + } + } + }; + + function BaseElement() { + } + + BaseElement.prototype = { + checkMasks: function () { + if (!this.data.hasMask) { + return false; + } + var i = 0; + var len = this.data.masksProperties.length; + while (i < len) { + if ((this.data.masksProperties[i].mode !== 'n' && this.data.masksProperties[i].cl !== false)) { + return true; + } + i += 1; + } + return false; + }, + initExpressions: function () { + this.layerInterface = LayerExpressionInterface(this); + if (this.data.hasMask && this.maskManager) { + this.layerInterface.registerMaskInterface(this.maskManager); + } + var effectsInterface = EffectsExpressionInterface.createEffectsInterface(this, this.layerInterface); + this.layerInterface.registerEffectsInterface(effectsInterface); + + if (this.data.ty === 0 || this.data.xt) { + this.compInterface = CompExpressionInterface(this); + } else if (this.data.ty === 4) { + this.layerInterface.shapeInterface = ShapeExpressionInterface(this.shapesData, this.itemsData, this.layerInterface); + this.layerInterface.content = this.layerInterface.shapeInterface; + } else if (this.data.ty === 5) { + this.layerInterface.textInterface = TextExpressionInterface(this); + this.layerInterface.text = this.layerInterface.textInterface; + } + }, + setBlendMode: function () { + var blendModeValue = getBlendMode(this.data.bm); + var elem = this.baseElement || this.layerElement; + + elem.style['mix-blend-mode'] = blendModeValue; + }, + initBaseData: function (data, globalData, comp) { + this.globalData = globalData; + this.comp = comp; + this.data = data; + this.layerId = createElementID(); + + // Stretch factor for old animations missing this property. + if (!this.data.sr) { + this.data.sr = 1; + } + // effects manager + this.effectsManager = new EffectsManager(this.data, this, this.dynamicProperties); + }, + getType: function () { + return this.type; + }, + sourceRectAtTime: function () {}, + }; + + /** + * @file + * Handles element's layer frame update. + * Checks layer in point and out point + * + */ + + function FrameElement() {} + + FrameElement.prototype = { + /** + * @function + * Initializes frame related properties. + * + */ + initFrame: function () { + // set to true when inpoint is rendered + this._isFirstFrame = false; + // list of animated properties + this.dynamicProperties = []; + // If layer has been modified in current tick this will be true + this._mdf = false; + }, + /** + * @function + * Calculates all dynamic values + * + * @param {number} num + * current frame number in Layer's time + * @param {boolean} isVisible + * if layers is currently in range + * + */ + prepareProperties: function (num, isVisible) { + var i; + var len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + if (isVisible || (this._isParent && this.dynamicProperties[i].propType === 'transform')) { + this.dynamicProperties[i].getValue(); + if (this.dynamicProperties[i]._mdf) { + this.globalData._mdf = true; + this._mdf = true; + } + } + } + }, + addDynamicProperty: function (prop) { + if (this.dynamicProperties.indexOf(prop) === -1) { + this.dynamicProperties.push(prop); + } + }, + }; + + const FootageInterface = (function () { + var outlineInterfaceFactory = (function (elem) { + var currentPropertyName = ''; + var currentProperty = elem.getFootageData(); + function init() { + currentPropertyName = ''; + currentProperty = elem.getFootageData(); + return searchProperty; + } + function searchProperty(value) { + if (currentProperty[value]) { + currentPropertyName = value; + currentProperty = currentProperty[value]; + if (typeof currentProperty === 'object') { + return searchProperty; + } + return currentProperty; + } + var propertyNameIndex = value.indexOf(currentPropertyName); + if (propertyNameIndex !== -1) { + var index = parseInt(value.substr(propertyNameIndex + currentPropertyName.length), 10); + currentProperty = currentProperty[index]; + if (typeof currentProperty === 'object') { + return searchProperty; + } + return currentProperty; + } + return ''; + } + return init; + }); + + var dataInterfaceFactory = function (elem) { + function interfaceFunction(value) { + if (value === 'Outline') { + return interfaceFunction.outlineInterface(); + } + return null; + } + + interfaceFunction._name = 'Outline'; + interfaceFunction.outlineInterface = outlineInterfaceFactory(elem); + return interfaceFunction; + }; + + return function (elem) { + function _interfaceFunction(value) { + if (value === 'Data') { + return _interfaceFunction.dataInterface; + } + return null; + } + + _interfaceFunction._name = 'Data'; + _interfaceFunction.dataInterface = dataInterfaceFactory(elem); + return _interfaceFunction; + }; + }()); + + function FootageElement(data, globalData, comp) { + this.initFrame(); + this.initRenderable(); + this.assetData = globalData.getAssetData(data.refId); + this.footageData = globalData.imageLoader.getAsset(this.assetData); + this.initBaseData(data, globalData, comp); + } + + FootageElement.prototype.prepareFrame = function () { + }; + + extendPrototype([RenderableElement, BaseElement, FrameElement], FootageElement); + + FootageElement.prototype.getBaseElement = function () { + return null; + }; + + FootageElement.prototype.renderFrame = function () { + }; + + FootageElement.prototype.destroy = function () { + }; + + FootageElement.prototype.initExpressions = function () { + this.layerInterface = FootageInterface(this); + }; + + FootageElement.prototype.getFootageData = function () { + return this.footageData; + }; + + function AudioElement(data, globalData, comp) { + this.initFrame(); + this.initRenderable(); + this.assetData = globalData.getAssetData(data.refId); + this.initBaseData(data, globalData, comp); + this._isPlaying = false; + this._canPlay = false; + var assetPath = this.globalData.getAssetsPath(this.assetData); + this.audio = this.globalData.audioController.createAudio(assetPath); + this._currentTime = 0; + this.globalData.audioController.addAudio(this); + this._volumeMultiplier = 1; + this._volume = 1; + this._previousVolume = null; + this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; + this.lv = PropertyFactory.getProp(this, data.au && data.au.lv ? data.au.lv : { k: [100] }, 1, 0.01, this); + } + + AudioElement.prototype.prepareFrame = function (num) { + this.prepareRenderableFrame(num, true); + this.prepareProperties(num, true); + if (!this.tm._placeholder) { + var timeRemapped = this.tm.v; + this._currentTime = timeRemapped; + } else { + this._currentTime = num / this.data.sr; + } + this._volume = this.lv.v[0]; + var totalVolume = this._volume * this._volumeMultiplier; + if (this._previousVolume !== totalVolume) { + this._previousVolume = totalVolume; + this.audio.volume(totalVolume); + } + }; + + extendPrototype([RenderableElement, BaseElement, FrameElement], AudioElement); + + AudioElement.prototype.renderFrame = function () { + if (this.isInRange && this._canPlay) { + if (!this._isPlaying) { + this.audio.play(); + this.audio.seek(this._currentTime / this.globalData.frameRate); + this._isPlaying = true; + } else if (!this.audio.playing() + || Math.abs(this._currentTime / this.globalData.frameRate - this.audio.seek()) > 0.1 + ) { + this.audio.seek(this._currentTime / this.globalData.frameRate); + } + } + }; + + AudioElement.prototype.show = function () { + // this.audio.play() + }; + + AudioElement.prototype.hide = function () { + this.audio.pause(); + this._isPlaying = false; + }; + + AudioElement.prototype.pause = function () { + this.audio.pause(); + this._isPlaying = false; + this._canPlay = false; + }; + + AudioElement.prototype.resume = function () { + this._canPlay = true; + }; + + AudioElement.prototype.setRate = function (rateValue) { + this.audio.rate(rateValue); + }; + + AudioElement.prototype.volume = function (volumeValue) { + this._volumeMultiplier = volumeValue; + this._previousVolume = volumeValue * this._volume; + this.audio.volume(this._previousVolume); + }; + + AudioElement.prototype.getBaseElement = function () { + return null; + }; + + AudioElement.prototype.destroy = function () { + }; + + AudioElement.prototype.sourceRectAtTime = function () { + }; + + AudioElement.prototype.initExpressions = function () { + }; + + function BaseRenderer() {} + BaseRenderer.prototype.checkLayers = function (num) { + var i; + var len = this.layers.length; + var data; + this.completeLayers = true; + for (i = len - 1; i >= 0; i -= 1) { + if (!this.elements[i]) { + data = this.layers[i]; + if (data.ip - data.st <= (num - this.layers[i].st) && data.op - data.st > (num - this.layers[i].st)) { + this.buildItem(i); + } + } + this.completeLayers = this.elements[i] ? this.completeLayers : false; + } + this.checkPendingElements(); + }; + + BaseRenderer.prototype.createItem = function (layer) { + switch (layer.ty) { + case 2: + return this.createImage(layer); + case 0: + return this.createComp(layer); + case 1: + return this.createSolid(layer); + case 3: + return this.createNull(layer); + case 4: + return this.createShape(layer); + case 5: + return this.createText(layer); + case 6: + return this.createAudio(layer); + case 13: + return this.createCamera(layer); + case 15: + return this.createFootage(layer); + default: + return this.createNull(layer); + } + }; + + BaseRenderer.prototype.createCamera = function () { + throw new Error('You\'re using a 3d camera. Try the html renderer.'); + }; + + BaseRenderer.prototype.createAudio = function (data) { + return new AudioElement(data, this.globalData, this); + }; + + BaseRenderer.prototype.createFootage = function (data) { + return new FootageElement(data, this.globalData, this); + }; + + BaseRenderer.prototype.buildAllItems = function () { + var i; + var len = this.layers.length; + for (i = 0; i < len; i += 1) { + this.buildItem(i); + } + this.checkPendingElements(); + }; + + BaseRenderer.prototype.includeLayers = function (newLayers) { + this.completeLayers = false; + var i; + var len = newLayers.length; + var j; + var jLen = this.layers.length; + for (i = 0; i < len; i += 1) { + j = 0; + while (j < jLen) { + if (this.layers[j].id === newLayers[i].id) { + this.layers[j] = newLayers[i]; + break; + } + j += 1; + } + } + }; + + BaseRenderer.prototype.setProjectInterface = function (pInterface) { + this.globalData.projectInterface = pInterface; + }; + + BaseRenderer.prototype.initItems = function () { + if (!this.globalData.progressiveLoad) { + this.buildAllItems(); + } + }; + BaseRenderer.prototype.buildElementParenting = function (element, parentName, hierarchy) { + var elements = this.elements; + var layers = this.layers; + var i = 0; + var len = layers.length; + while (i < len) { + if (layers[i].ind == parentName) { // eslint-disable-line eqeqeq + if (!elements[i] || elements[i] === true) { + this.buildItem(i); + this.addPendingElement(element); + } else { + hierarchy.push(elements[i]); + elements[i].setAsParent(); + if (layers[i].parent !== undefined) { + this.buildElementParenting(element, layers[i].parent, hierarchy); + } else { + element.setHierarchy(hierarchy); + } + } + } + i += 1; + } + }; + + BaseRenderer.prototype.addPendingElement = function (element) { + this.pendingElements.push(element); + }; + + BaseRenderer.prototype.searchExtraCompositions = function (assets) { + var i; + var len = assets.length; + for (i = 0; i < len; i += 1) { + if (assets[i].xt) { + var comp = this.createComp(assets[i]); + comp.initExpressions(); + this.globalData.projectInterface.registerComposition(comp); + } + } + }; + + BaseRenderer.prototype.getElementByPath = function (path) { + var pathValue = path.shift(); + var element; + if (typeof pathValue === 'number') { + element = this.elements[pathValue]; + } else { + var i; + var len = this.elements.length; + for (i = 0; i < len; i += 1) { + if (this.elements[i].data.nm === pathValue) { + element = this.elements[i]; + break; + } + } + } + if (path.length === 0) { + return element; + } + return element.getElementByPath(path); + }; + + BaseRenderer.prototype.setupGlobalData = function (animData, fontsContainer) { + this.globalData.fontManager = new FontManager(); + this.globalData.fontManager.addChars(animData.chars); + this.globalData.fontManager.addFonts(animData.fonts, fontsContainer); + this.globalData.getAssetData = this.animationItem.getAssetData.bind(this.animationItem); + this.globalData.getAssetsPath = this.animationItem.getAssetsPath.bind(this.animationItem); + this.globalData.imageLoader = this.animationItem.imagePreloader; + this.globalData.audioController = this.animationItem.audioController; + this.globalData.frameId = 0; + this.globalData.frameRate = animData.fr; + this.globalData.nm = animData.nm; + this.globalData.compSize = { + w: animData.w, + h: animData.h, + }; + }; + + function TransformElement() {} + + TransformElement.prototype = { + initTransform: function () { + this.finalTransform = { + mProp: this.data.ks ? TransformPropertyFactory.getTransformProperty(this, this.data.ks, this) : { o: 0 }, + _matMdf: false, + _opMdf: false, + mat: new Matrix(), + }; + if (this.data.ao) { + this.finalTransform.mProp.autoOriented = true; + } + + // TODO: check TYPE 11: Guided elements + if (this.data.ty !== 11) { + // this.createElements(); + } + }, + renderTransform: function () { + this.finalTransform._opMdf = this.finalTransform.mProp.o._mdf || this._isFirstFrame; + this.finalTransform._matMdf = this.finalTransform.mProp._mdf || this._isFirstFrame; + + if (this.hierarchy) { + var mat; + var finalMat = this.finalTransform.mat; + var i = 0; + var len = this.hierarchy.length; + // Checking if any of the transformation matrices in the hierarchy chain has changed. + if (!this.finalTransform._matMdf) { + while (i < len) { + if (this.hierarchy[i].finalTransform.mProp._mdf) { + this.finalTransform._matMdf = true; + break; + } + i += 1; + } + } + + if (this.finalTransform._matMdf) { + mat = this.finalTransform.mProp.v.props; + finalMat.cloneFromProps(mat); + for (i = 0; i < len; i += 1) { + mat = this.hierarchy[i].finalTransform.mProp.v.props; + finalMat.transform(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5], mat[6], mat[7], mat[8], mat[9], mat[10], mat[11], mat[12], mat[13], mat[14], mat[15]); + } + } + } + }, + globalToLocal: function (pt) { + var transforms = []; + transforms.push(this.finalTransform); + var flag = true; + var comp = this.comp; + while (flag) { + if (comp.finalTransform) { + if (comp.data.hasMask) { + transforms.splice(0, 0, comp.finalTransform); + } + comp = comp.comp; + } else { + flag = false; + } + } + var i; + var len = transforms.length; + var ptNew; + for (i = 0; i < len; i += 1) { + ptNew = transforms[i].mat.applyToPointArray(0, 0, 0); + // ptNew = transforms[i].mat.applyToPointArray(pt[0],pt[1],pt[2]); + pt = [pt[0] - ptNew[0], pt[1] - ptNew[1], 0]; + } + return pt; + }, + mHelper: new Matrix(), + }; + + function MaskElement(data, element, globalData) { + this.data = data; + this.element = element; + this.globalData = globalData; + this.storedData = []; + this.masksProperties = this.data.masksProperties || []; + this.maskElement = null; + var defs = this.globalData.defs; + var i; + var len = this.masksProperties ? this.masksProperties.length : 0; + this.viewData = createSizedArray(len); + this.solidPath = ''; + + var path; + var properties = this.masksProperties; + var count = 0; + var currentMasks = []; + var j; + var jLen; + var layerId = createElementID(); + var rect; + var expansor; + var feMorph; + var x; + var maskType = 'clipPath'; + var maskRef = 'clip-path'; + for (i = 0; i < len; i += 1) { + if ((properties[i].mode !== 'a' && properties[i].mode !== 'n') || properties[i].inv || properties[i].o.k !== 100 || properties[i].o.x) { + maskType = 'mask'; + maskRef = 'mask'; + } + + if ((properties[i].mode === 's' || properties[i].mode === 'i') && count === 0) { + rect = createNS('rect'); + rect.setAttribute('fill', '#ffffff'); + rect.setAttribute('width', this.element.comp.data.w || 0); + rect.setAttribute('height', this.element.comp.data.h || 0); + currentMasks.push(rect); + } else { + rect = null; + } + + path = createNS('path'); + if (properties[i].mode === 'n') { + // TODO move this to a factory or to a constructor + this.viewData[i] = { + op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), + prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), + elem: path, + lastPath: '', + }; + defs.appendChild(path); + } else { + count += 1; + + path.setAttribute('fill', properties[i].mode === 's' ? '#000000' : '#ffffff'); + path.setAttribute('clip-rule', 'nonzero'); + var filterID; + + if (properties[i].x.k !== 0) { + maskType = 'mask'; + maskRef = 'mask'; + x = PropertyFactory.getProp(this.element, properties[i].x, 0, null, this.element); + filterID = createElementID(); + expansor = createNS('filter'); + expansor.setAttribute('id', filterID); + feMorph = createNS('feMorphology'); + feMorph.setAttribute('operator', 'erode'); + feMorph.setAttribute('in', 'SourceGraphic'); + feMorph.setAttribute('radius', '0'); + expansor.appendChild(feMorph); + defs.appendChild(expansor); + path.setAttribute('stroke', properties[i].mode === 's' ? '#000000' : '#ffffff'); + } else { + feMorph = null; + x = null; + } + + // TODO move this to a factory or to a constructor + this.storedData[i] = { + elem: path, + x: x, + expan: feMorph, + lastPath: '', + lastOperator: '', + filterId: filterID, + lastRadius: 0, + }; + if (properties[i].mode === 'i') { + jLen = currentMasks.length; + var g = createNS('g'); + for (j = 0; j < jLen; j += 1) { + g.appendChild(currentMasks[j]); + } + var mask = createNS('mask'); + mask.setAttribute('mask-type', 'alpha'); + mask.setAttribute('id', layerId + '_' + count); + mask.appendChild(path); + defs.appendChild(mask); + g.setAttribute('mask', 'url(' + getLocationHref() + '#' + layerId + '_' + count + ')'); + + currentMasks.length = 0; + currentMasks.push(g); + } else { + currentMasks.push(path); + } + if (properties[i].inv && !this.solidPath) { + this.solidPath = this.createLayerSolidPath(); + } + // TODO move this to a factory or to a constructor + this.viewData[i] = { + elem: path, + lastPath: '', + op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), + prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), + invRect: rect, + }; + if (!this.viewData[i].prop.k) { + this.drawPath(properties[i], this.viewData[i].prop.v, this.viewData[i]); + } + } + } + + this.maskElement = createNS(maskType); + + len = currentMasks.length; + for (i = 0; i < len; i += 1) { + this.maskElement.appendChild(currentMasks[i]); + } + + if (count > 0) { + this.maskElement.setAttribute('id', layerId); + this.element.maskedElement.setAttribute(maskRef, 'url(' + getLocationHref() + '#' + layerId + ')'); + defs.appendChild(this.maskElement); + } + if (this.viewData.length) { + this.element.addRenderableComponent(this); + } + } + + MaskElement.prototype.getMaskProperty = function (pos) { + return this.viewData[pos].prop; + }; + + MaskElement.prototype.renderFrame = function (isFirstFrame) { + var finalMat = this.element.finalTransform.mat; + var i; + var len = this.masksProperties.length; + for (i = 0; i < len; i += 1) { + if (this.viewData[i].prop._mdf || isFirstFrame) { + this.drawPath(this.masksProperties[i], this.viewData[i].prop.v, this.viewData[i]); + } + if (this.viewData[i].op._mdf || isFirstFrame) { + this.viewData[i].elem.setAttribute('fill-opacity', this.viewData[i].op.v); + } + if (this.masksProperties[i].mode !== 'n') { + if (this.viewData[i].invRect && (this.element.finalTransform.mProp._mdf || isFirstFrame)) { + this.viewData[i].invRect.setAttribute('transform', finalMat.getInverseMatrix().to2dCSS()); + } + if (this.storedData[i].x && (this.storedData[i].x._mdf || isFirstFrame)) { + var feMorph = this.storedData[i].expan; + if (this.storedData[i].x.v < 0) { + if (this.storedData[i].lastOperator !== 'erode') { + this.storedData[i].lastOperator = 'erode'; + this.storedData[i].elem.setAttribute('filter', 'url(' + getLocationHref() + '#' + this.storedData[i].filterId + ')'); + } + feMorph.setAttribute('radius', -this.storedData[i].x.v); + } else { + if (this.storedData[i].lastOperator !== 'dilate') { + this.storedData[i].lastOperator = 'dilate'; + this.storedData[i].elem.setAttribute('filter', null); + } + this.storedData[i].elem.setAttribute('stroke-width', this.storedData[i].x.v * 2); + } + } + } + } + }; + + MaskElement.prototype.getMaskelement = function () { + return this.maskElement; + }; + + MaskElement.prototype.createLayerSolidPath = function () { + var path = 'M0,0 '; + path += ' h' + this.globalData.compSize.w; + path += ' v' + this.globalData.compSize.h; + path += ' h-' + this.globalData.compSize.w; + path += ' v-' + this.globalData.compSize.h + ' '; + return path; + }; + + MaskElement.prototype.drawPath = function (pathData, pathNodes, viewData) { + var pathString = ' M' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; + var i; + var len; + len = pathNodes._length; + for (i = 1; i < len; i += 1) { + // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[i][0]+','+pathNodes.i[i][1] + " "+pathNodes.v[i][0]+','+pathNodes.v[i][1]; + pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[i][0] + ',' + pathNodes.i[i][1] + ' ' + pathNodes.v[i][0] + ',' + pathNodes.v[i][1]; + } + // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[0][0]+','+pathNodes.i[0][1] + " "+pathNodes.v[0][0]+','+pathNodes.v[0][1]; + if (pathNodes.c && len > 1) { + pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[0][0] + ',' + pathNodes.i[0][1] + ' ' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; + } + // pathNodes.__renderedString = pathString; + + if (viewData.lastPath !== pathString) { + var pathShapeValue = ''; + if (viewData.elem) { + if (pathNodes.c) { + pathShapeValue = pathData.inv ? this.solidPath + pathString : pathString; + } + viewData.elem.setAttribute('d', pathShapeValue); + } + viewData.lastPath = pathString; + } + }; + + MaskElement.prototype.destroy = function () { + this.element = null; + this.globalData = null; + this.maskElement = null; + this.data = null; + this.masksProperties = null; + }; + + const filtersFactory = (function () { + var ob = {}; + ob.createFilter = createFilter; + ob.createAlphaToLuminanceFilter = createAlphaToLuminanceFilter; + + function createFilter(filId, skipCoordinates) { + var fil = createNS('filter'); + fil.setAttribute('id', filId); + if (skipCoordinates !== true) { + fil.setAttribute('filterUnits', 'objectBoundingBox'); + fil.setAttribute('x', '0%'); + fil.setAttribute('y', '0%'); + fil.setAttribute('width', '100%'); + fil.setAttribute('height', '100%'); + } + return fil; + } + + function createAlphaToLuminanceFilter() { + var feColorMatrix = createNS('feColorMatrix'); + feColorMatrix.setAttribute('type', 'matrix'); + feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); + feColorMatrix.setAttribute('values', '0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1'); + return feColorMatrix; + } + + return ob; + }()); + + const featureSupport = (function () { + var ob = { + maskType: true, + }; + if (/MSIE 10/i.test(navigator.userAgent) || /MSIE 9/i.test(navigator.userAgent) || /rv:11.0/i.test(navigator.userAgent) || /Edge\/\d./i.test(navigator.userAgent)) { + ob.maskType = false; + } + return ob; + }()); + + var registeredEffects = {}; + var idPrefix = 'filter_result_'; + + function SVGEffects(elem) { + var i; + var source = 'SourceGraphic'; + var len = elem.data.ef ? elem.data.ef.length : 0; + var filId = createElementID(); + var fil = filtersFactory.createFilter(filId, true); + var count = 0; + this.filters = []; + var filterManager; + for (i = 0; i < len; i += 1) { + filterManager = null; + var type = elem.data.ef[i].ty; + if (registeredEffects[type]) { + var Effect = registeredEffects[type].effect; + filterManager = new Effect(fil, elem.effectsManager.effectElements[i], elem, idPrefix + count, source); + source = idPrefix + count; + if (registeredEffects[type].countsAsEffect) { + count += 1; + } + } + if (filterManager) { + this.filters.push(filterManager); + } + } + if (count) { + elem.globalData.defs.appendChild(fil); + elem.layerElement.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); + } + if (this.filters.length) { + elem.addRenderableComponent(this); + } + } + + SVGEffects.prototype.renderFrame = function (_isFirstFrame) { + var i; + var len = this.filters.length; + for (i = 0; i < len; i += 1) { + this.filters[i].renderFrame(_isFirstFrame); + } + }; + + function registerEffect(id, effect, countsAsEffect) { + registeredEffects[id] = { + effect, + countsAsEffect, + }; + } + + function SVGBaseElement() { + } + + SVGBaseElement.prototype = { + initRendererElement: function () { + this.layerElement = createNS('g'); + }, + createContainerElements: function () { + this.matteElement = createNS('g'); + this.transformedElement = this.layerElement; + this.maskedElement = this.layerElement; + this._sizeChanged = false; + var layerElementParent = null; + // If this layer acts as a mask for the following layer + var filId; + var fil; + var gg; + if (this.data.td) { + if (this.data.td == 3 || this.data.td == 1) { // eslint-disable-line eqeqeq + var masker = createNS('mask'); + masker.setAttribute('id', this.layerId); + masker.setAttribute('mask-type', this.data.td == 3 ? 'luminance' : 'alpha'); // eslint-disable-line eqeqeq + masker.appendChild(this.layerElement); + layerElementParent = masker; + this.globalData.defs.appendChild(masker); + // This is only for IE and Edge when mask if of type alpha + if (!featureSupport.maskType && this.data.td == 1) { // eslint-disable-line eqeqeq + masker.setAttribute('mask-type', 'luminance'); + filId = createElementID(); + fil = filtersFactory.createFilter(filId); + this.globalData.defs.appendChild(fil); + fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); + gg = createNS('g'); + gg.appendChild(this.layerElement); + layerElementParent = gg; + masker.appendChild(gg); + gg.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); + } + } else if (this.data.td == 2) { // eslint-disable-line eqeqeq + var maskGroup = createNS('mask'); + maskGroup.setAttribute('id', this.layerId); + maskGroup.setAttribute('mask-type', 'alpha'); + var maskGrouper = createNS('g'); + maskGroup.appendChild(maskGrouper); + filId = createElementID(); + fil = filtersFactory.createFilter(filId); + /// / + + // This solution doesn't work on Android when meta tag with viewport attribute is set + /* var feColorMatrix = createNS('feColorMatrix'); + feColorMatrix.setAttribute('type', 'matrix'); + feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); + feColorMatrix.setAttribute('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 -1 1'); + fil.appendChild(feColorMatrix); */ + /// / + var feCTr = createNS('feComponentTransfer'); + feCTr.setAttribute('in', 'SourceGraphic'); + fil.appendChild(feCTr); + var feFunc = createNS('feFuncA'); + feFunc.setAttribute('type', 'table'); + feFunc.setAttribute('tableValues', '1.0 0.0'); + feCTr.appendChild(feFunc); + /// / + this.globalData.defs.appendChild(fil); + var alphaRect = createNS('rect'); + alphaRect.setAttribute('width', this.comp.data.w); + alphaRect.setAttribute('height', this.comp.data.h); + alphaRect.setAttribute('x', '0'); + alphaRect.setAttribute('y', '0'); + alphaRect.setAttribute('fill', '#ffffff'); + alphaRect.setAttribute('opacity', '0'); + maskGrouper.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); + maskGrouper.appendChild(alphaRect); + maskGrouper.appendChild(this.layerElement); + layerElementParent = maskGrouper; + if (!featureSupport.maskType) { + maskGroup.setAttribute('mask-type', 'luminance'); + fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); + gg = createNS('g'); + maskGrouper.appendChild(alphaRect); + gg.appendChild(this.layerElement); + layerElementParent = gg; + maskGrouper.appendChild(gg); + } + this.globalData.defs.appendChild(maskGroup); + } + } else if (this.data.tt) { + this.matteElement.appendChild(this.layerElement); + layerElementParent = this.matteElement; + this.baseElement = this.matteElement; + } else { + this.baseElement = this.layerElement; + } + if (this.data.ln) { + this.layerElement.setAttribute('id', this.data.ln); + } + if (this.data.cl) { + this.layerElement.setAttribute('class', this.data.cl); + } + // Clipping compositions to hide content that exceeds boundaries. If collapsed transformations is on, component should not be clipped + if (this.data.ty === 0 && !this.data.hd) { + var cp = createNS('clipPath'); + var pt = createNS('path'); + pt.setAttribute('d', 'M0,0 L' + this.data.w + ',0 L' + this.data.w + ',' + this.data.h + ' L0,' + this.data.h + 'z'); + var clipId = createElementID(); + cp.setAttribute('id', clipId); + cp.appendChild(pt); + this.globalData.defs.appendChild(cp); + + if (this.checkMasks()) { + var cpGroup = createNS('g'); + cpGroup.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); + cpGroup.appendChild(this.layerElement); + this.transformedElement = cpGroup; + if (layerElementParent) { + layerElementParent.appendChild(this.transformedElement); + } else { + this.baseElement = this.transformedElement; + } + } else { + this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); + } + } + if (this.data.bm !== 0) { + this.setBlendMode(); + } + }, + renderElement: function () { + if (this.finalTransform._matMdf) { + this.transformedElement.setAttribute('transform', this.finalTransform.mat.to2dCSS()); + } + if (this.finalTransform._opMdf) { + this.transformedElement.setAttribute('opacity', this.finalTransform.mProp.o.v); + } + }, + destroyBaseElement: function () { + this.layerElement = null; + this.matteElement = null; + this.maskManager.destroy(); + }, + getBaseElement: function () { + if (this.data.hd) { + return null; + } + return this.baseElement; + }, + createRenderableComponents: function () { + this.maskManager = new MaskElement(this.data, this, this.globalData); + this.renderableEffectsManager = new SVGEffects(this); + }, + setMatte: function (id) { + if (!this.matteElement) { + return; + } + this.matteElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + id + ')'); + }, + }; + + /** + * @file + * Handles AE's layer parenting property. + * + */ + + function HierarchyElement() {} + + HierarchyElement.prototype = { + /** + * @function + * Initializes hierarchy properties + * + */ + initHierarchy: function () { + // element's parent list + this.hierarchy = []; + // if element is parent of another layer _isParent will be true + this._isParent = false; + this.checkParenting(); + }, + /** + * @function + * Sets layer's hierarchy. + * @param {array} hierarch + * layer's parent list + * + */ + setHierarchy: function (hierarchy) { + this.hierarchy = hierarchy; + }, + /** + * @function + * Sets layer as parent. + * + */ + setAsParent: function () { + this._isParent = true; + }, + /** + * @function + * Searches layer's parenting chain + * + */ + checkParenting: function () { + if (this.data.parent !== undefined) { + this.comp.buildElementParenting(this, this.data.parent, []); + } + }, + }; + + function RenderableDOMElement() {} + + (function () { + var _prototype = { + initElement: function (data, globalData, comp) { + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.initTransform(data, globalData, comp); + this.initHierarchy(); + this.initRenderable(); + this.initRendererElement(); + this.createContainerElements(); + this.createRenderableComponents(); + this.createContent(); + this.hide(); + }, + hide: function () { + // console.log('HIDE', this); + if (!this.hidden && (!this.isInRange || this.isTransparent)) { + var elem = this.baseElement || this.layerElement; + elem.style.display = 'none'; + this.hidden = true; + } + }, + show: function () { + // console.log('SHOW', this); + if (this.isInRange && !this.isTransparent) { + if (!this.data.hd) { + var elem = this.baseElement || this.layerElement; + elem.style.display = 'block'; + } + this.hidden = false; + this._isFirstFrame = true; + } + }, + renderFrame: function () { + // If it is exported as hidden (data.hd === true) no need to render + // If it is not visible no need to render + if (this.data.hd || this.hidden) { + return; + } + this.renderTransform(); + this.renderRenderable(); + this.renderElement(); + this.renderInnerContent(); + if (this._isFirstFrame) { + this._isFirstFrame = false; + } + }, + renderInnerContent: function () {}, + prepareFrame: function (num) { + this._mdf = false; + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + this.checkTransparency(); + }, + destroy: function () { + this.innerElem = null; + this.destroyBaseElement(); + }, + }; + extendPrototype([RenderableElement, createProxyFunction(_prototype)], RenderableDOMElement); + }()); + + function IImageElement(data, globalData, comp) { + this.assetData = globalData.getAssetData(data.refId); + this.initElement(data, globalData, comp); + this.sourceRect = { + top: 0, left: 0, width: this.assetData.w, height: this.assetData.h, + }; + } + + extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement], IImageElement); + + IImageElement.prototype.createContent = function () { + var assetPath = this.globalData.getAssetsPath(this.assetData); + + this.innerElem = createNS('image'); + this.innerElem.setAttribute('width', this.assetData.w + 'px'); + this.innerElem.setAttribute('height', this.assetData.h + 'px'); + this.innerElem.setAttribute('preserveAspectRatio', this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio); + this.innerElem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', assetPath); + + this.layerElement.appendChild(this.innerElem); + }; + + IImageElement.prototype.sourceRectAtTime = function () { + return this.sourceRect; + }; + + function ProcessedElement(element, position) { + this.elem = element; + this.pos = position; + } + + function IShapeElement() { + } + + IShapeElement.prototype = { + addShapeToModifiers: function (data) { + var i; + var len = this.shapeModifiers.length; + for (i = 0; i < len; i += 1) { + this.shapeModifiers[i].addShape(data); + } + }, + isShapeInAnimatedModifiers: function (data) { + var i = 0; + var len = this.shapeModifiers.length; + while (i < len) { + if (this.shapeModifiers[i].isAnimatedWithShape(data)) { + return true; + } + } + return false; + }, + renderModifiers: function () { + if (!this.shapeModifiers.length) { + return; + } + var i; + var len = this.shapes.length; + for (i = 0; i < len; i += 1) { + this.shapes[i].sh.reset(); + } + + len = this.shapeModifiers.length; + var shouldBreakProcess; + for (i = len - 1; i >= 0; i -= 1) { + shouldBreakProcess = this.shapeModifiers[i].processShapes(this._isFirstFrame); + // workaround to fix cases where a repeater resets the shape so the following processes get called twice + // TODO: find a better solution for this + if (shouldBreakProcess) { + break; + } + } + }, + + searchProcessedElement: function (elem) { + var elements = this.processedElements; + var i = 0; + var len = elements.length; + while (i < len) { + if (elements[i].elem === elem) { + return elements[i].pos; + } + i += 1; + } + return 0; + }, + addProcessedElement: function (elem, pos) { + var elements = this.processedElements; + var i = elements.length; + while (i) { + i -= 1; + if (elements[i].elem === elem) { + elements[i].pos = pos; + return; + } + } + elements.push(new ProcessedElement(elem, pos)); + }, + prepareFrame: function (num) { + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + }, + }; + + const lineCapEnum = { + 1: 'butt', + 2: 'round', + 3: 'square', + }; + + const lineJoinEnum = { + 1: 'miter', + 2: 'round', + 3: 'bevel', + }; + + function SVGShapeData(transformers, level, shape) { + this.caches = []; + this.styles = []; + this.transformers = transformers; + this.lStr = ''; + this.sh = shape; + this.lvl = level; + // TODO find if there are some cases where _isAnimated can be false. + // For now, since shapes add up with other shapes. They have to be calculated every time. + // One way of finding out is checking if all styles associated to this shape depend only of this shape + this._isAnimated = !!shape.k; + // TODO: commenting this for now since all shapes are animated + var i = 0; + var len = transformers.length; + while (i < len) { + if (transformers[i].mProps.dynamicProperties.length) { + this._isAnimated = true; + break; + } + i += 1; + } + } + + SVGShapeData.prototype.setAsAnimated = function () { + this._isAnimated = true; + }; + + function SVGStyleData(data, level) { + this.data = data; + this.type = data.ty; + this.d = ''; + this.lvl = level; + this._mdf = false; + this.closed = data.hd === true; + this.pElem = createNS('path'); + this.msElem = null; + } + + SVGStyleData.prototype.reset = function () { + this.d = ''; + this._mdf = false; + }; + + function DashProperty(elem, data, renderer, container) { + this.elem = elem; + this.frameId = -1; + this.dataProps = createSizedArray(data.length); + this.renderer = renderer; + this.k = false; + this.dashStr = ''; + this.dashArray = createTypedArray('float32', data.length ? data.length - 1 : 0); + this.dashoffset = createTypedArray('float32', 1); + this.initDynamicPropertyContainer(container); + var i; + var len = data.length || 0; + var prop; + for (i = 0; i < len; i += 1) { + prop = PropertyFactory.getProp(elem, data[i].v, 0, 0, this); + this.k = prop.k || this.k; + this.dataProps[i] = { n: data[i].n, p: prop }; + } + if (!this.k) { + this.getValue(true); + } + this._isAnimated = this.k; + } + + DashProperty.prototype.getValue = function (forceRender) { + if (this.elem.globalData.frameId === this.frameId && !forceRender) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + this._mdf = this._mdf || forceRender; + if (this._mdf) { + var i = 0; + var len = this.dataProps.length; + if (this.renderer === 'svg') { + this.dashStr = ''; + } + for (i = 0; i < len; i += 1) { + if (this.dataProps[i].n !== 'o') { + if (this.renderer === 'svg') { + this.dashStr += ' ' + this.dataProps[i].p.v; + } else { + this.dashArray[i] = this.dataProps[i].p.v; + } + } else { + this.dashoffset[0] = this.dataProps[i].p.v; + } + } + } + }; + extendPrototype([DynamicPropertyContainer], DashProperty); + + function SVGStrokeStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); + this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); + this.d = new DashProperty(elem, data.d || {}, 'svg', this); + this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); + this.style = styleOb; + this._isAnimated = !!this._isAnimated; + } + + extendPrototype([DynamicPropertyContainer], SVGStrokeStyleData); + + function SVGFillStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); + this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); + this.style = styleOb; + } + + extendPrototype([DynamicPropertyContainer], SVGFillStyleData); + + function SVGNoStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.style = styleOb; + } + + extendPrototype([DynamicPropertyContainer], SVGNoStyleData); + + function GradientProperty(elem, data, container) { + this.data = data; + this.c = createTypedArray('uint8c', data.p * 4); + var cLength = data.k.k[0].s ? (data.k.k[0].s.length - data.p * 4) : data.k.k.length - data.p * 4; + this.o = createTypedArray('float32', cLength); + this._cmdf = false; + this._omdf = false; + this._collapsable = this.checkCollapsable(); + this._hasOpacity = cLength; + this.initDynamicPropertyContainer(container); + this.prop = PropertyFactory.getProp(elem, data.k, 1, null, this); + this.k = this.prop.k; + this.getValue(true); + } + + GradientProperty.prototype.comparePoints = function (values, points) { + var i = 0; + var len = this.o.length / 2; + var diff; + while (i < len) { + diff = Math.abs(values[i * 4] - values[points * 4 + i * 2]); + if (diff > 0.01) { + return false; + } + i += 1; + } + return true; + }; + + GradientProperty.prototype.checkCollapsable = function () { + if (this.o.length / 2 !== this.c.length / 4) { + return false; + } + if (this.data.k.k[0].s) { + var i = 0; + var len = this.data.k.k.length; + while (i < len) { + if (!this.comparePoints(this.data.k.k[i].s, this.data.p)) { + return false; + } + i += 1; + } + } else if (!this.comparePoints(this.data.k.k, this.data.p)) { + return false; + } + return true; + }; + + GradientProperty.prototype.getValue = function (forceRender) { + this.prop.getValue(); + this._mdf = false; + this._cmdf = false; + this._omdf = false; + if (this.prop._mdf || forceRender) { + var i; + var len = this.data.p * 4; + var mult; + var val; + for (i = 0; i < len; i += 1) { + mult = i % 4 === 0 ? 100 : 255; + val = Math.round(this.prop.v[i] * mult); + if (this.c[i] !== val) { + this.c[i] = val; + this._cmdf = !forceRender; + } + } + if (this.o.length) { + len = this.prop.v.length; + for (i = this.data.p * 4; i < len; i += 1) { + mult = i % 2 === 0 ? 100 : 1; + val = i % 2 === 0 ? Math.round(this.prop.v[i] * 100) : this.prop.v[i]; + if (this.o[i - this.data.p * 4] !== val) { + this.o[i - this.data.p * 4] = val; + this._omdf = !forceRender; + } + } + } + this._mdf = !forceRender; + } + }; + + extendPrototype([DynamicPropertyContainer], GradientProperty); + + function SVGGradientFillStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.initGradientData(elem, data, styleOb); + } + + SVGGradientFillStyleData.prototype.initGradientData = function (elem, data, styleOb) { + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); + this.s = PropertyFactory.getProp(elem, data.s, 1, null, this); + this.e = PropertyFactory.getProp(elem, data.e, 1, null, this); + this.h = PropertyFactory.getProp(elem, data.h || { k: 0 }, 0, 0.01, this); + this.a = PropertyFactory.getProp(elem, data.a || { k: 0 }, 0, degToRads, this); + this.g = new GradientProperty(elem, data.g, this); + this.style = styleOb; + this.stops = []; + this.setGradientData(styleOb.pElem, data); + this.setGradientOpacity(data, styleOb); + this._isAnimated = !!this._isAnimated; + }; + + SVGGradientFillStyleData.prototype.setGradientData = function (pathElement, data) { + var gradientId = createElementID(); + var gfill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); + gfill.setAttribute('id', gradientId); + gfill.setAttribute('spreadMethod', 'pad'); + gfill.setAttribute('gradientUnits', 'userSpaceOnUse'); + var stops = []; + var stop; + var j; + var jLen; + jLen = data.g.p * 4; + for (j = 0; j < jLen; j += 4) { + stop = createNS('stop'); + gfill.appendChild(stop); + stops.push(stop); + } + pathElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + gradientId + ')'); + this.gf = gfill; + this.cst = stops; + }; + + SVGGradientFillStyleData.prototype.setGradientOpacity = function (data, styleOb) { + if (this.g._hasOpacity && !this.g._collapsable) { + var stop; + var j; + var jLen; + var mask = createNS('mask'); + var maskElement = createNS('path'); + mask.appendChild(maskElement); + var opacityId = createElementID(); + var maskId = createElementID(); + mask.setAttribute('id', maskId); + var opFill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); + opFill.setAttribute('id', opacityId); + opFill.setAttribute('spreadMethod', 'pad'); + opFill.setAttribute('gradientUnits', 'userSpaceOnUse'); + jLen = data.g.k.k[0].s ? data.g.k.k[0].s.length : data.g.k.k.length; + var stops = this.stops; + for (j = data.g.p * 4; j < jLen; j += 2) { + stop = createNS('stop'); + stop.setAttribute('stop-color', 'rgb(255,255,255)'); + opFill.appendChild(stop); + stops.push(stop); + } + maskElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + opacityId + ')'); + if (data.ty === 'gs') { + maskElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); + maskElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); + if (data.lj === 1) { + maskElement.setAttribute('stroke-miterlimit', data.ml); + } + } + this.of = opFill; + this.ms = mask; + this.ost = stops; + this.maskId = maskId; + styleOb.msElem = maskElement; + } + }; + + extendPrototype([DynamicPropertyContainer], SVGGradientFillStyleData); + + function SVGGradientStrokeStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); + this.d = new DashProperty(elem, data.d || {}, 'svg', this); + this.initGradientData(elem, data, styleOb); + this._isAnimated = !!this._isAnimated; + } + + extendPrototype([SVGGradientFillStyleData, DynamicPropertyContainer], SVGGradientStrokeStyleData); + + function ShapeGroupData() { + this.it = []; + this.prevViewData = []; + this.gr = createNS('g'); + } + + function SVGTransformData(mProps, op, container) { + this.transform = { + mProps: mProps, + op: op, + container: container, + }; + this.elements = []; + this._isAnimated = this.transform.mProps.dynamicProperties.length || this.transform.op.effectsSequence.length; + } + + const buildShapeString = function (pathNodes, length, closed, mat) { + if (length === 0) { + return ''; + } + var _o = pathNodes.o; + var _i = pathNodes.i; + var _v = pathNodes.v; + var i; + var shapeString = ' M' + mat.applyToPointStringified(_v[0][0], _v[0][1]); + for (i = 1; i < length; i += 1) { + shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[i][0], _i[i][1]) + ' ' + mat.applyToPointStringified(_v[i][0], _v[i][1]); + } + if (closed && length) { + shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[0][0], _i[0][1]) + ' ' + mat.applyToPointStringified(_v[0][0], _v[0][1]); + shapeString += 'z'; + } + return shapeString; + }; + + const SVGElementsRenderer = (function () { + var _identityMatrix = new Matrix(); + var _matrixHelper = new Matrix(); + + var ob = { + createRenderFunction: createRenderFunction, + }; + + function createRenderFunction(data) { + switch (data.ty) { + case 'fl': + return renderFill; + case 'gf': + return renderGradient; + case 'gs': + return renderGradientStroke; + case 'st': + return renderStroke; + case 'sh': + case 'el': + case 'rc': + case 'sr': + return renderPath; + case 'tr': + return renderContentTransform; + case 'no': + return renderNoop; + default: + return null; + } + } + + function renderContentTransform(styleData, itemData, isFirstFrame) { + if (isFirstFrame || itemData.transform.op._mdf) { + itemData.transform.container.setAttribute('opacity', itemData.transform.op.v); + } + if (isFirstFrame || itemData.transform.mProps._mdf) { + itemData.transform.container.setAttribute('transform', itemData.transform.mProps.v.to2dCSS()); + } + } + + function renderNoop() { + + } + + function renderPath(styleData, itemData, isFirstFrame) { + var j; + var jLen; + var pathStringTransformed; + var redraw; + var pathNodes; + var l; + var lLen = itemData.styles.length; + var lvl = itemData.lvl; + var paths; + var mat; + var props; + var iterations; + var k; + for (l = 0; l < lLen; l += 1) { + redraw = itemData.sh._mdf || isFirstFrame; + if (itemData.styles[l].lvl < lvl) { + mat = _matrixHelper.reset(); + iterations = lvl - itemData.styles[l].lvl; + k = itemData.transformers.length - 1; + while (!redraw && iterations > 0) { + redraw = itemData.transformers[k].mProps._mdf || redraw; + iterations -= 1; + k -= 1; + } + if (redraw) { + iterations = lvl - itemData.styles[l].lvl; + k = itemData.transformers.length - 1; + while (iterations > 0) { + props = itemData.transformers[k].mProps.v.props; + mat.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); + iterations -= 1; + k -= 1; + } + } + } else { + mat = _identityMatrix; + } + paths = itemData.sh.paths; + jLen = paths._length; + if (redraw) { + pathStringTransformed = ''; + for (j = 0; j < jLen; j += 1) { + pathNodes = paths.shapes[j]; + if (pathNodes && pathNodes._length) { + pathStringTransformed += buildShapeString(pathNodes, pathNodes._length, pathNodes.c, mat); + } + } + itemData.caches[l] = pathStringTransformed; + } else { + pathStringTransformed = itemData.caches[l]; + } + itemData.styles[l].d += styleData.hd === true ? '' : pathStringTransformed; + itemData.styles[l]._mdf = redraw || itemData.styles[l]._mdf; + } + } + + function renderFill(styleData, itemData, isFirstFrame) { + var styleElem = itemData.style; + + if (itemData.c._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('fill', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); + } + if (itemData.o._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('fill-opacity', itemData.o.v); + } + } + + function renderGradientStroke(styleData, itemData, isFirstFrame) { + renderGradient(styleData, itemData, isFirstFrame); + renderStroke(styleData, itemData, isFirstFrame); + } + + function renderGradient(styleData, itemData, isFirstFrame) { + var gfill = itemData.gf; + var hasOpacity = itemData.g._hasOpacity; + var pt1 = itemData.s.v; + var pt2 = itemData.e.v; + + if (itemData.o._mdf || isFirstFrame) { + var attr = styleData.ty === 'gf' ? 'fill-opacity' : 'stroke-opacity'; + itemData.style.pElem.setAttribute(attr, itemData.o.v); + } + if (itemData.s._mdf || isFirstFrame) { + var attr1 = styleData.t === 1 ? 'x1' : 'cx'; + var attr2 = attr1 === 'x1' ? 'y1' : 'cy'; + gfill.setAttribute(attr1, pt1[0]); + gfill.setAttribute(attr2, pt1[1]); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute(attr1, pt1[0]); + itemData.of.setAttribute(attr2, pt1[1]); + } + } + var stops; + var i; + var len; + var stop; + if (itemData.g._cmdf || isFirstFrame) { + stops = itemData.cst; + var cValues = itemData.g.c; + len = stops.length; + for (i = 0; i < len; i += 1) { + stop = stops[i]; + stop.setAttribute('offset', cValues[i * 4] + '%'); + stop.setAttribute('stop-color', 'rgb(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ')'); + } + } + if (hasOpacity && (itemData.g._omdf || isFirstFrame)) { + var oValues = itemData.g.o; + if (itemData.g._collapsable) { + stops = itemData.cst; + } else { + stops = itemData.ost; + } + len = stops.length; + for (i = 0; i < len; i += 1) { + stop = stops[i]; + if (!itemData.g._collapsable) { + stop.setAttribute('offset', oValues[i * 2] + '%'); + } + stop.setAttribute('stop-opacity', oValues[i * 2 + 1]); + } + } + if (styleData.t === 1) { + if (itemData.e._mdf || isFirstFrame) { + gfill.setAttribute('x2', pt2[0]); + gfill.setAttribute('y2', pt2[1]); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute('x2', pt2[0]); + itemData.of.setAttribute('y2', pt2[1]); + } + } + } else { + var rad; + if (itemData.s._mdf || itemData.e._mdf || isFirstFrame) { + rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); + gfill.setAttribute('r', rad); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute('r', rad); + } + } + if (itemData.e._mdf || itemData.h._mdf || itemData.a._mdf || isFirstFrame) { + if (!rad) { + rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); + } + var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); + + var percent = itemData.h.v; + if (percent >= 1) { + percent = 0.99; + } else if (percent <= -1) { + percent = -0.99; + } + var dist = rad * percent; + var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; + var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; + gfill.setAttribute('fx', x); + gfill.setAttribute('fy', y); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute('fx', x); + itemData.of.setAttribute('fy', y); + } + } + // gfill.setAttribute('fy','200'); + } + } + + function renderStroke(styleData, itemData, isFirstFrame) { + var styleElem = itemData.style; + var d = itemData.d; + if (d && (d._mdf || isFirstFrame) && d.dashStr) { + styleElem.pElem.setAttribute('stroke-dasharray', d.dashStr); + styleElem.pElem.setAttribute('stroke-dashoffset', d.dashoffset[0]); + } + if (itemData.c && (itemData.c._mdf || isFirstFrame)) { + styleElem.pElem.setAttribute('stroke', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); + } + if (itemData.o._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('stroke-opacity', itemData.o.v); + } + if (itemData.w._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('stroke-width', itemData.w.v); + if (styleElem.msElem) { + styleElem.msElem.setAttribute('stroke-width', itemData.w.v); + } + } + } + + return ob; + }()); + + function SVGShapeElement(data, globalData, comp) { + // List of drawable elements + this.shapes = []; + // Full shape data + this.shapesData = data.shapes; + // List of styles that will be applied to shapes + this.stylesList = []; + // List of modifiers that will be applied to shapes + this.shapeModifiers = []; + // List of items in shape tree + this.itemsData = []; + // List of items in previous shape tree + this.processedElements = []; + // List of animated components + this.animatedContents = []; + this.initElement(data, globalData, comp); + // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. + // List of elements that have been created + this.prevViewData = []; + // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. + } + + extendPrototype([BaseElement, TransformElement, SVGBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableDOMElement], SVGShapeElement); + + SVGShapeElement.prototype.initSecondaryElement = function () { + }; + + SVGShapeElement.prototype.identityMatrix = new Matrix(); + + SVGShapeElement.prototype.buildExpressionInterface = function () {}; + + SVGShapeElement.prototype.createContent = function () { + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); + this.filterUniqueShapes(); + }; + + /* + This method searches for multiple shapes that affect a single element and one of them is animated + */ + SVGShapeElement.prototype.filterUniqueShapes = function () { + var i; + var len = this.shapes.length; + var shape; + var j; + var jLen = this.stylesList.length; + var style; + var tempShapes = []; + var areAnimated = false; + for (j = 0; j < jLen; j += 1) { + style = this.stylesList[j]; + areAnimated = false; + tempShapes.length = 0; + for (i = 0; i < len; i += 1) { + shape = this.shapes[i]; + if (shape.styles.indexOf(style) !== -1) { + tempShapes.push(shape); + areAnimated = shape._isAnimated || areAnimated; + } + } + if (tempShapes.length > 1 && areAnimated) { + this.setShapesAsAnimated(tempShapes); + } + } + }; + + SVGShapeElement.prototype.setShapesAsAnimated = function (shapes) { + var i; + var len = shapes.length; + for (i = 0; i < len; i += 1) { + shapes[i].setAsAnimated(); + } + }; + + SVGShapeElement.prototype.createStyleElement = function (data, level) { + // TODO: prevent drawing of hidden styles + var elementData; + var styleOb = new SVGStyleData(data, level); + + var pathElement = styleOb.pElem; + if (data.ty === 'st') { + elementData = new SVGStrokeStyleData(this, data, styleOb); + } else if (data.ty === 'fl') { + elementData = new SVGFillStyleData(this, data, styleOb); + } else if (data.ty === 'gf' || data.ty === 'gs') { + var GradientConstructor = data.ty === 'gf' ? SVGGradientFillStyleData : SVGGradientStrokeStyleData; + elementData = new GradientConstructor(this, data, styleOb); + this.globalData.defs.appendChild(elementData.gf); + if (elementData.maskId) { + this.globalData.defs.appendChild(elementData.ms); + this.globalData.defs.appendChild(elementData.of); + pathElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + elementData.maskId + ')'); + } + } else if (data.ty === 'no') { + elementData = new SVGNoStyleData(this, data, styleOb); + } + + if (data.ty === 'st' || data.ty === 'gs') { + pathElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); + pathElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); + pathElement.setAttribute('fill-opacity', '0'); + if (data.lj === 1) { + pathElement.setAttribute('stroke-miterlimit', data.ml); + } + } + + if (data.r === 2) { + pathElement.setAttribute('fill-rule', 'evenodd'); + } + + if (data.ln) { + pathElement.setAttribute('id', data.ln); + } + if (data.cl) { + pathElement.setAttribute('class', data.cl); + } + if (data.bm) { + pathElement.style['mix-blend-mode'] = getBlendMode(data.bm); + } + this.stylesList.push(styleOb); + this.addToAnimatedContents(data, elementData); + return elementData; + }; + + SVGShapeElement.prototype.createGroupElement = function (data) { + var elementData = new ShapeGroupData(); + if (data.ln) { + elementData.gr.setAttribute('id', data.ln); + } + if (data.cl) { + elementData.gr.setAttribute('class', data.cl); + } + if (data.bm) { + elementData.gr.style['mix-blend-mode'] = getBlendMode(data.bm); + } + return elementData; + }; + + SVGShapeElement.prototype.createTransformElement = function (data, container) { + var transformProperty = TransformPropertyFactory.getTransformProperty(this, data, this); + var elementData = new SVGTransformData(transformProperty, transformProperty.o, container); + this.addToAnimatedContents(data, elementData); + return elementData; + }; + + SVGShapeElement.prototype.createShapeElement = function (data, ownTransformers, level) { + var ty = 4; + if (data.ty === 'rc') { + ty = 5; + } else if (data.ty === 'el') { + ty = 6; + } else if (data.ty === 'sr') { + ty = 7; + } + var shapeProperty = ShapePropertyFactory.getShapeProp(this, data, ty, this); + var elementData = new SVGShapeData(ownTransformers, level, shapeProperty); + this.shapes.push(elementData); + this.addShapeToModifiers(elementData); + this.addToAnimatedContents(data, elementData); + return elementData; + }; + + SVGShapeElement.prototype.addToAnimatedContents = function (data, element) { + var i = 0; + var len = this.animatedContents.length; + while (i < len) { + if (this.animatedContents[i].element === element) { + return; + } + i += 1; + } + this.animatedContents.push({ + fn: SVGElementsRenderer.createRenderFunction(data), + element: element, + data: data, + }); + }; + + SVGShapeElement.prototype.setElementStyles = function (elementData) { + var arr = elementData.styles; + var j; + var jLen = this.stylesList.length; + for (j = 0; j < jLen; j += 1) { + if (!this.stylesList[j].closed) { + arr.push(this.stylesList[j]); + } + } + }; + + SVGShapeElement.prototype.reloadShapes = function () { + this._isFirstFrame = true; + var i; + var len = this.itemsData.length; + for (i = 0; i < len; i += 1) { + this.prevViewData[i] = this.itemsData[i]; + } + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); + this.filterUniqueShapes(); + len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + this.dynamicProperties[i].getValue(); + } + this.renderModifiers(); + }; + + SVGShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, container, level, transformers, render) { + var ownTransformers = [].concat(transformers); + var i; + var len = arr.length - 1; + var j; + var jLen; + var ownStyles = []; + var ownModifiers = []; + var currentTransform; + var modifier; + var processedPos; + for (i = len; i >= 0; i -= 1) { + processedPos = this.searchProcessedElement(arr[i]); + if (!processedPos) { + arr[i]._render = render; + } else { + itemsData[i] = prevViewData[processedPos - 1]; + } + if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs' || arr[i].ty === 'no') { + if (!processedPos) { + itemsData[i] = this.createStyleElement(arr[i], level); + } else { + itemsData[i].style.closed = false; + } + if (arr[i]._render) { + if (itemsData[i].style.pElem.parentNode !== container) { + container.appendChild(itemsData[i].style.pElem); + } + } + ownStyles.push(itemsData[i].style); + } else if (arr[i].ty === 'gr') { + if (!processedPos) { + itemsData[i] = this.createGroupElement(arr[i]); + } else { + jLen = itemsData[i].it.length; + for (j = 0; j < jLen; j += 1) { + itemsData[i].prevViewData[j] = itemsData[i].it[j]; + } + } + this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, itemsData[i].gr, level + 1, ownTransformers, render); + if (arr[i]._render) { + if (itemsData[i].gr.parentNode !== container) { + container.appendChild(itemsData[i].gr); + } + } + } else if (arr[i].ty === 'tr') { + if (!processedPos) { + itemsData[i] = this.createTransformElement(arr[i], container); + } + currentTransform = itemsData[i].transform; + ownTransformers.push(currentTransform); + } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { + if (!processedPos) { + itemsData[i] = this.createShapeElement(arr[i], ownTransformers, level); + } + this.setElementStyles(itemsData[i]); + } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'ms' || arr[i].ty === 'pb') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + modifier.init(this, arr[i]); + itemsData[i] = modifier; + this.shapeModifiers.push(modifier); + } else { + modifier = itemsData[i]; + modifier.closed = false; + } + ownModifiers.push(modifier); + } else if (arr[i].ty === 'rp') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + itemsData[i] = modifier; + modifier.init(this, arr, i, itemsData); + this.shapeModifiers.push(modifier); + render = false; + } else { + modifier = itemsData[i]; + modifier.closed = true; + } + ownModifiers.push(modifier); + } + this.addProcessedElement(arr[i], i + 1); + } + len = ownStyles.length; + for (i = 0; i < len; i += 1) { + ownStyles[i].closed = true; + } + len = ownModifiers.length; + for (i = 0; i < len; i += 1) { + ownModifiers[i].closed = true; + } + }; + + SVGShapeElement.prototype.renderInnerContent = function () { + this.renderModifiers(); + var i; + var len = this.stylesList.length; + for (i = 0; i < len; i += 1) { + this.stylesList[i].reset(); + } + this.renderShape(); + for (i = 0; i < len; i += 1) { + if (this.stylesList[i]._mdf || this._isFirstFrame) { + if (this.stylesList[i].msElem) { + this.stylesList[i].msElem.setAttribute('d', this.stylesList[i].d); + // Adding M0 0 fixes same mask bug on all browsers + this.stylesList[i].d = 'M0 0' + this.stylesList[i].d; + } + this.stylesList[i].pElem.setAttribute('d', this.stylesList[i].d || 'M0 0'); + } + } + }; + + SVGShapeElement.prototype.renderShape = function () { + var i; + var len = this.animatedContents.length; + var animatedContent; + for (i = 0; i < len; i += 1) { + animatedContent = this.animatedContents[i]; + if ((this._isFirstFrame || animatedContent.element._isAnimated) && animatedContent.data !== true) { + animatedContent.fn(animatedContent.data, animatedContent.element, this._isFirstFrame); + } + } + }; + + SVGShapeElement.prototype.destroy = function () { + this.destroyBaseElement(); + this.shapesData = null; + this.itemsData = null; + }; + + function LetterProps(o, sw, sc, fc, m, p) { + this.o = o; + this.sw = sw; + this.sc = sc; + this.fc = fc; + this.m = m; + this.p = p; + this._mdf = { + o: true, + sw: !!sw, + sc: !!sc, + fc: !!fc, + m: true, + p: true, + }; + } + + LetterProps.prototype.update = function (o, sw, sc, fc, m, p) { + this._mdf.o = false; + this._mdf.sw = false; + this._mdf.sc = false; + this._mdf.fc = false; + this._mdf.m = false; + this._mdf.p = false; + var updated = false; + + if (this.o !== o) { + this.o = o; + this._mdf.o = true; + updated = true; + } + if (this.sw !== sw) { + this.sw = sw; + this._mdf.sw = true; + updated = true; + } + if (this.sc !== sc) { + this.sc = sc; + this._mdf.sc = true; + updated = true; + } + if (this.fc !== fc) { + this.fc = fc; + this._mdf.fc = true; + updated = true; + } + if (this.m !== m) { + this.m = m; + this._mdf.m = true; + updated = true; + } + if (p.length && (this.p[0] !== p[0] || this.p[1] !== p[1] || this.p[4] !== p[4] || this.p[5] !== p[5] || this.p[12] !== p[12] || this.p[13] !== p[13])) { + this.p = p; + this._mdf.p = true; + updated = true; + } + return updated; + }; + + function TextProperty(elem, data) { + this._frameId = initialDefaultFrame; + this.pv = ''; + this.v = ''; + this.kf = false; + this._isFirstFrame = true; + this._mdf = false; + this.data = data; + this.elem = elem; + this.comp = this.elem.comp; + this.keysIndex = 0; + this.canResize = false; + this.minimumFontSize = 1; + this.effectsSequence = []; + this.currentData = { + ascent: 0, + boxWidth: this.defaultBoxWidth, + f: '', + fStyle: '', + fWeight: '', + fc: '', + j: '', + justifyOffset: '', + l: [], + lh: 0, + lineWidths: [], + ls: '', + of: '', + s: '', + sc: '', + sw: 0, + t: 0, + tr: 0, + sz: 0, + ps: null, + fillColorAnim: false, + strokeColorAnim: false, + strokeWidthAnim: false, + yOffset: 0, + finalSize: 0, + finalText: [], + finalLineHeight: 0, + __complete: false, + + }; + this.copyData(this.currentData, this.data.d.k[0].s); + + if (!this.searchProperty()) { + this.completeTextData(this.currentData); + } + } + + TextProperty.prototype.defaultBoxWidth = [0, 0]; + + TextProperty.prototype.copyData = function (obj, data) { + for (var s in data) { + if (Object.prototype.hasOwnProperty.call(data, s)) { + obj[s] = data[s]; + } + } + return obj; + }; + + TextProperty.prototype.setCurrentData = function (data) { + if (!data.__complete) { + this.completeTextData(data); + } + this.currentData = data; + this.currentData.boxWidth = this.currentData.boxWidth || this.defaultBoxWidth; + this._mdf = true; + }; + + TextProperty.prototype.searchProperty = function () { + return this.searchKeyframes(); + }; + + TextProperty.prototype.searchKeyframes = function () { + this.kf = this.data.d.k.length > 1; + if (this.kf) { + this.addEffect(this.getKeyframeValue.bind(this)); + } + return this.kf; + }; + + TextProperty.prototype.addEffect = function (effectFunction) { + this.effectsSequence.push(effectFunction); + this.elem.addDynamicProperty(this); + }; + + TextProperty.prototype.getValue = function (_finalValue) { + if ((this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) && !_finalValue) { + return; + } + this.currentData.t = this.data.d.k[this.keysIndex].s.t; + var currentValue = this.currentData; + var currentIndex = this.keysIndex; + if (this.lock) { + this.setCurrentData(this.currentData); + return; + } + this.lock = true; + this._mdf = false; + var i; var + len = this.effectsSequence.length; + var finalValue = _finalValue || this.data.d.k[this.keysIndex].s; + for (i = 0; i < len; i += 1) { + // Checking if index changed to prevent creating a new object every time the expression updates. + if (currentIndex !== this.keysIndex) { + finalValue = this.effectsSequence[i](finalValue, finalValue.t); + } else { + finalValue = this.effectsSequence[i](this.currentData, finalValue.t); + } + } + if (currentValue !== finalValue) { + this.setCurrentData(finalValue); + } + this.v = this.currentData; + this.pv = this.v; + this.lock = false; + this.frameId = this.elem.globalData.frameId; + }; + + TextProperty.prototype.getKeyframeValue = function () { + var textKeys = this.data.d.k; + var frameNum = this.elem.comp.renderedFrame; + var i = 0; var + len = textKeys.length; + while (i <= len - 1) { + if (i === len - 1 || textKeys[i + 1].t > frameNum) { + break; + } + i += 1; + } + if (this.keysIndex !== i) { + this.keysIndex = i; + } + return this.data.d.k[this.keysIndex].s; + }; + + TextProperty.prototype.buildFinalText = function (text) { + var charactersArray = []; + var i = 0; + var len = text.length; + var charCode; + var secondCharCode; + var shouldCombine = false; + while (i < len) { + charCode = text.charCodeAt(i); + if (FontManager.isCombinedCharacter(charCode)) { + charactersArray[charactersArray.length - 1] += text.charAt(i); + } else if (charCode >= 0xD800 && charCode <= 0xDBFF) { + secondCharCode = text.charCodeAt(i + 1); + if (secondCharCode >= 0xDC00 && secondCharCode <= 0xDFFF) { + if (shouldCombine || FontManager.isModifier(charCode, secondCharCode)) { + charactersArray[charactersArray.length - 1] += text.substr(i, 2); + shouldCombine = false; + } else { + charactersArray.push(text.substr(i, 2)); + } + i += 1; + } else { + charactersArray.push(text.charAt(i)); + } + } else if (charCode > 0xDBFF) { + secondCharCode = text.charCodeAt(i + 1); + if (FontManager.isZeroWidthJoiner(charCode, secondCharCode)) { + shouldCombine = true; + charactersArray[charactersArray.length - 1] += text.substr(i, 2); + i += 1; + } else { + charactersArray.push(text.charAt(i)); + } + } else if (FontManager.isZeroWidthJoiner(charCode)) { + charactersArray[charactersArray.length - 1] += text.charAt(i); + shouldCombine = true; + } else { + charactersArray.push(text.charAt(i)); + } + i += 1; + } + return charactersArray; + }; + + TextProperty.prototype.completeTextData = function (documentData) { + documentData.__complete = true; + var fontManager = this.elem.globalData.fontManager; + var data = this.data; + var letters = []; + var i; var + len; + var newLineFlag; var index = 0; var + val; + var anchorGrouping = data.m.g; + var currentSize = 0; var currentPos = 0; var currentLine = 0; var + lineWidths = []; + var lineWidth = 0; + var maxLineWidth = 0; + var j; var + jLen; + var fontData = fontManager.getFontByName(documentData.f); + var charData; var + cLength = 0; + + var fontProps = getFontProperties(fontData); + documentData.fWeight = fontProps.weight; + documentData.fStyle = fontProps.style; + documentData.finalSize = documentData.s; + documentData.finalText = this.buildFinalText(documentData.t); + len = documentData.finalText.length; + documentData.finalLineHeight = documentData.lh; + var trackingOffset = (documentData.tr / 1000) * documentData.finalSize; + var charCode; + if (documentData.sz) { + var flag = true; + var boxWidth = documentData.sz[0]; + var boxHeight = documentData.sz[1]; + var currentHeight; var + finalText; + while (flag) { + finalText = this.buildFinalText(documentData.t); + currentHeight = 0; + lineWidth = 0; + len = finalText.length; + trackingOffset = (documentData.tr / 1000) * documentData.finalSize; + var lastSpaceIndex = -1; + for (i = 0; i < len; i += 1) { + charCode = finalText[i].charCodeAt(0); + newLineFlag = false; + if (finalText[i] === ' ') { + lastSpaceIndex = i; + } else if (charCode === 13 || charCode === 3) { + lineWidth = 0; + newLineFlag = true; + currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; + } + if (fontManager.chars) { + charData = fontManager.getCharData(finalText[i], fontData.fStyle, fontData.fFamily); + cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; + } else { + // tCanvasHelper.font = documentData.s + 'px '+ fontData.fFamily; + cLength = fontManager.measureText(finalText[i], documentData.f, documentData.finalSize); + } + if (lineWidth + cLength > boxWidth && finalText[i] !== ' ') { + if (lastSpaceIndex === -1) { + len += 1; + } else { + i = lastSpaceIndex; + } + currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; + finalText.splice(i, lastSpaceIndex === i ? 1 : 0, '\r'); + // finalText = finalText.substr(0,i) + "\r" + finalText.substr(i === lastSpaceIndex ? i + 1 : i); + lastSpaceIndex = -1; + lineWidth = 0; + } else { + lineWidth += cLength; + lineWidth += trackingOffset; + } + } + currentHeight += (fontData.ascent * documentData.finalSize) / 100; + if (this.canResize && documentData.finalSize > this.minimumFontSize && boxHeight < currentHeight) { + documentData.finalSize -= 1; + documentData.finalLineHeight = (documentData.finalSize * documentData.lh) / documentData.s; + } else { + documentData.finalText = finalText; + len = documentData.finalText.length; + flag = false; + } + } + } + lineWidth = -trackingOffset; + cLength = 0; + var uncollapsedSpaces = 0; + var currentChar; + for (i = 0; i < len; i += 1) { + newLineFlag = false; + currentChar = documentData.finalText[i]; + charCode = currentChar.charCodeAt(0); + if (charCode === 13 || charCode === 3) { + uncollapsedSpaces = 0; + lineWidths.push(lineWidth); + maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; + lineWidth = -2 * trackingOffset; + val = ''; + newLineFlag = true; + currentLine += 1; + } else { + val = currentChar; + } + if (fontManager.chars) { + charData = fontManager.getCharData(currentChar, fontData.fStyle, fontManager.getFontByName(documentData.f).fFamily); + cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; + } else { + // var charWidth = fontManager.measureText(val, documentData.f, documentData.finalSize); + // tCanvasHelper.font = documentData.finalSize + 'px '+ fontManager.getFontByName(documentData.f).fFamily; + cLength = fontManager.measureText(val, documentData.f, documentData.finalSize); + } + + // + if (currentChar === ' ') { + uncollapsedSpaces += cLength + trackingOffset; + } else { + lineWidth += cLength + trackingOffset + uncollapsedSpaces; + uncollapsedSpaces = 0; + } + letters.push({ + l: cLength, an: cLength, add: currentSize, n: newLineFlag, anIndexes: [], val: val, line: currentLine, animatorJustifyOffset: 0, + }); + if (anchorGrouping == 2) { // eslint-disable-line eqeqeq + currentSize += cLength; + if (val === '' || val === ' ' || i === len - 1) { + if (val === '' || val === ' ') { + currentSize -= cLength; + } + while (currentPos <= i) { + letters[currentPos].an = currentSize; + letters[currentPos].ind = index; + letters[currentPos].extra = cLength; + currentPos += 1; + } + index += 1; + currentSize = 0; + } + } else if (anchorGrouping == 3) { // eslint-disable-line eqeqeq + currentSize += cLength; + if (val === '' || i === len - 1) { + if (val === '') { + currentSize -= cLength; + } + while (currentPos <= i) { + letters[currentPos].an = currentSize; + letters[currentPos].ind = index; + letters[currentPos].extra = cLength; + currentPos += 1; + } + currentSize = 0; + index += 1; + } + } else { + letters[index].ind = index; + letters[index].extra = 0; + index += 1; + } + } + documentData.l = letters; + maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; + lineWidths.push(lineWidth); + if (documentData.sz) { + documentData.boxWidth = documentData.sz[0]; + documentData.justifyOffset = 0; + } else { + documentData.boxWidth = maxLineWidth; + switch (documentData.j) { + case 1: + documentData.justifyOffset = -documentData.boxWidth; + break; + case 2: + documentData.justifyOffset = -documentData.boxWidth / 2; + break; + default: + documentData.justifyOffset = 0; + } + } + documentData.lineWidths = lineWidths; + + var animators = data.a; var animatorData; var + letterData; + jLen = animators.length; + var based; var ind; var + indexes = []; + for (j = 0; j < jLen; j += 1) { + animatorData = animators[j]; + if (animatorData.a.sc) { + documentData.strokeColorAnim = true; + } + if (animatorData.a.sw) { + documentData.strokeWidthAnim = true; + } + if (animatorData.a.fc || animatorData.a.fh || animatorData.a.fs || animatorData.a.fb) { + documentData.fillColorAnim = true; + } + ind = 0; + based = animatorData.s.b; + for (i = 0; i < len; i += 1) { + letterData = letters[i]; + letterData.anIndexes[j] = ind; + if ((based == 1 && letterData.val !== '') || (based == 2 && letterData.val !== '' && letterData.val !== ' ') || (based == 3 && (letterData.n || letterData.val == ' ' || i == len - 1)) || (based == 4 && (letterData.n || i == len - 1))) { // eslint-disable-line eqeqeq + if (animatorData.s.rn === 1) { + indexes.push(ind); + } + ind += 1; + } + } + data.a[j].s.totalChars = ind; + var currentInd = -1; var + newInd; + if (animatorData.s.rn === 1) { + for (i = 0; i < len; i += 1) { + letterData = letters[i]; + if (currentInd != letterData.anIndexes[j]) { // eslint-disable-line eqeqeq + currentInd = letterData.anIndexes[j]; + newInd = indexes.splice(Math.floor(Math.random() * indexes.length), 1)[0]; + } + letterData.anIndexes[j] = newInd; + } + } + } + documentData.yOffset = documentData.finalLineHeight || documentData.finalSize * 1.2; + documentData.ls = documentData.ls || 0; + documentData.ascent = (fontData.ascent * documentData.finalSize) / 100; + }; + + TextProperty.prototype.updateDocumentData = function (newData, index) { + index = index === undefined ? this.keysIndex : index; + var dData = this.copyData({}, this.data.d.k[index].s); + dData = this.copyData(dData, newData); + this.data.d.k[index].s = dData; + this.recalculate(index); + this.elem.addDynamicProperty(this); + }; + + TextProperty.prototype.recalculate = function (index) { + var dData = this.data.d.k[index].s; + dData.__complete = false; + this.keysIndex = 0; + this._isFirstFrame = true; + this.getValue(dData); + }; + + TextProperty.prototype.canResizeFont = function (_canResize) { + this.canResize = _canResize; + this.recalculate(this.keysIndex); + this.elem.addDynamicProperty(this); + }; + + TextProperty.prototype.setMinimumFontSize = function (_fontValue) { + this.minimumFontSize = Math.floor(_fontValue) || 1; + this.recalculate(this.keysIndex); + this.elem.addDynamicProperty(this); + }; + + const TextSelectorProp = (function () { + var max = Math.max; + var min = Math.min; + var floor = Math.floor; + + function TextSelectorPropFactory(elem, data) { + this._currentTextLength = -1; + this.k = false; + this.data = data; + this.elem = elem; + this.comp = elem.comp; + this.finalS = 0; + this.finalE = 0; + this.initDynamicPropertyContainer(elem); + this.s = PropertyFactory.getProp(elem, data.s || { k: 0 }, 0, 0, this); + if ('e' in data) { + this.e = PropertyFactory.getProp(elem, data.e, 0, 0, this); + } else { + this.e = { v: 100 }; + } + this.o = PropertyFactory.getProp(elem, data.o || { k: 0 }, 0, 0, this); + this.xe = PropertyFactory.getProp(elem, data.xe || { k: 0 }, 0, 0, this); + this.ne = PropertyFactory.getProp(elem, data.ne || { k: 0 }, 0, 0, this); + this.sm = PropertyFactory.getProp(elem, data.sm || { k: 100 }, 0, 0, this); + this.a = PropertyFactory.getProp(elem, data.a, 0, 0.01, this); + if (!this.dynamicProperties.length) { + this.getValue(); + } + } + + TextSelectorPropFactory.prototype = { + getMult: function (ind) { + if (this._currentTextLength !== this.elem.textProperty.currentData.l.length) { + this.getValue(); + } + var x1 = 0; + var y1 = 0; + var x2 = 1; + var y2 = 1; + if (this.ne.v > 0) { + x1 = this.ne.v / 100.0; + } else { + y1 = -this.ne.v / 100.0; + } + if (this.xe.v > 0) { + x2 = 1.0 - this.xe.v / 100.0; + } else { + y2 = 1.0 + this.xe.v / 100.0; + } + var easer = BezierFactory.getBezierEasing(x1, y1, x2, y2).get; + + var mult = 0; + var s = this.finalS; + var e = this.finalE; + var type = this.data.sh; + if (type === 2) { + if (e === s) { + mult = ind >= e ? 1 : 0; + } else { + mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); + } + mult = easer(mult); + } else if (type === 3) { + if (e === s) { + mult = ind >= e ? 0 : 1; + } else { + mult = 1 - max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); + } + + mult = easer(mult); + } else if (type === 4) { + if (e === s) { + mult = 0; + } else { + mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); + if (mult < 0.5) { + mult *= 2; + } else { + mult = 1 - 2 * (mult - 0.5); + } + } + mult = easer(mult); + } else if (type === 5) { + if (e === s) { + mult = 0; + } else { + var tot = e - s; + /* ind += 0.5; + mult = -4/(tot*tot)*(ind*ind)+(4/tot)*ind; */ + ind = min(max(0, ind + 0.5 - s), e - s); + var x = -tot / 2 + ind; + var a = tot / 2; + mult = Math.sqrt(1 - (x * x) / (a * a)); + } + mult = easer(mult); + } else if (type === 6) { + if (e === s) { + mult = 0; + } else { + ind = min(max(0, ind + 0.5 - s), e - s); + mult = (1 + (Math.cos((Math.PI + Math.PI * 2 * (ind) / (e - s))))) / 2; // eslint-disable-line + } + mult = easer(mult); + } else { + if (ind >= floor(s)) { + if (ind - s < 0) { + mult = max(0, min(min(e, 1) - (s - ind), 1)); + } else { + mult = max(0, min(e - ind, 1)); + } + } + mult = easer(mult); + } + // Smoothness implementation. + // The smoothness represents a reduced range of the original [0; 1] range. + // if smoothness is 25%, the new range will be [0.375; 0.625] + // Steps are: + // - find the lower value of the new range (threshold) + // - if multiplier is smaller than that value, floor it to 0 + // - if it is larger, + // - subtract the threshold + // - divide it by the smoothness (this will return the range to [0; 1]) + // Note: If it doesn't work on some scenarios, consider applying it before the easer. + if (this.sm.v !== 100) { + var smoothness = this.sm.v * 0.01; + if (smoothness === 0) { + smoothness = 0.00000001; + } + var threshold = 0.5 - smoothness * 0.5; + if (mult < threshold) { + mult = 0; + } else { + mult = (mult - threshold) / smoothness; + if (mult > 1) { + mult = 1; + } + } + } + return mult * this.a.v; + }, + getValue: function (newCharsFlag) { + this.iterateDynamicProperties(); + this._mdf = newCharsFlag || this._mdf; + this._currentTextLength = this.elem.textProperty.currentData.l.length || 0; + if (newCharsFlag && this.data.r === 2) { + this.e.v = this._currentTextLength; + } + var divisor = this.data.r === 2 ? 1 : 100 / this.data.totalChars; + var o = this.o.v / divisor; + var s = this.s.v / divisor + o; + var e = (this.e.v / divisor) + o; + if (s > e) { + var _s = s; + s = e; + e = _s; + } + this.finalS = s; + this.finalE = e; + }, + }; + extendPrototype([DynamicPropertyContainer], TextSelectorPropFactory); + + function getTextSelectorProp(elem, data, arr) { + return new TextSelectorPropFactory(elem, data, arr); + } + + return { + getTextSelectorProp: getTextSelectorProp, + }; + }()); + + function TextAnimatorDataProperty(elem, animatorProps, container) { + var defaultData = { propType: false }; + var getProp = PropertyFactory.getProp; + var textAnimatorAnimatables = animatorProps.a; + this.a = { + r: textAnimatorAnimatables.r ? getProp(elem, textAnimatorAnimatables.r, 0, degToRads, container) : defaultData, + rx: textAnimatorAnimatables.rx ? getProp(elem, textAnimatorAnimatables.rx, 0, degToRads, container) : defaultData, + ry: textAnimatorAnimatables.ry ? getProp(elem, textAnimatorAnimatables.ry, 0, degToRads, container) : defaultData, + sk: textAnimatorAnimatables.sk ? getProp(elem, textAnimatorAnimatables.sk, 0, degToRads, container) : defaultData, + sa: textAnimatorAnimatables.sa ? getProp(elem, textAnimatorAnimatables.sa, 0, degToRads, container) : defaultData, + s: textAnimatorAnimatables.s ? getProp(elem, textAnimatorAnimatables.s, 1, 0.01, container) : defaultData, + a: textAnimatorAnimatables.a ? getProp(elem, textAnimatorAnimatables.a, 1, 0, container) : defaultData, + o: textAnimatorAnimatables.o ? getProp(elem, textAnimatorAnimatables.o, 0, 0.01, container) : defaultData, + p: textAnimatorAnimatables.p ? getProp(elem, textAnimatorAnimatables.p, 1, 0, container) : defaultData, + sw: textAnimatorAnimatables.sw ? getProp(elem, textAnimatorAnimatables.sw, 0, 0, container) : defaultData, + sc: textAnimatorAnimatables.sc ? getProp(elem, textAnimatorAnimatables.sc, 1, 0, container) : defaultData, + fc: textAnimatorAnimatables.fc ? getProp(elem, textAnimatorAnimatables.fc, 1, 0, container) : defaultData, + fh: textAnimatorAnimatables.fh ? getProp(elem, textAnimatorAnimatables.fh, 0, 0, container) : defaultData, + fs: textAnimatorAnimatables.fs ? getProp(elem, textAnimatorAnimatables.fs, 0, 0.01, container) : defaultData, + fb: textAnimatorAnimatables.fb ? getProp(elem, textAnimatorAnimatables.fb, 0, 0.01, container) : defaultData, + t: textAnimatorAnimatables.t ? getProp(elem, textAnimatorAnimatables.t, 0, 0, container) : defaultData, + }; + + this.s = TextSelectorProp.getTextSelectorProp(elem, animatorProps.s, container); + this.s.t = animatorProps.s.t; + } + + function TextAnimatorProperty(textData, renderType, elem) { + this._isFirstFrame = true; + this._hasMaskedPath = false; + this._frameId = -1; + this._textData = textData; + this._renderType = renderType; + this._elem = elem; + this._animatorsData = createSizedArray(this._textData.a.length); + this._pathData = {}; + this._moreOptions = { + alignment: {}, + }; + this.renderedLetters = []; + this.lettersChangedFlag = false; + this.initDynamicPropertyContainer(elem); + } + + TextAnimatorProperty.prototype.searchProperties = function () { + var i; + var len = this._textData.a.length; + var animatorProps; + var getProp = PropertyFactory.getProp; + for (i = 0; i < len; i += 1) { + animatorProps = this._textData.a[i]; + this._animatorsData[i] = new TextAnimatorDataProperty(this._elem, animatorProps, this); + } + if (this._textData.p && 'm' in this._textData.p) { + this._pathData = { + a: getProp(this._elem, this._textData.p.a, 0, 0, this), + f: getProp(this._elem, this._textData.p.f, 0, 0, this), + l: getProp(this._elem, this._textData.p.l, 0, 0, this), + r: getProp(this._elem, this._textData.p.r, 0, 0, this), + p: getProp(this._elem, this._textData.p.p, 0, 0, this), + m: this._elem.maskManager.getMaskProperty(this._textData.p.m), + }; + this._hasMaskedPath = true; + } else { + this._hasMaskedPath = false; + } + this._moreOptions.alignment = getProp(this._elem, this._textData.m.a, 1, 0, this); + }; + + TextAnimatorProperty.prototype.getMeasures = function (documentData, lettersChangedFlag) { + this.lettersChangedFlag = lettersChangedFlag; + if (!this._mdf && !this._isFirstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m._mdf)) { + return; + } + this._isFirstFrame = false; + var alignment = this._moreOptions.alignment.v; + var animators = this._animatorsData; + var textData = this._textData; + var matrixHelper = this.mHelper; + var renderType = this._renderType; + var renderedLettersCount = this.renderedLetters.length; + var xPos; + var yPos; + var i; + var len; + var letters = documentData.l; + var pathInfo; + var currentLength; + var currentPoint; + var segmentLength; + var flag; + var pointInd; + var segmentInd; + var prevPoint; + var points; + var segments; + var partialLength; + var totalLength; + var perc; + var tanAngle; + var mask; + if (this._hasMaskedPath) { + mask = this._pathData.m; + if (!this._pathData.n || this._pathData._mdf) { + var paths = mask.v; + if (this._pathData.r.v) { + paths = paths.reverse(); + } + // TODO: release bezier data cached from previous pathInfo: this._pathData.pi + pathInfo = { + tLength: 0, + segments: [], + }; + len = paths._length - 1; + var bezierData; + totalLength = 0; + for (i = 0; i < len; i += 1) { + bezierData = bez.buildBezierData(paths.v[i], + paths.v[i + 1], + [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], + [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]); + pathInfo.tLength += bezierData.segmentLength; + pathInfo.segments.push(bezierData); + totalLength += bezierData.segmentLength; + } + i = len; + if (mask.v.c) { + bezierData = bez.buildBezierData(paths.v[i], + paths.v[0], + [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], + [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]); + pathInfo.tLength += bezierData.segmentLength; + pathInfo.segments.push(bezierData); + totalLength += bezierData.segmentLength; + } + this._pathData.pi = pathInfo; + } + pathInfo = this._pathData.pi; + + currentLength = this._pathData.f.v; + segmentInd = 0; + pointInd = 1; + segmentLength = 0; + flag = true; + segments = pathInfo.segments; + if (currentLength < 0 && mask.v.c) { + if (pathInfo.tLength < Math.abs(currentLength)) { + currentLength = -Math.abs(currentLength) % pathInfo.tLength; + } + segmentInd = segments.length - 1; + points = segments[segmentInd].points; + pointInd = points.length - 1; + while (currentLength < 0) { + currentLength += points[pointInd].partialLength; + pointInd -= 1; + if (pointInd < 0) { + segmentInd -= 1; + points = segments[segmentInd].points; + pointInd = points.length - 1; + } + } + } + points = segments[segmentInd].points; + prevPoint = points[pointInd - 1]; + currentPoint = points[pointInd]; + partialLength = currentPoint.partialLength; + } + + len = letters.length; + xPos = 0; + yPos = 0; + var yOff = documentData.finalSize * 1.2 * 0.714; + var firstLine = true; + var animatorProps; + var animatorSelector; + var j; + var jLen; + var letterValue; + + jLen = animators.length; + + var mult; + var ind = -1; + var offf; + var xPathPos; + var yPathPos; + var initPathPos = currentLength; + var initSegmentInd = segmentInd; + var initPointInd = pointInd; + var currentLine = -1; + var elemOpacity; + var sc; + var sw; + var fc; + var k; + var letterSw; + var letterSc; + var letterFc; + var letterM = ''; + var letterP = this.defaultPropsArray; + var letterO; + + // + if (documentData.j === 2 || documentData.j === 1) { + var animatorJustifyOffset = 0; + var animatorFirstCharOffset = 0; + var justifyOffsetMult = documentData.j === 2 ? -0.5 : -1; + var lastIndex = 0; + var isNewLine = true; + + for (i = 0; i < len; i += 1) { + if (letters[i].n) { + if (animatorJustifyOffset) { + animatorJustifyOffset += animatorFirstCharOffset; + } + while (lastIndex < i) { + letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; + lastIndex += 1; + } + animatorJustifyOffset = 0; + isNewLine = true; + } else { + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.t.propType) { + if (isNewLine && documentData.j === 2) { + animatorFirstCharOffset += animatorProps.t.v * justifyOffsetMult; + } + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + animatorJustifyOffset += animatorProps.t.v * mult[0] * justifyOffsetMult; + } else { + animatorJustifyOffset += animatorProps.t.v * mult * justifyOffsetMult; + } + } + } + isNewLine = false; + } + } + if (animatorJustifyOffset) { + animatorJustifyOffset += animatorFirstCharOffset; + } + while (lastIndex < i) { + letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; + lastIndex += 1; + } + } + // + + for (i = 0; i < len; i += 1) { + matrixHelper.reset(); + elemOpacity = 1; + if (letters[i].n) { + xPos = 0; + yPos += documentData.yOffset; + yPos += firstLine ? 1 : 0; + currentLength = initPathPos; + firstLine = false; + if (this._hasMaskedPath) { + segmentInd = initSegmentInd; + pointInd = initPointInd; + points = segments[segmentInd].points; + prevPoint = points[pointInd - 1]; + currentPoint = points[pointInd]; + partialLength = currentPoint.partialLength; + segmentLength = 0; + } + letterM = ''; + letterFc = ''; + letterSw = ''; + letterO = ''; + letterP = this.defaultPropsArray; + } else { + if (this._hasMaskedPath) { + if (currentLine !== letters[i].line) { + switch (documentData.j) { + case 1: + currentLength += totalLength - documentData.lineWidths[letters[i].line]; + break; + case 2: + currentLength += (totalLength - documentData.lineWidths[letters[i].line]) / 2; + break; + default: + break; + } + currentLine = letters[i].line; + } + if (ind !== letters[i].ind) { + if (letters[ind]) { + currentLength += letters[ind].extra; + } + currentLength += letters[i].an / 2; + ind = letters[i].ind; + } + currentLength += (alignment[0] * letters[i].an) * 0.005; + var animatorOffset = 0; + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.p.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + animatorOffset += animatorProps.p.v[0] * mult[0]; + } else { + animatorOffset += animatorProps.p.v[0] * mult; + } + } + if (animatorProps.a.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + animatorOffset += animatorProps.a.v[0] * mult[0]; + } else { + animatorOffset += animatorProps.a.v[0] * mult; + } + } + } + flag = true; + // Force alignment only works with a single line for now + if (this._pathData.a.v) { + currentLength = letters[0].an * 0.5 + ((totalLength - this._pathData.f.v - letters[0].an * 0.5 - letters[letters.length - 1].an * 0.5) * ind) / (len - 1); + currentLength += this._pathData.f.v; + } + while (flag) { + if (segmentLength + partialLength >= currentLength + animatorOffset || !points) { + perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength; + xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc; + yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc; + matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, -(alignment[1] * yOff) * 0.01); + flag = false; + } else if (points) { + segmentLength += currentPoint.partialLength; + pointInd += 1; + if (pointInd >= points.length) { + pointInd = 0; + segmentInd += 1; + if (!segments[segmentInd]) { + if (mask.v.c) { + pointInd = 0; + segmentInd = 0; + points = segments[segmentInd].points; + } else { + segmentLength -= currentPoint.partialLength; + points = null; + } + } else { + points = segments[segmentInd].points; + } + } + if (points) { + prevPoint = currentPoint; + currentPoint = points[pointInd]; + partialLength = currentPoint.partialLength; + } + } + } + offf = letters[i].an / 2 - letters[i].add; + matrixHelper.translate(-offf, 0, 0); + } else { + offf = letters[i].an / 2 - letters[i].add; + matrixHelper.translate(-offf, 0, 0); + + // Grouping alignment + matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, (-alignment[1] * yOff) * 0.01, 0); + } + + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.t.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + // This condition is to prevent applying tracking to first character in each line. Might be better to use a boolean "isNewLine" + if (xPos !== 0 || documentData.j !== 0) { + if (this._hasMaskedPath) { + if (mult.length) { + currentLength += animatorProps.t.v * mult[0]; + } else { + currentLength += animatorProps.t.v * mult; + } + } else if (mult.length) { + xPos += animatorProps.t.v * mult[0]; + } else { + xPos += animatorProps.t.v * mult; + } + } + } + } + if (documentData.strokeWidthAnim) { + sw = documentData.sw || 0; + } + if (documentData.strokeColorAnim) { + if (documentData.sc) { + sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]]; + } else { + sc = [0, 0, 0]; + } + } + if (documentData.fillColorAnim && documentData.fc) { + fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]]; + } + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.a.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + + if (mult.length) { + matrixHelper.translate(-animatorProps.a.v[0] * mult[0], -animatorProps.a.v[1] * mult[1], animatorProps.a.v[2] * mult[2]); + } else { + matrixHelper.translate(-animatorProps.a.v[0] * mult, -animatorProps.a.v[1] * mult, animatorProps.a.v[2] * mult); + } + } + } + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.s.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult[0]), 1 + ((animatorProps.s.v[1] - 1) * mult[1]), 1); + } else { + matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult), 1 + ((animatorProps.s.v[1] - 1) * mult), 1); + } + } + } + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (animatorProps.sk.propType) { + if (mult.length) { + matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1]); + } else { + matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult); + } + } + if (animatorProps.r.propType) { + if (mult.length) { + matrixHelper.rotateZ(-animatorProps.r.v * mult[2]); + } else { + matrixHelper.rotateZ(-animatorProps.r.v * mult); + } + } + if (animatorProps.ry.propType) { + if (mult.length) { + matrixHelper.rotateY(animatorProps.ry.v * mult[1]); + } else { + matrixHelper.rotateY(animatorProps.ry.v * mult); + } + } + if (animatorProps.rx.propType) { + if (mult.length) { + matrixHelper.rotateX(animatorProps.rx.v * mult[0]); + } else { + matrixHelper.rotateX(animatorProps.rx.v * mult); + } + } + if (animatorProps.o.propType) { + if (mult.length) { + elemOpacity += ((animatorProps.o.v) * mult[0] - elemOpacity) * mult[0]; + } else { + elemOpacity += ((animatorProps.o.v) * mult - elemOpacity) * mult; + } + } + if (documentData.strokeWidthAnim && animatorProps.sw.propType) { + if (mult.length) { + sw += animatorProps.sw.v * mult[0]; + } else { + sw += animatorProps.sw.v * mult; + } + } + if (documentData.strokeColorAnim && animatorProps.sc.propType) { + for (k = 0; k < 3; k += 1) { + if (mult.length) { + sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult[0]; + } else { + sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult; + } + } + } + if (documentData.fillColorAnim && documentData.fc) { + if (animatorProps.fc.propType) { + for (k = 0; k < 3; k += 1) { + if (mult.length) { + fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult[0]; + } else { + fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult; + } + } + } + if (animatorProps.fh.propType) { + if (mult.length) { + fc = addHueToRGB(fc, animatorProps.fh.v * mult[0]); + } else { + fc = addHueToRGB(fc, animatorProps.fh.v * mult); + } + } + if (animatorProps.fs.propType) { + if (mult.length) { + fc = addSaturationToRGB(fc, animatorProps.fs.v * mult[0]); + } else { + fc = addSaturationToRGB(fc, animatorProps.fs.v * mult); + } + } + if (animatorProps.fb.propType) { + if (mult.length) { + fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult[0]); + } else { + fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult); + } + } + } + } + + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + + if (animatorProps.p.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (this._hasMaskedPath) { + if (mult.length) { + matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1]); + } else { + matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); + } + } else if (mult.length) { + matrixHelper.translate(animatorProps.p.v[0] * mult[0], animatorProps.p.v[1] * mult[1], -animatorProps.p.v[2] * mult[2]); + } else { + matrixHelper.translate(animatorProps.p.v[0] * mult, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); + } + } + } + if (documentData.strokeWidthAnim) { + letterSw = sw < 0 ? 0 : sw; + } + if (documentData.strokeColorAnim) { + letterSc = 'rgb(' + Math.round(sc[0] * 255) + ',' + Math.round(sc[1] * 255) + ',' + Math.round(sc[2] * 255) + ')'; + } + if (documentData.fillColorAnim && documentData.fc) { + letterFc = 'rgb(' + Math.round(fc[0] * 255) + ',' + Math.round(fc[1] * 255) + ',' + Math.round(fc[2] * 255) + ')'; + } + + if (this._hasMaskedPath) { + matrixHelper.translate(0, -documentData.ls); + + matrixHelper.translate(0, (alignment[1] * yOff) * 0.01 + yPos, 0); + if (this._pathData.p.v) { + tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0]); + var rot = (Math.atan(tanAngle) * 180) / Math.PI; + if (currentPoint.point[0] < prevPoint.point[0]) { + rot += 180; + } + matrixHelper.rotate((-rot * Math.PI) / 180); + } + matrixHelper.translate(xPathPos, yPathPos, 0); + currentLength -= (alignment[0] * letters[i].an) * 0.005; + if (letters[i + 1] && ind !== letters[i + 1].ind) { + currentLength += letters[i].an / 2; + currentLength += (documentData.tr * 0.001) * documentData.finalSize; + } + } else { + matrixHelper.translate(xPos, yPos, 0); + + if (documentData.ps) { + // matrixHelper.translate(documentData.ps[0],documentData.ps[1],0); + matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); + } + switch (documentData.j) { + case 1: + matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]), 0, 0); + break; + case 2: + matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]) / 2, 0, 0); + break; + default: + break; + } + matrixHelper.translate(0, -documentData.ls); + matrixHelper.translate(offf, 0, 0); + matrixHelper.translate((alignment[0] * letters[i].an) * 0.005, (alignment[1] * yOff) * 0.01, 0); + xPos += letters[i].l + (documentData.tr * 0.001) * documentData.finalSize; + } + if (renderType === 'html') { + letterM = matrixHelper.toCSS(); + } else if (renderType === 'svg') { + letterM = matrixHelper.to2dCSS(); + } else { + letterP = [matrixHelper.props[0], matrixHelper.props[1], matrixHelper.props[2], matrixHelper.props[3], matrixHelper.props[4], matrixHelper.props[5], matrixHelper.props[6], matrixHelper.props[7], matrixHelper.props[8], matrixHelper.props[9], matrixHelper.props[10], matrixHelper.props[11], matrixHelper.props[12], matrixHelper.props[13], matrixHelper.props[14], matrixHelper.props[15]]; + } + letterO = elemOpacity; + } + + if (renderedLettersCount <= i) { + letterValue = new LetterProps(letterO, letterSw, letterSc, letterFc, letterM, letterP); + this.renderedLetters.push(letterValue); + renderedLettersCount += 1; + this.lettersChangedFlag = true; + } else { + letterValue = this.renderedLetters[i]; + this.lettersChangedFlag = letterValue.update(letterO, letterSw, letterSc, letterFc, letterM, letterP) || this.lettersChangedFlag; + } + } + }; + + TextAnimatorProperty.prototype.getValue = function () { + if (this._elem.globalData.frameId === this._frameId) { + return; + } + this._frameId = this._elem.globalData.frameId; + this.iterateDynamicProperties(); + }; + + TextAnimatorProperty.prototype.mHelper = new Matrix(); + TextAnimatorProperty.prototype.defaultPropsArray = []; + extendPrototype([DynamicPropertyContainer], TextAnimatorProperty); + + function ITextElement() { + } + + ITextElement.prototype.initElement = function (data, globalData, comp) { + this.lettersChangedFlag = true; + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.textProperty = new TextProperty(this, data.t, this.dynamicProperties); + this.textAnimator = new TextAnimatorProperty(data.t, this.renderType, this); + this.initTransform(data, globalData, comp); + this.initHierarchy(); + this.initRenderable(); + this.initRendererElement(); + this.createContainerElements(); + this.createRenderableComponents(); + this.createContent(); + this.hide(); + this.textAnimator.searchProperties(this.dynamicProperties); + }; + + ITextElement.prototype.prepareFrame = function (num) { + this._mdf = false; + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + if (this.textProperty._mdf || this.textProperty._isFirstFrame) { + this.buildNewText(); + this.textProperty._isFirstFrame = false; + this.textProperty._mdf = false; + } + }; + + ITextElement.prototype.createPathShape = function (matrixHelper, shapes) { + var j; + var jLen = shapes.length; + var pathNodes; + var shapeStr = ''; + for (j = 0; j < jLen; j += 1) { + if (shapes[j].ty === 'sh') { + pathNodes = shapes[j].ks.k; + shapeStr += buildShapeString(pathNodes, pathNodes.i.length, true, matrixHelper); + } + } + return shapeStr; + }; + + ITextElement.prototype.updateDocumentData = function (newData, index) { + this.textProperty.updateDocumentData(newData, index); + }; + + ITextElement.prototype.canResizeFont = function (_canResize) { + this.textProperty.canResizeFont(_canResize); + }; + + ITextElement.prototype.setMinimumFontSize = function (_fontSize) { + this.textProperty.setMinimumFontSize(_fontSize); + }; + + ITextElement.prototype.applyTextPropertiesToMatrix = function (documentData, matrixHelper, lineNumber, xPos, yPos) { + if (documentData.ps) { + matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); + } + matrixHelper.translate(0, -documentData.ls, 0); + switch (documentData.j) { + case 1: + matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]), 0, 0); + break; + case 2: + matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]) / 2, 0, 0); + break; + default: + break; + } + matrixHelper.translate(xPos, yPos, 0); + }; + + ITextElement.prototype.buildColor = function (colorData) { + return 'rgb(' + Math.round(colorData[0] * 255) + ',' + Math.round(colorData[1] * 255) + ',' + Math.round(colorData[2] * 255) + ')'; + }; + + ITextElement.prototype.emptyProp = new LetterProps(); + + ITextElement.prototype.destroy = function () { + + }; + + var emptyShapeData = { + shapes: [], + }; + + function SVGTextLottieElement(data, globalData, comp) { + this.textSpans = []; + this.renderType = 'svg'; + this.initElement(data, globalData, comp); + } + + extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement, ITextElement], SVGTextLottieElement); + + SVGTextLottieElement.prototype.createContent = function () { + if (this.data.singleShape && !this.globalData.fontManager.chars) { + this.textContainer = createNS('text'); + } + }; + + SVGTextLottieElement.prototype.buildTextContents = function (textArray) { + var i = 0; + var len = textArray.length; + var textContents = []; + var currentTextContent = ''; + while (i < len) { + if (textArray[i] === String.fromCharCode(13) || textArray[i] === String.fromCharCode(3)) { + textContents.push(currentTextContent); + currentTextContent = ''; + } else { + currentTextContent += textArray[i]; + } + i += 1; + } + textContents.push(currentTextContent); + return textContents; + }; + + SVGTextLottieElement.prototype.buildShapeData = function (data, scale) { + // data should probably be cloned to apply scale separately to each instance of a text on different layers + // but since text internal content gets only rendered once and then it's never rerendered, + // it's probably safe not to clone data and reuse always the same instance even if the object is mutated. + // Avoiding cloning is preferred since cloning each character shape data is expensive + if (data.shapes && data.shapes.length) { + var shape = data.shapes[0]; + if (shape.it) { + var shapeItem = shape.it[shape.it.length - 1]; + if (shapeItem.s) { + shapeItem.s.k[0] = scale; + shapeItem.s.k[1] = scale; + } + } + } + return data; + }; + + SVGTextLottieElement.prototype.buildNewText = function () { + this.addDynamicProperty(this); + var i; + var len; + + var documentData = this.textProperty.currentData; + this.renderedLetters = createSizedArray(documentData ? documentData.l.length : 0); + if (documentData.fc) { + this.layerElement.setAttribute('fill', this.buildColor(documentData.fc)); + } else { + this.layerElement.setAttribute('fill', 'rgba(0,0,0,0)'); + } + if (documentData.sc) { + this.layerElement.setAttribute('stroke', this.buildColor(documentData.sc)); + this.layerElement.setAttribute('stroke-width', documentData.sw); + } + this.layerElement.setAttribute('font-size', documentData.finalSize); + var fontData = this.globalData.fontManager.getFontByName(documentData.f); + if (fontData.fClass) { + this.layerElement.setAttribute('class', fontData.fClass); + } else { + this.layerElement.setAttribute('font-family', fontData.fFamily); + var fWeight = documentData.fWeight; + var fStyle = documentData.fStyle; + this.layerElement.setAttribute('font-style', fStyle); + this.layerElement.setAttribute('font-weight', fWeight); + } + this.layerElement.setAttribute('aria-label', documentData.t); + + var letters = documentData.l || []; + var usesGlyphs = !!this.globalData.fontManager.chars; + len = letters.length; + + var tSpan; + var matrixHelper = this.mHelper; + var shapeStr = ''; + var singleShape = this.data.singleShape; + var xPos = 0; + var yPos = 0; + var firstLine = true; + var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; + if (singleShape && !usesGlyphs && !documentData.sz) { + var tElement = this.textContainer; + var justify = 'start'; + switch (documentData.j) { + case 1: + justify = 'end'; + break; + case 2: + justify = 'middle'; + break; + default: + justify = 'start'; + break; + } + tElement.setAttribute('text-anchor', justify); + tElement.setAttribute('letter-spacing', trackingOffset); + var textContent = this.buildTextContents(documentData.finalText); + len = textContent.length; + yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0; + for (i = 0; i < len; i += 1) { + tSpan = this.textSpans[i].span || createNS('tspan'); + tSpan.textContent = textContent[i]; + tSpan.setAttribute('x', 0); + tSpan.setAttribute('y', yPos); + tSpan.style.display = 'inherit'; + tElement.appendChild(tSpan); + if (!this.textSpans[i]) { + this.textSpans[i] = { + span: null, + glyph: null, + }; + } + this.textSpans[i].span = tSpan; + yPos += documentData.finalLineHeight; + } + + this.layerElement.appendChild(tElement); + } else { + var cachedSpansLength = this.textSpans.length; + var charData; + for (i = 0; i < len; i += 1) { + if (!this.textSpans[i]) { + this.textSpans[i] = { + span: null, + childSpan: null, + glyph: null, + }; + } + if (!usesGlyphs || !singleShape || i === 0) { + tSpan = cachedSpansLength > i ? this.textSpans[i].span : createNS(usesGlyphs ? 'g' : 'text'); + if (cachedSpansLength <= i) { + tSpan.setAttribute('stroke-linecap', 'butt'); + tSpan.setAttribute('stroke-linejoin', 'round'); + tSpan.setAttribute('stroke-miterlimit', '4'); + this.textSpans[i].span = tSpan; + if (usesGlyphs) { + var childSpan = createNS('g'); + tSpan.appendChild(childSpan); + this.textSpans[i].childSpan = childSpan; + } + this.textSpans[i].span = tSpan; + this.layerElement.appendChild(tSpan); + } + tSpan.style.display = 'inherit'; + } + + matrixHelper.reset(); + if (singleShape) { + if (letters[i].n) { + xPos = -trackingOffset; + yPos += documentData.yOffset; + yPos += firstLine ? 1 : 0; + firstLine = false; + } + this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); + xPos += letters[i].l || 0; + // xPos += letters[i].val === ' ' ? 0 : trackingOffset; + xPos += trackingOffset; + } + if (usesGlyphs) { + charData = this.globalData.fontManager.getCharData( + documentData.finalText[i], + fontData.fStyle, + this.globalData.fontManager.getFontByName(documentData.f).fFamily + ); + var glyphElement; + // t === 1 means the character has been replaced with an animated shaped + if (charData.t === 1) { + glyphElement = new SVGCompElement(charData.data, this.globalData, this); + } else { + var data = emptyShapeData; + if (charData.data && charData.data.shapes) { + data = this.buildShapeData(charData.data, documentData.finalSize); + } + glyphElement = new SVGShapeElement(data, this.globalData, this); + } + if (this.textSpans[i].glyph) { + var glyph = this.textSpans[i].glyph; + this.textSpans[i].childSpan.removeChild(glyph.layerElement); + glyph.destroy(); + } + this.textSpans[i].glyph = glyphElement; + glyphElement._debug = true; + glyphElement.prepareFrame(0); + glyphElement.renderFrame(); + this.textSpans[i].childSpan.appendChild(glyphElement.layerElement); + // when using animated shapes, the layer will be scaled instead of replacing the internal scale + // this might have issues with strokes and might need a different solution + if (charData.t === 1) { + this.textSpans[i].childSpan.setAttribute('transform', 'scale(' + documentData.finalSize / 100 + ',' + documentData.finalSize / 100 + ')'); + } + } else { + if (singleShape) { + tSpan.setAttribute('transform', 'translate(' + matrixHelper.props[12] + ',' + matrixHelper.props[13] + ')'); + } + tSpan.textContent = letters[i].val; + tSpan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + } + // + } + if (singleShape && tSpan) { + tSpan.setAttribute('d', shapeStr); + } + } + while (i < this.textSpans.length) { + this.textSpans[i].span.style.display = 'none'; + i += 1; + } + + this._sizeChanged = true; + }; + + SVGTextLottieElement.prototype.sourceRectAtTime = function () { + this.prepareFrame(this.comp.renderedFrame - this.data.st); + this.renderInnerContent(); + if (this._sizeChanged) { + this._sizeChanged = false; + var textBox = this.layerElement.getBBox(); + this.bbox = { + top: textBox.y, + left: textBox.x, + width: textBox.width, + height: textBox.height, + }; + } + return this.bbox; + }; + + SVGTextLottieElement.prototype.getValue = function () { + var i; + var len = this.textSpans.length; + var glyphElement; + this.renderedFrame = this.comp.renderedFrame; + for (i = 0; i < len; i += 1) { + glyphElement = this.textSpans[i].glyph; + if (glyphElement) { + glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st); + if (glyphElement._mdf) { + this._mdf = true; + } + } + } + }; + + SVGTextLottieElement.prototype.renderInnerContent = function () { + if (!this.data.singleShape || this._mdf) { + this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); + if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) { + this._sizeChanged = true; + var i; + var len; + var renderedLetters = this.textAnimator.renderedLetters; + + var letters = this.textProperty.currentData.l; + + len = letters.length; + var renderedLetter; + var textSpan; + var glyphElement; + for (i = 0; i < len; i += 1) { + if (!letters[i].n) { + renderedLetter = renderedLetters[i]; + textSpan = this.textSpans[i].span; + glyphElement = this.textSpans[i].glyph; + if (glyphElement) { + glyphElement.renderFrame(); + } + if (renderedLetter._mdf.m) { + textSpan.setAttribute('transform', renderedLetter.m); + } + if (renderedLetter._mdf.o) { + textSpan.setAttribute('opacity', renderedLetter.o); + } + if (renderedLetter._mdf.sw) { + textSpan.setAttribute('stroke-width', renderedLetter.sw); + } + if (renderedLetter._mdf.sc) { + textSpan.setAttribute('stroke', renderedLetter.sc); + } + if (renderedLetter._mdf.fc) { + textSpan.setAttribute('fill', renderedLetter.fc); + } + } + } + } + } + }; + + function ISolidElement(data, globalData, comp) { + this.initElement(data, globalData, comp); + } + extendPrototype([IImageElement], ISolidElement); + + ISolidElement.prototype.createContent = function () { + var rect = createNS('rect'); + /// /rect.style.width = this.data.sw; + /// /rect.style.height = this.data.sh; + /// /rect.style.fill = this.data.sc; + rect.setAttribute('width', this.data.sw); + rect.setAttribute('height', this.data.sh); + rect.setAttribute('fill', this.data.sc); + this.layerElement.appendChild(rect); + }; + + function NullElement(data, globalData, comp) { + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.initFrame(); + this.initTransform(data, globalData, comp); + this.initHierarchy(); + } + + NullElement.prototype.prepareFrame = function (num) { + this.prepareProperties(num, true); + }; + + NullElement.prototype.renderFrame = function () { + }; + + NullElement.prototype.getBaseElement = function () { + return null; + }; + + NullElement.prototype.destroy = function () { + }; + + NullElement.prototype.sourceRectAtTime = function () { + }; + + NullElement.prototype.hide = function () { + }; + + extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement], NullElement); + + function SVGRendererBase() { + } + + extendPrototype([BaseRenderer], SVGRendererBase); + + SVGRendererBase.prototype.createNull = function (data) { + return new NullElement(data, this.globalData, this); + }; + + SVGRendererBase.prototype.createShape = function (data) { + return new SVGShapeElement(data, this.globalData, this); + }; + + SVGRendererBase.prototype.createText = function (data) { + return new SVGTextLottieElement(data, this.globalData, this); + }; + + SVGRendererBase.prototype.createImage = function (data) { + return new IImageElement(data, this.globalData, this); + }; + + SVGRendererBase.prototype.createSolid = function (data) { + return new ISolidElement(data, this.globalData, this); + }; + + SVGRendererBase.prototype.configAnimation = function (animData) { + this.svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + if (this.renderConfig.viewBoxSize) { + this.svgElement.setAttribute('viewBox', this.renderConfig.viewBoxSize); + } else { + this.svgElement.setAttribute('viewBox', '0 0 ' + animData.w + ' ' + animData.h); + } + + if (!this.renderConfig.viewBoxOnly) { + this.svgElement.setAttribute('width', animData.w); + this.svgElement.setAttribute('height', animData.h); + this.svgElement.style.width = '100%'; + this.svgElement.style.height = '100%'; + this.svgElement.style.transform = 'translate3d(0,0,0)'; + this.svgElement.style.contentVisibility = this.renderConfig.contentVisibility; + } + if (this.renderConfig.width) { + this.svgElement.setAttribute('width', this.renderConfig.width); + } + if (this.renderConfig.height) { + this.svgElement.setAttribute('height', this.renderConfig.height); + } + if (this.renderConfig.className) { + this.svgElement.setAttribute('class', this.renderConfig.className); + } + if (this.renderConfig.id) { + this.svgElement.setAttribute('id', this.renderConfig.id); + } + if (this.renderConfig.focusable !== undefined) { + this.svgElement.setAttribute('focusable', this.renderConfig.focusable); + } + this.svgElement.setAttribute('preserveAspectRatio', this.renderConfig.preserveAspectRatio); + // this.layerElement.style.transform = 'translate3d(0,0,0)'; + // this.layerElement.style.transformOrigin = this.layerElement.style.mozTransformOrigin = this.layerElement.style.webkitTransformOrigin = this.layerElement.style['-webkit-transform'] = "0px 0px 0px"; + this.animationItem.wrapper.appendChild(this.svgElement); + // Mask animation + var defs = this.globalData.defs; + + this.setupGlobalData(animData, defs); + this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; + this.data = animData; + + var maskElement = createNS('clipPath'); + var rect = createNS('rect'); + rect.setAttribute('width', animData.w); + rect.setAttribute('height', animData.h); + rect.setAttribute('x', 0); + rect.setAttribute('y', 0); + var maskId = createElementID(); + maskElement.setAttribute('id', maskId); + maskElement.appendChild(rect); + this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + maskId + ')'); + + defs.appendChild(maskElement); + this.layers = animData.layers; + this.elements = createSizedArray(animData.layers.length); + }; + + SVGRendererBase.prototype.destroy = function () { + if (this.animationItem.wrapper) { + this.animationItem.wrapper.innerText = ''; + } + this.layerElement = null; + this.globalData.defs = null; + var i; + var len = this.layers ? this.layers.length : 0; + for (i = 0; i < len; i += 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + this.elements.length = 0; + this.destroyed = true; + this.animationItem = null; + }; + + SVGRendererBase.prototype.updateContainerSize = function () { + }; + + SVGRendererBase.prototype.buildItem = function (pos) { + var elements = this.elements; + if (elements[pos] || this.layers[pos].ty === 99) { + return; + } + elements[pos] = true; + var element = this.createItem(this.layers[pos]); + + elements[pos] = element; + if (getExpressionsPlugin()) { + if (this.layers[pos].ty === 0) { + this.globalData.projectInterface.registerComposition(element); + } + element.initExpressions(); + } + this.appendElementInPos(element, pos); + if (this.layers[pos].tt) { + if (!this.elements[pos - 1] || this.elements[pos - 1] === true) { + this.buildItem(pos - 1); + this.addPendingElement(element); + } else { + element.setMatte(elements[pos - 1].layerId); + } + } + }; + + SVGRendererBase.prototype.checkPendingElements = function () { + while (this.pendingElements.length) { + var element = this.pendingElements.pop(); + element.checkParenting(); + if (element.data.tt) { + var i = 0; + var len = this.elements.length; + while (i < len) { + if (this.elements[i] === element) { + element.setMatte(this.elements[i - 1].layerId); + break; + } + i += 1; + } + } + } + }; + + SVGRendererBase.prototype.renderFrame = function (num) { + if (this.renderedFrame === num || this.destroyed) { + return; + } + if (num === null) { + num = this.renderedFrame; + } else { + this.renderedFrame = num; + } + // console.log('-------'); + // console.log('FRAME ',num); + this.globalData.frameNum = num; + this.globalData.frameId += 1; + this.globalData.projectInterface.currentFrame = num; + this.globalData._mdf = false; + var i; + var len = this.layers.length; + if (!this.completeLayers) { + this.checkLayers(num); + } + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].prepareFrame(num - this.layers[i].st); + } + } + if (this.globalData._mdf) { + for (i = 0; i < len; i += 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } + } + }; + + SVGRendererBase.prototype.appendElementInPos = function (element, pos) { + var newElement = element.getBaseElement(); + if (!newElement) { + return; + } + var i = 0; + var nextElement; + while (i < pos) { + if (this.elements[i] && this.elements[i] !== true && this.elements[i].getBaseElement()) { + nextElement = this.elements[i].getBaseElement(); + } + i += 1; + } + if (nextElement) { + this.layerElement.insertBefore(newElement, nextElement); + } else { + this.layerElement.appendChild(newElement); + } + }; + + SVGRendererBase.prototype.hide = function () { + this.layerElement.style.display = 'none'; + }; + + SVGRendererBase.prototype.show = function () { + this.layerElement.style.display = 'block'; + }; + + function ICompElement() {} + + extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement, RenderableDOMElement], ICompElement); + + ICompElement.prototype.initElement = function (data, globalData, comp) { + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.initTransform(data, globalData, comp); + this.initRenderable(); + this.initHierarchy(); + this.initRendererElement(); + this.createContainerElements(); + this.createRenderableComponents(); + if (this.data.xt || !globalData.progressiveLoad) { + this.buildAllItems(); + } + this.hide(); + }; + + /* ICompElement.prototype.hide = function(){ + if(!this.hidden){ + this.hideElement(); + var i,len = this.elements.length; + for( i = 0; i < len; i+=1 ){ + if(this.elements[i]){ + this.elements[i].hide(); + } + } + } + }; */ + + ICompElement.prototype.prepareFrame = function (num) { + this._mdf = false; + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + if (!this.isInRange && !this.data.xt) { + return; + } + + if (!this.tm._placeholder) { + var timeRemapped = this.tm.v; + if (timeRemapped === this.data.op) { + timeRemapped = this.data.op - 1; + } + this.renderedFrame = timeRemapped; + } else { + this.renderedFrame = num / this.data.sr; + } + var i; + var len = this.elements.length; + if (!this.completeLayers) { + this.checkLayers(this.renderedFrame); + } + // This iteration needs to be backwards because of how expressions connect between each other + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].prepareFrame(this.renderedFrame - this.layers[i].st); + if (this.elements[i]._mdf) { + this._mdf = true; + } + } + } + }; + + ICompElement.prototype.renderInnerContent = function () { + var i; + var len = this.layers.length; + for (i = 0; i < len; i += 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } + }; + + ICompElement.prototype.setElements = function (elems) { + this.elements = elems; + }; + + ICompElement.prototype.getElements = function () { + return this.elements; + }; + + ICompElement.prototype.destroyElements = function () { + var i; + var len = this.layers.length; + for (i = 0; i < len; i += 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + }; + + ICompElement.prototype.destroy = function () { + this.destroyElements(); + this.destroyBaseElement(); + }; + + function SVGCompElement(data, globalData, comp) { + this.layers = data.layers; + this.supports3d = true; + this.completeLayers = false; + this.pendingElements = []; + this.elements = this.layers ? createSizedArray(this.layers.length) : []; + this.initElement(data, globalData, comp); + this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; } - } -}; - -ICompElement.prototype.renderInnerContent = function () { - var i; - var len = this.layers.length; - for (i = 0; i < len; i += 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } -}; - -ICompElement.prototype.setElements = function (elems) { - this.elements = elems; -}; - -ICompElement.prototype.getElements = function () { - return this.elements; -}; - -ICompElement.prototype.destroyElements = function () { - var i; - var len = this.layers.length; - for (i = 0; i < len; i += 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } -}; - -ICompElement.prototype.destroy = function () { - this.destroyElements(); - this.destroyBaseElement(); -}; - -function SVGCompElement(data, globalData, comp) { - this.layers = data.layers; - this.supports3d = true; - this.completeLayers = false; - this.pendingElements = []; - this.elements = this.layers ? createSizedArray(this.layers.length) : []; - this.initElement(data, globalData, comp); - this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; -} - -extendPrototype([SVGRendererBase, ICompElement, SVGBaseElement], SVGCompElement); - -SVGCompElement.prototype.createComp = function (data) { - return new SVGCompElement(data, this.globalData, this); -}; - -function SVGRenderer(animationItem, config) { - this.animationItem = animationItem; - this.layers = null; - this.renderedFrame = -1; - this.svgElement = createNS('svg'); - var ariaLabel = ''; - if (config && config.title) { - var titleElement = createNS('title'); - var titleId = createElementID(); - titleElement.setAttribute('id', titleId); - titleElement.textContent = config.title; - this.svgElement.appendChild(titleElement); - ariaLabel += titleId; - } - if (config && config.description) { - var descElement = createNS('desc'); - var descId = createElementID(); - descElement.setAttribute('id', descId); - descElement.textContent = config.description; - this.svgElement.appendChild(descElement); - ariaLabel += ' ' + descId; - } - if (ariaLabel) { - this.svgElement.setAttribute('aria-labelledby', ariaLabel); - } - var defs = createNS('defs'); - this.svgElement.appendChild(defs); - var maskElement = createNS('g'); - this.svgElement.appendChild(maskElement); - this.layerElement = maskElement; - this.renderConfig = { - preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', - imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', - contentVisibility: (config && config.contentVisibility) || 'visible', - progressiveLoad: (config && config.progressiveLoad) || false, - hideOnTransparent: !((config && config.hideOnTransparent === false)), - viewBoxOnly: (config && config.viewBoxOnly) || false, - viewBoxSize: (config && config.viewBoxSize) || false, - className: (config && config.className) || '', - id: (config && config.id) || '', - focusable: config && config.focusable, - filterSize: { - width: (config && config.filterSize && config.filterSize.width) || '100%', - height: (config && config.filterSize && config.filterSize.height) || '100%', - x: (config && config.filterSize && config.filterSize.x) || '0%', - y: (config && config.filterSize && config.filterSize.y) || '0%', - }, - width: (config && config.width), - height: (config && config.height), - }; - - this.globalData = { - _mdf: false, - frameNum: -1, - defs: defs, - renderConfig: this.renderConfig, - }; - this.elements = []; - this.pendingElements = []; - this.destroyed = false; - this.rendererType = 'svg'; -} - -extendPrototype([SVGRendererBase], SVGRenderer); - -SVGRenderer.prototype.createComp = function (data) { - return new SVGCompElement(data, this.globalData, this); -}; - -function CVContextData() { - this.saved = []; - this.cArrPos = 0; - this.cTr = new Matrix(); - this.cO = 1; - var i; - var len = 15; - this.savedOp = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - this.saved[i] = createTypedArray('float32', 16); - } - this._length = len; -} - -CVContextData.prototype.duplicate = function () { - var newLength = this._length * 2; - var currentSavedOp = this.savedOp; - this.savedOp = createTypedArray('float32', newLength); - this.savedOp.set(currentSavedOp); - var i = 0; - for (i = this._length; i < newLength; i += 1) { - this.saved[i] = createTypedArray('float32', 16); - } - this._length = newLength; -}; - -CVContextData.prototype.reset = function () { - this.cArrPos = 0; - this.cTr.reset(); - this.cO = 1; -}; - -function ShapeTransformManager() { - this.sequences = {}; - this.sequenceList = []; - this.transform_key_count = 0; -} - -ShapeTransformManager.prototype = { - addTransformSequence: function (transforms) { - var i; - var len = transforms.length; - var key = '_'; - for (i = 0; i < len; i += 1) { - key += transforms[i].transform.key + '_'; - } - var sequence = this.sequences[key]; - if (!sequence) { - sequence = { - transforms: [].concat(transforms), - finalTransform: new Matrix(), - _mdf: false, - }; - this.sequences[key] = sequence; - this.sequenceList.push(sequence); - } - return sequence; - }, - processSequence: function (sequence, isFirstFrame) { - var i = 0; - var len = sequence.transforms.length; - var _mdf = isFirstFrame; - while (i < len && !isFirstFrame) { - if (sequence.transforms[i].transform.mProps._mdf) { - _mdf = true; - break; - } - i += 1; + + extendPrototype([SVGRendererBase, ICompElement, SVGBaseElement], SVGCompElement); + + SVGCompElement.prototype.createComp = function (data) { + return new SVGCompElement(data, this.globalData, this); + }; + + function SVGRenderer(animationItem, config) { + this.animationItem = animationItem; + this.layers = null; + this.renderedFrame = -1; + this.svgElement = createNS('svg'); + var ariaLabel = ''; + if (config && config.title) { + var titleElement = createNS('title'); + var titleId = createElementID(); + titleElement.setAttribute('id', titleId); + titleElement.textContent = config.title; + this.svgElement.appendChild(titleElement); + ariaLabel += titleId; + } + if (config && config.description) { + var descElement = createNS('desc'); + var descId = createElementID(); + descElement.setAttribute('id', descId); + descElement.textContent = config.description; + this.svgElement.appendChild(descElement); + ariaLabel += ' ' + descId; + } + if (ariaLabel) { + this.svgElement.setAttribute('aria-labelledby', ariaLabel); + } + var defs = createNS('defs'); + this.svgElement.appendChild(defs); + var maskElement = createNS('g'); + this.svgElement.appendChild(maskElement); + this.layerElement = maskElement; + this.renderConfig = { + preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', + imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', + contentVisibility: (config && config.contentVisibility) || 'visible', + progressiveLoad: (config && config.progressiveLoad) || false, + hideOnTransparent: !((config && config.hideOnTransparent === false)), + viewBoxOnly: (config && config.viewBoxOnly) || false, + viewBoxSize: (config && config.viewBoxSize) || false, + className: (config && config.className) || '', + id: (config && config.id) || '', + focusable: config && config.focusable, + filterSize: { + width: (config && config.filterSize && config.filterSize.width) || '100%', + height: (config && config.filterSize && config.filterSize.height) || '100%', + x: (config && config.filterSize && config.filterSize.x) || '0%', + y: (config && config.filterSize && config.filterSize.y) || '0%', + }, + width: (config && config.width), + height: (config && config.height), + }; + + this.globalData = { + _mdf: false, + frameNum: -1, + defs: defs, + renderConfig: this.renderConfig, + }; + this.elements = []; + this.pendingElements = []; + this.destroyed = false; + this.rendererType = 'svg'; } - if (_mdf) { - var props; - sequence.finalTransform.reset(); - for (i = len - 1; i >= 0; i -= 1) { - props = sequence.transforms[i].transform.mProps.v.props; - sequence.finalTransform.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); + + extendPrototype([SVGRendererBase], SVGRenderer); + + SVGRenderer.prototype.createComp = function (data) { + return new SVGCompElement(data, this.globalData, this); + }; + + function CVContextData() { + this.saved = []; + this.cArrPos = 0; + this.cTr = new Matrix(); + this.cO = 1; + var i; + var len = 15; + this.savedOp = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + this.saved[i] = createTypedArray('float32', 16); + } + this._length = len; + } + + CVContextData.prototype.duplicate = function () { + var newLength = this._length * 2; + var currentSavedOp = this.savedOp; + this.savedOp = createTypedArray('float32', newLength); + this.savedOp.set(currentSavedOp); + var i = 0; + for (i = this._length; i < newLength; i += 1) { + this.saved[i] = createTypedArray('float32', 16); + } + this._length = newLength; + }; + + CVContextData.prototype.reset = function () { + this.cArrPos = 0; + this.cTr.reset(); + this.cO = 1; + }; + + function ShapeTransformManager() { + this.sequences = {}; + this.sequenceList = []; + this.transform_key_count = 0; + } + + ShapeTransformManager.prototype = { + addTransformSequence: function (transforms) { + var i; + var len = transforms.length; + var key = '_'; + for (i = 0; i < len; i += 1) { + key += transforms[i].transform.key + '_'; + } + var sequence = this.sequences[key]; + if (!sequence) { + sequence = { + transforms: [].concat(transforms), + finalTransform: new Matrix(), + _mdf: false, + }; + this.sequences[key] = sequence; + this.sequenceList.push(sequence); + } + return sequence; + }, + processSequence: function (sequence, isFirstFrame) { + var i = 0; + var len = sequence.transforms.length; + var _mdf = isFirstFrame; + while (i < len && !isFirstFrame) { + if (sequence.transforms[i].transform.mProps._mdf) { + _mdf = true; + break; + } + i += 1; + } + if (_mdf) { + var props; + sequence.finalTransform.reset(); + for (i = len - 1; i >= 0; i -= 1) { + props = sequence.transforms[i].transform.mProps.v.props; + sequence.finalTransform.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); + } + } + sequence._mdf = _mdf; + }, + processSequences: function (isFirstFrame) { + var i; + var len = this.sequenceList.length; + for (i = 0; i < len; i += 1) { + this.processSequence(this.sequenceList[i], isFirstFrame); + } + }, + getNewKey: function () { + this.transform_key_count += 1; + return '_' + this.transform_key_count; + }, + }; + + function CVEffects() { + + } + CVEffects.prototype.renderFrame = function () {}; + + function CVMaskElement(data, element) { + this.data = data; + this.element = element; + this.masksProperties = this.data.masksProperties || []; + this.viewData = createSizedArray(this.masksProperties.length); + var i; + var len = this.masksProperties.length; + var hasMasks = false; + for (i = 0; i < len; i += 1) { + if (this.masksProperties[i].mode !== 'n') { + hasMasks = true; + } + this.viewData[i] = ShapePropertyFactory.getShapeProp(this.element, this.masksProperties[i], 3); + } + this.hasMasks = hasMasks; + if (hasMasks) { + this.element.addRenderableComponent(this); + } + } + + CVMaskElement.prototype.renderFrame = function () { + if (!this.hasMasks) { + return; + } + var transform = this.element.finalTransform.mat; + var ctx = this.element.canvasContext; + var i; + var len = this.masksProperties.length; + var pt; + var pts; + var data; + ctx.beginPath(); + for (i = 0; i < len; i += 1) { + if (this.masksProperties[i].mode !== 'n') { + if (this.masksProperties[i].inv) { + ctx.moveTo(0, 0); + ctx.lineTo(this.element.globalData.compSize.w, 0); + ctx.lineTo(this.element.globalData.compSize.w, this.element.globalData.compSize.h); + ctx.lineTo(0, this.element.globalData.compSize.h); + ctx.lineTo(0, 0); + } + data = this.viewData[i].v; + pt = transform.applyToPointArray(data.v[0][0], data.v[0][1], 0); + ctx.moveTo(pt[0], pt[1]); + var j; + var jLen = data._length; + for (j = 1; j < jLen; j += 1) { + pts = transform.applyToTriplePoints(data.o[j - 1], data.i[j], data.v[j]); + ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); + } + pts = transform.applyToTriplePoints(data.o[j - 1], data.i[0], data.v[0]); + ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); + } + } + this.element.globalData.renderer.save(true); + ctx.clip(); + }; + + CVMaskElement.prototype.getMaskProperty = MaskElement.prototype.getMaskProperty; + + CVMaskElement.prototype.destroy = function () { + this.element = null; + }; + + function CVBaseElement() { + } + + CVBaseElement.prototype = { + createElements: function () {}, + initRendererElement: function () {}, + createContainerElements: function () { + this.canvasContext = this.globalData.canvasContext; + this.renderableEffectsManager = new CVEffects(this); + }, + createContent: function () {}, + setBlendMode: function () { + var globalData = this.globalData; + if (globalData.blendMode !== this.data.bm) { + globalData.blendMode = this.data.bm; + var blendModeValue = getBlendMode(this.data.bm); + globalData.canvasContext.globalCompositeOperation = blendModeValue; + } + }, + createRenderableComponents: function () { + this.maskManager = new CVMaskElement(this.data, this); + }, + hideElement: function () { + if (!this.hidden && (!this.isInRange || this.isTransparent)) { + this.hidden = true; + } + }, + showElement: function () { + if (this.isInRange && !this.isTransparent) { + this.hidden = false; + this._isFirstFrame = true; + this.maskManager._isFirstFrame = true; + } + }, + renderFrame: function () { + if (this.hidden || this.data.hd) { + return; + } + this.renderTransform(); + this.renderRenderable(); + this.setBlendMode(); + var forceRealStack = this.data.ty === 0; + this.globalData.renderer.save(forceRealStack); + this.globalData.renderer.ctxTransform(this.finalTransform.mat.props); + this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v); + this.renderInnerContent(); + this.globalData.renderer.restore(forceRealStack); + if (this.maskManager.hasMasks) { + this.globalData.renderer.restore(true); + } + if (this._isFirstFrame) { + this._isFirstFrame = false; + } + }, + destroy: function () { + this.canvasContext = null; + this.data = null; + this.globalData = null; + this.maskManager.destroy(); + }, + mHelper: new Matrix(), + }; + CVBaseElement.prototype.hide = CVBaseElement.prototype.hideElement; + CVBaseElement.prototype.show = CVBaseElement.prototype.showElement; + + function CVShapeData(element, data, styles, transformsManager) { + this.styledShapes = []; + this.tr = [0, 0, 0, 0, 0, 0]; + var ty = 4; + if (data.ty === 'rc') { + ty = 5; + } else if (data.ty === 'el') { + ty = 6; + } else if (data.ty === 'sr') { + ty = 7; + } + this.sh = ShapePropertyFactory.getShapeProp(element, data, ty, element); + var i; + var len = styles.length; + var styledShape; + for (i = 0; i < len; i += 1) { + if (!styles[i].closed) { + styledShape = { + transforms: transformsManager.addTransformSequence(styles[i].transforms), + trNodes: [], + }; + this.styledShapes.push(styledShape); + styles[i].elements.push(styledShape); + } + } + } + + CVShapeData.prototype.setAsAnimated = SVGShapeData.prototype.setAsAnimated; + + function CVShapeElement(data, globalData, comp) { + this.shapes = []; + this.shapesData = data.shapes; + this.stylesList = []; + this.itemsData = []; + this.prevViewData = []; + this.shapeModifiers = []; + this.processedElements = []; + this.transformsManager = new ShapeTransformManager(); + this.initElement(data, globalData, comp); + } + + extendPrototype([BaseElement, TransformElement, CVBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableElement], CVShapeElement); + + CVShapeElement.prototype.initElement = RenderableDOMElement.prototype.initElement; + + CVShapeElement.prototype.transformHelper = { opacity: 1, _opMdf: false }; + + CVShapeElement.prototype.dashResetter = []; + + CVShapeElement.prototype.createContent = function () { + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); + }; + + CVShapeElement.prototype.createStyleElement = function (data, transforms) { + var styleElem = { + data: data, + type: data.ty, + preTransforms: this.transformsManager.addTransformSequence(transforms), + transforms: [], + elements: [], + closed: data.hd === true, + }; + var elementData = {}; + if (data.ty === 'fl' || data.ty === 'st') { + elementData.c = PropertyFactory.getProp(this, data.c, 1, 255, this); + if (!elementData.c.k) { + styleElem.co = 'rgb(' + bmFloor(elementData.c.v[0]) + ',' + bmFloor(elementData.c.v[1]) + ',' + bmFloor(elementData.c.v[2]) + ')'; + } + } else if (data.ty === 'gf' || data.ty === 'gs') { + elementData.s = PropertyFactory.getProp(this, data.s, 1, null, this); + elementData.e = PropertyFactory.getProp(this, data.e, 1, null, this); + elementData.h = PropertyFactory.getProp(this, data.h || { k: 0 }, 0, 0.01, this); + elementData.a = PropertyFactory.getProp(this, data.a || { k: 0 }, 0, degToRads, this); + elementData.g = new GradientProperty(this, data.g, this); + } + elementData.o = PropertyFactory.getProp(this, data.o, 0, 0.01, this); + if (data.ty === 'st' || data.ty === 'gs') { + styleElem.lc = lineCapEnum[data.lc || 2]; + styleElem.lj = lineJoinEnum[data.lj || 2]; + if (data.lj == 1) { // eslint-disable-line eqeqeq + styleElem.ml = data.ml; + } + elementData.w = PropertyFactory.getProp(this, data.w, 0, null, this); + if (!elementData.w.k) { + styleElem.wi = elementData.w.v; + } + if (data.d) { + var d = new DashProperty(this, data.d, 'canvas', this); + elementData.d = d; + if (!elementData.d.k) { + styleElem.da = elementData.d.dashArray; + styleElem.do = elementData.d.dashoffset[0]; + } + } + } else { + styleElem.r = data.r === 2 ? 'evenodd' : 'nonzero'; + } + this.stylesList.push(styleElem); + elementData.style = styleElem; + return elementData; + }; + + CVShapeElement.prototype.createGroupElement = function () { + var elementData = { + it: [], + prevViewData: [], + }; + return elementData; + }; + + CVShapeElement.prototype.createTransformElement = function (data) { + var elementData = { + transform: { + opacity: 1, + _opMdf: false, + key: this.transformsManager.getNewKey(), + op: PropertyFactory.getProp(this, data.o, 0, 0.01, this), + mProps: TransformPropertyFactory.getTransformProperty(this, data, this), + }, + }; + return elementData; + }; + + CVShapeElement.prototype.createShapeElement = function (data) { + var elementData = new CVShapeData(this, data, this.stylesList, this.transformsManager); + + this.shapes.push(elementData); + this.addShapeToModifiers(elementData); + return elementData; + }; + + CVShapeElement.prototype.reloadShapes = function () { + this._isFirstFrame = true; + var i; + var len = this.itemsData.length; + for (i = 0; i < len; i += 1) { + this.prevViewData[i] = this.itemsData[i]; + } + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); + len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + this.dynamicProperties[i].getValue(); + } + this.renderModifiers(); + this.transformsManager.processSequences(this._isFirstFrame); + }; + + CVShapeElement.prototype.addTransformToStyleList = function (transform) { + var i; + var len = this.stylesList.length; + for (i = 0; i < len; i += 1) { + if (!this.stylesList[i].closed) { + this.stylesList[i].transforms.push(transform); + } + } + }; + + CVShapeElement.prototype.removeTransformFromStyleList = function () { + var i; + var len = this.stylesList.length; + for (i = 0; i < len; i += 1) { + if (!this.stylesList[i].closed) { + this.stylesList[i].transforms.pop(); + } + } + }; + + CVShapeElement.prototype.closeStyles = function (styles) { + var i; + var len = styles.length; + for (i = 0; i < len; i += 1) { + styles[i].closed = true; + } + }; + + CVShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, shouldRender, transforms) { + var i; + var len = arr.length - 1; + var j; + var jLen; + var ownStyles = []; + var ownModifiers = []; + var processedPos; + var modifier; + var currentTransform; + var ownTransforms = [].concat(transforms); + for (i = len; i >= 0; i -= 1) { + processedPos = this.searchProcessedElement(arr[i]); + if (!processedPos) { + arr[i]._shouldRender = shouldRender; + } else { + itemsData[i] = prevViewData[processedPos - 1]; + } + if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs') { + if (!processedPos) { + itemsData[i] = this.createStyleElement(arr[i], ownTransforms); + } else { + itemsData[i].style.closed = false; + } + + ownStyles.push(itemsData[i].style); + } else if (arr[i].ty === 'gr') { + if (!processedPos) { + itemsData[i] = this.createGroupElement(arr[i]); + } else { + jLen = itemsData[i].it.length; + for (j = 0; j < jLen; j += 1) { + itemsData[i].prevViewData[j] = itemsData[i].it[j]; + } + } + this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, shouldRender, ownTransforms); + } else if (arr[i].ty === 'tr') { + if (!processedPos) { + currentTransform = this.createTransformElement(arr[i]); + itemsData[i] = currentTransform; + } + ownTransforms.push(itemsData[i]); + this.addTransformToStyleList(itemsData[i]); + } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { + if (!processedPos) { + itemsData[i] = this.createShapeElement(arr[i]); + } + } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'pb') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + modifier.init(this, arr[i]); + itemsData[i] = modifier; + this.shapeModifiers.push(modifier); + } else { + modifier = itemsData[i]; + modifier.closed = false; + } + ownModifiers.push(modifier); + } else if (arr[i].ty === 'rp') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + itemsData[i] = modifier; + modifier.init(this, arr, i, itemsData); + this.shapeModifiers.push(modifier); + shouldRender = false; + } else { + modifier = itemsData[i]; + modifier.closed = true; + } + ownModifiers.push(modifier); + } + this.addProcessedElement(arr[i], i + 1); + } + this.removeTransformFromStyleList(); + this.closeStyles(ownStyles); + len = ownModifiers.length; + for (i = 0; i < len; i += 1) { + ownModifiers[i].closed = true; + } + }; + + CVShapeElement.prototype.renderInnerContent = function () { + this.transformHelper.opacity = 1; + this.transformHelper._opMdf = false; + this.renderModifiers(); + this.transformsManager.processSequences(this._isFirstFrame); + this.renderShape(this.transformHelper, this.shapesData, this.itemsData, true); + }; + + CVShapeElement.prototype.renderShapeTransform = function (parentTransform, groupTransform) { + if (parentTransform._opMdf || groupTransform.op._mdf || this._isFirstFrame) { + groupTransform.opacity = parentTransform.opacity; + groupTransform.opacity *= groupTransform.op.v; + groupTransform._opMdf = true; + } + }; + + CVShapeElement.prototype.drawLayer = function () { + var i; + var len = this.stylesList.length; + var j; + var jLen; + var k; + var kLen; + var elems; + var nodes; + var renderer = this.globalData.renderer; + var ctx = this.globalData.canvasContext; + var type; + var currentStyle; + for (i = 0; i < len; i += 1) { + currentStyle = this.stylesList[i]; + type = currentStyle.type; + + // Skipping style when + // Stroke width equals 0 + // style should not be rendered (extra unused repeaters) + // current opacity equals 0 + // global opacity equals 0 + if (!(((type === 'st' || type === 'gs') && currentStyle.wi === 0) || !currentStyle.data._shouldRender || currentStyle.coOp === 0 || this.globalData.currentGlobalAlpha === 0)) { + renderer.save(); + elems = currentStyle.elements; + if (type === 'st' || type === 'gs') { + ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd; + ctx.lineWidth = currentStyle.wi; + ctx.lineCap = currentStyle.lc; + ctx.lineJoin = currentStyle.lj; + ctx.miterLimit = currentStyle.ml || 0; + } else { + ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd; + } + renderer.ctxOpacity(currentStyle.coOp); + if (type !== 'st' && type !== 'gs') { + ctx.beginPath(); + } + renderer.ctxTransform(currentStyle.preTransforms.finalTransform.props); + jLen = elems.length; + for (j = 0; j < jLen; j += 1) { + if (type === 'st' || type === 'gs') { + ctx.beginPath(); + if (currentStyle.da) { + ctx.setLineDash(currentStyle.da); + ctx.lineDashOffset = currentStyle.do; + } + } + nodes = elems[j].trNodes; + kLen = nodes.length; + + for (k = 0; k < kLen; k += 1) { + if (nodes[k].t === 'm') { + ctx.moveTo(nodes[k].p[0], nodes[k].p[1]); + } else if (nodes[k].t === 'c') { + ctx.bezierCurveTo(nodes[k].pts[0], nodes[k].pts[1], nodes[k].pts[2], nodes[k].pts[3], nodes[k].pts[4], nodes[k].pts[5]); + } else { + ctx.closePath(); + } + } + if (type === 'st' || type === 'gs') { + ctx.stroke(); + if (currentStyle.da) { + ctx.setLineDash(this.dashResetter); + } + } + } + if (type !== 'st' && type !== 'gs') { + ctx.fill(currentStyle.r); + } + renderer.restore(); + } + } + }; + + CVShapeElement.prototype.renderShape = function (parentTransform, items, data, isMain) { + var i; + var len = items.length - 1; + var groupTransform; + groupTransform = parentTransform; + for (i = len; i >= 0; i -= 1) { + if (items[i].ty === 'tr') { + groupTransform = data[i].transform; + this.renderShapeTransform(parentTransform, groupTransform); + } else if (items[i].ty === 'sh' || items[i].ty === 'el' || items[i].ty === 'rc' || items[i].ty === 'sr') { + this.renderPath(items[i], data[i]); + } else if (items[i].ty === 'fl') { + this.renderFill(items[i], data[i], groupTransform); + } else if (items[i].ty === 'st') { + this.renderStroke(items[i], data[i], groupTransform); + } else if (items[i].ty === 'gf' || items[i].ty === 'gs') { + this.renderGradientFill(items[i], data[i], groupTransform); + } else if (items[i].ty === 'gr') { + this.renderShape(groupTransform, items[i].it, data[i].it); + } else if (items[i].ty === 'tm') { + // + } + } + if (isMain) { + this.drawLayer(); + } + }; + + CVShapeElement.prototype.renderStyledShape = function (styledShape, shape) { + if (this._isFirstFrame || shape._mdf || styledShape.transforms._mdf) { + var shapeNodes = styledShape.trNodes; + var paths = shape.paths; + var i; + var len; + var j; + var jLen = paths._length; + shapeNodes.length = 0; + var groupTransformMat = styledShape.transforms.finalTransform; + for (j = 0; j < jLen; j += 1) { + var pathNodes = paths.shapes[j]; + if (pathNodes && pathNodes.v) { + len = pathNodes._length; + for (i = 1; i < len; i += 1) { + if (i === 1) { + shapeNodes.push({ + t: 'm', + p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), + }); + } + shapeNodes.push({ + t: 'c', + pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[i], pathNodes.v[i]), + }); + } + if (len === 1) { + shapeNodes.push({ + t: 'm', + p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), + }); + } + if (pathNodes.c && len) { + shapeNodes.push({ + t: 'c', + pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[0], pathNodes.v[0]), + }); + shapeNodes.push({ + t: 'z', + }); + } + } + } + styledShape.trNodes = shapeNodes; + } + }; + + CVShapeElement.prototype.renderPath = function (pathData, itemData) { + if (pathData.hd !== true && pathData._shouldRender) { + var i; + var len = itemData.styledShapes.length; + for (i = 0; i < len; i += 1) { + this.renderStyledShape(itemData.styledShapes[i], itemData.sh); + } + } + }; + + CVShapeElement.prototype.renderFill = function (styleData, itemData, groupTransform) { + var styleElem = itemData.style; + + if (itemData.c._mdf || this._isFirstFrame) { + styleElem.co = 'rgb(' + + bmFloor(itemData.c.v[0]) + ',' + + bmFloor(itemData.c.v[1]) + ',' + + bmFloor(itemData.c.v[2]) + ')'; + } + if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { + styleElem.coOp = itemData.o.v * groupTransform.opacity; + } + }; + + CVShapeElement.prototype.renderGradientFill = function (styleData, itemData, groupTransform) { + var styleElem = itemData.style; + var grd; + if (!styleElem.grd || itemData.g._mdf || itemData.s._mdf || itemData.e._mdf || (styleData.t !== 1 && (itemData.h._mdf || itemData.a._mdf))) { + var ctx = this.globalData.canvasContext; + var pt1 = itemData.s.v; + var pt2 = itemData.e.v; + if (styleData.t === 1) { + grd = ctx.createLinearGradient(pt1[0], pt1[1], pt2[0], pt2[1]); + } else { + var rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); + var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); + + var percent = itemData.h.v; + if (percent >= 1) { + percent = 0.99; + } else if (percent <= -1) { + percent = -0.99; + } + var dist = rad * percent; + var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; + var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; + grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad); + } + + var i; + var len = styleData.g.p; + var cValues = itemData.g.c; + var opacity = 1; + + for (i = 0; i < len; i += 1) { + if (itemData.g._hasOpacity && itemData.g._collapsable) { + opacity = itemData.g.o[i * 2 + 1]; + } + grd.addColorStop(cValues[i * 4] / 100, 'rgba(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ',' + opacity + ')'); + } + styleElem.grd = grd; + } + styleElem.coOp = itemData.o.v * groupTransform.opacity; + }; + + CVShapeElement.prototype.renderStroke = function (styleData, itemData, groupTransform) { + var styleElem = itemData.style; + var d = itemData.d; + if (d && (d._mdf || this._isFirstFrame)) { + styleElem.da = d.dashArray; + styleElem.do = d.dashoffset[0]; + } + if (itemData.c._mdf || this._isFirstFrame) { + styleElem.co = 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'; + } + if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { + styleElem.coOp = itemData.o.v * groupTransform.opacity; + } + if (itemData.w._mdf || this._isFirstFrame) { + styleElem.wi = itemData.w.v; } + }; + + CVShapeElement.prototype.destroy = function () { + this.shapesData = null; + this.globalData = null; + this.canvasContext = null; + this.stylesList.length = 0; + this.itemsData.length = 0; + }; + + function CVTextElement(data, globalData, comp) { + this.textSpans = []; + this.yOffset = 0; + this.fillColorAnim = false; + this.strokeColorAnim = false; + this.strokeWidthAnim = false; + this.stroke = false; + this.fill = false; + this.justifyOffset = 0; + this.currentRender = null; + this.renderType = 'canvas'; + this.values = { + fill: 'rgba(0,0,0,0)', + stroke: 'rgba(0,0,0,0)', + sWidth: 0, + fValue: '', + }; + this.initElement(data, globalData, comp); } - sequence._mdf = _mdf; - }, - processSequences: function (isFirstFrame) { - var i; - var len = this.sequenceList.length; - for (i = 0; i < len; i += 1) { - this.processSequence(this.sequenceList[i], isFirstFrame); - } - }, - getNewKey: function () { - this.transform_key_count += 1; - return '_' + this.transform_key_count; - }, -}; - -function CVEffects() { - -} -CVEffects.prototype.renderFrame = function () {}; - -function CVMaskElement(data, element) { - this.data = data; - this.element = element; - this.masksProperties = this.data.masksProperties || []; - this.viewData = createSizedArray(this.masksProperties.length); - var i; - var len = this.masksProperties.length; - var hasMasks = false; - for (i = 0; i < len; i += 1) { - if (this.masksProperties[i].mode !== 'n') { - hasMasks = true; - } - this.viewData[i] = ShapePropertyFactory.getShapeProp(this.element, this.masksProperties[i], 3); - } - this.hasMasks = hasMasks; - if (hasMasks) { - this.element.addRenderableComponent(this); - } -} - -CVMaskElement.prototype.renderFrame = function () { - if (!this.hasMasks) { - return; - } - var transform = this.element.finalTransform.mat; - var ctx = this.element.canvasContext; - var i; - var len = this.masksProperties.length; - var pt; - var pts; - var data; - ctx.beginPath(); - for (i = 0; i < len; i += 1) { - if (this.masksProperties[i].mode !== 'n') { - if (this.masksProperties[i].inv) { - ctx.moveTo(0, 0); - ctx.lineTo(this.element.globalData.compSize.w, 0); - ctx.lineTo(this.element.globalData.compSize.w, this.element.globalData.compSize.h); - ctx.lineTo(0, this.element.globalData.compSize.h); - ctx.lineTo(0, 0); - } - data = this.viewData[i].v; - pt = transform.applyToPointArray(data.v[0][0], data.v[0][1], 0); - ctx.moveTo(pt[0], pt[1]); + extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement, ITextElement], CVTextElement); + + CVTextElement.prototype.tHelper = createTag('canvas').getContext('2d'); + + CVTextElement.prototype.buildNewText = function () { + var documentData = this.textProperty.currentData; + this.renderedLetters = createSizedArray(documentData.l ? documentData.l.length : 0); + + var hasFill = false; + if (documentData.fc) { + hasFill = true; + this.values.fill = this.buildColor(documentData.fc); + } else { + this.values.fill = 'rgba(0,0,0,0)'; + } + this.fill = hasFill; + var hasStroke = false; + if (documentData.sc) { + hasStroke = true; + this.values.stroke = this.buildColor(documentData.sc); + this.values.sWidth = documentData.sw; + } + var fontData = this.globalData.fontManager.getFontByName(documentData.f); + var i; + var len; + var letters = documentData.l; + var matrixHelper = this.mHelper; + this.stroke = hasStroke; + this.values.fValue = documentData.finalSize + 'px ' + this.globalData.fontManager.getFontByName(documentData.f).fFamily; + len = documentData.finalText.length; + // this.tHelper.font = this.values.fValue; + var charData; + var shapeData; + var k; + var kLen; + var shapes; var j; - var jLen = data._length; - for (j = 1; j < jLen; j += 1) { - pts = transform.applyToTriplePoints(data.o[j - 1], data.i[j], data.v[j]); - ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); + var jLen; + var pathNodes; + var commands; + var pathArr; + var singleShape = this.data.singleShape; + var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; + var xPos = 0; + var yPos = 0; + var firstLine = true; + var cnt = 0; + for (i = 0; i < len; i += 1) { + charData = this.globalData.fontManager.getCharData(documentData.finalText[i], fontData.fStyle, this.globalData.fontManager.getFontByName(documentData.f).fFamily); + shapeData = (charData && charData.data) || {}; + matrixHelper.reset(); + if (singleShape && letters[i].n) { + xPos = -trackingOffset; + yPos += documentData.yOffset; + yPos += firstLine ? 1 : 0; + firstLine = false; + } + shapes = shapeData.shapes ? shapeData.shapes[0].it : []; + jLen = shapes.length; + matrixHelper.scale(documentData.finalSize / 100, documentData.finalSize / 100); + if (singleShape) { + this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); + } + commands = createSizedArray(jLen - 1); + var commandsCounter = 0; + for (j = 0; j < jLen; j += 1) { + if (shapes[j].ty === 'sh') { + kLen = shapes[j].ks.k.i.length; + pathNodes = shapes[j].ks.k; + pathArr = []; + for (k = 1; k < kLen; k += 1) { + if (k === 1) { + pathArr.push(matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); + } + pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToY(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToX(pathNodes.v[k][0], pathNodes.v[k][1], 0), matrixHelper.applyToY(pathNodes.v[k][0], pathNodes.v[k][1], 0)); + } + pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToY(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); + commands[commandsCounter] = pathArr; + commandsCounter += 1; + } + } + if (singleShape) { + xPos += letters[i].l; + xPos += trackingOffset; + } + if (this.textSpans[cnt]) { + this.textSpans[cnt].elem = commands; + } else { + this.textSpans[cnt] = { elem: commands }; + } + cnt += 1; } - pts = transform.applyToTriplePoints(data.o[j - 1], data.i[0], data.v[0]); - ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); - } - } - this.element.globalData.renderer.save(true); - ctx.clip(); -}; - -CVMaskElement.prototype.getMaskProperty = MaskElement.prototype.getMaskProperty; - -CVMaskElement.prototype.destroy = function () { - this.element = null; -}; - -function CVBaseElement() { -} - -CVBaseElement.prototype = { - createElements: function () {}, - initRendererElement: function () {}, - createContainerElements: function () { - this.canvasContext = this.globalData.canvasContext; - this.renderableEffectsManager = new CVEffects(this); - }, - createContent: function () {}, - setBlendMode: function () { - var globalData = this.globalData; - if (globalData.blendMode !== this.data.bm) { - globalData.blendMode = this.data.bm; - var blendModeValue = getBlendMode(this.data.bm); - globalData.canvasContext.globalCompositeOperation = blendModeValue; - } - }, - createRenderableComponents: function () { - this.maskManager = new CVMaskElement(this.data, this); - }, - hideElement: function () { - if (!this.hidden && (!this.isInRange || this.isTransparent)) { - this.hidden = true; - } - }, - showElement: function () { - if (this.isInRange && !this.isTransparent) { - this.hidden = false; - this._isFirstFrame = true; - this.maskManager._isFirstFrame = true; - } - }, - renderFrame: function () { - if (this.hidden || this.data.hd) { - return; - } - this.renderTransform(); - this.renderRenderable(); - this.setBlendMode(); - var forceRealStack = this.data.ty === 0; - this.globalData.renderer.save(forceRealStack); - this.globalData.renderer.ctxTransform(this.finalTransform.mat.props); - this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v); - this.renderInnerContent(); - this.globalData.renderer.restore(forceRealStack); - if (this.maskManager.hasMasks) { - this.globalData.renderer.restore(true); - } - if (this._isFirstFrame) { - this._isFirstFrame = false; + }; + + CVTextElement.prototype.renderInnerContent = function () { + var ctx = this.canvasContext; + ctx.font = this.values.fValue; + ctx.lineCap = 'butt'; + ctx.lineJoin = 'miter'; + ctx.miterLimit = 4; + + if (!this.data.singleShape) { + this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); + } + + var i; + var len; + var j; + var jLen; + var k; + var kLen; + var renderedLetters = this.textAnimator.renderedLetters; + + var letters = this.textProperty.currentData.l; + + len = letters.length; + var renderedLetter; + var lastFill = null; + var lastStroke = null; + var lastStrokeW = null; + var commands; + var pathArr; + for (i = 0; i < len; i += 1) { + if (!letters[i].n) { + renderedLetter = renderedLetters[i]; + if (renderedLetter) { + this.globalData.renderer.save(); + this.globalData.renderer.ctxTransform(renderedLetter.p); + this.globalData.renderer.ctxOpacity(renderedLetter.o); + } + if (this.fill) { + if (renderedLetter && renderedLetter.fc) { + if (lastFill !== renderedLetter.fc) { + lastFill = renderedLetter.fc; + ctx.fillStyle = renderedLetter.fc; + } + } else if (lastFill !== this.values.fill) { + lastFill = this.values.fill; + ctx.fillStyle = this.values.fill; + } + commands = this.textSpans[i].elem; + jLen = commands.length; + this.globalData.canvasContext.beginPath(); + for (j = 0; j < jLen; j += 1) { + pathArr = commands[j]; + kLen = pathArr.length; + this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); + for (k = 2; k < kLen; k += 6) { + this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); + } + } + this.globalData.canvasContext.closePath(); + this.globalData.canvasContext.fill(); + /// ctx.fillText(this.textSpans[i].val,0,0); + } + if (this.stroke) { + if (renderedLetter && renderedLetter.sw) { + if (lastStrokeW !== renderedLetter.sw) { + lastStrokeW = renderedLetter.sw; + ctx.lineWidth = renderedLetter.sw; + } + } else if (lastStrokeW !== this.values.sWidth) { + lastStrokeW = this.values.sWidth; + ctx.lineWidth = this.values.sWidth; + } + if (renderedLetter && renderedLetter.sc) { + if (lastStroke !== renderedLetter.sc) { + lastStroke = renderedLetter.sc; + ctx.strokeStyle = renderedLetter.sc; + } + } else if (lastStroke !== this.values.stroke) { + lastStroke = this.values.stroke; + ctx.strokeStyle = this.values.stroke; + } + commands = this.textSpans[i].elem; + jLen = commands.length; + this.globalData.canvasContext.beginPath(); + for (j = 0; j < jLen; j += 1) { + pathArr = commands[j]; + kLen = pathArr.length; + this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); + for (k = 2; k < kLen; k += 6) { + this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); + } + } + this.globalData.canvasContext.closePath(); + this.globalData.canvasContext.stroke(); + /// ctx.strokeText(letters[i].val,0,0); + } + if (renderedLetter) { + this.globalData.renderer.restore(); + } + } + } + }; + + function CVImageElement(data, globalData, comp) { + this.assetData = globalData.getAssetData(data.refId); + this.img = globalData.imageLoader.getAsset(this.assetData); + this.initElement(data, globalData, comp); + } + extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVImageElement); + + CVImageElement.prototype.initElement = SVGShapeElement.prototype.initElement; + CVImageElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; + + CVImageElement.prototype.createContent = function () { + if (this.img.width && (this.assetData.w !== this.img.width || this.assetData.h !== this.img.height)) { + var canvas = createTag('canvas'); + canvas.width = this.assetData.w; + canvas.height = this.assetData.h; + var ctx = canvas.getContext('2d'); + + var imgW = this.img.width; + var imgH = this.img.height; + var imgRel = imgW / imgH; + var canvasRel = this.assetData.w / this.assetData.h; + var widthCrop; + var heightCrop; + var par = this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio; + if ((imgRel > canvasRel && par === 'xMidYMid slice') || (imgRel < canvasRel && par !== 'xMidYMid slice')) { + heightCrop = imgH; + widthCrop = heightCrop * canvasRel; + } else { + widthCrop = imgW; + heightCrop = widthCrop / canvasRel; + } + ctx.drawImage(this.img, (imgW - widthCrop) / 2, (imgH - heightCrop) / 2, widthCrop, heightCrop, 0, 0, this.assetData.w, this.assetData.h); + this.img = canvas; + } + }; + + CVImageElement.prototype.renderInnerContent = function () { + this.canvasContext.drawImage(this.img, 0, 0); + }; + + CVImageElement.prototype.destroy = function () { + this.img = null; + }; + + function CVSolidElement(data, globalData, comp) { + this.initElement(data, globalData, comp); } - }, - destroy: function () { - this.canvasContext = null; - this.data = null; - this.globalData = null; - this.maskManager.destroy(); - }, - mHelper: new Matrix(), -}; -CVBaseElement.prototype.hide = CVBaseElement.prototype.hideElement; -CVBaseElement.prototype.show = CVBaseElement.prototype.showElement; - -function CVShapeData(element, data, styles, transformsManager) { - this.styledShapes = []; - this.tr = [0, 0, 0, 0, 0, 0]; - var ty = 4; - if (data.ty === 'rc') { - ty = 5; - } else if (data.ty === 'el') { - ty = 6; - } else if (data.ty === 'sr') { - ty = 7; - } - this.sh = ShapePropertyFactory.getShapeProp(element, data, ty, element); - var i; - var len = styles.length; - var styledShape; - for (i = 0; i < len; i += 1) { - if (!styles[i].closed) { - styledShape = { - transforms: transformsManager.addTransformSequence(styles[i].transforms), - trNodes: [], + extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVSolidElement); + + CVSolidElement.prototype.initElement = SVGShapeElement.prototype.initElement; + CVSolidElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; + + CVSolidElement.prototype.renderInnerContent = function () { + var ctx = this.canvasContext; + ctx.fillStyle = this.data.sc; + ctx.fillRect(0, 0, this.data.sw, this.data.sh); + // + }; + + function CanvasRendererBase(animationItem, config) { + this.animationItem = animationItem; + this.renderConfig = { + clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, + context: (config && config.context) || null, + progressiveLoad: (config && config.progressiveLoad) || false, + preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', + imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', + contentVisibility: (config && config.contentVisibility) || 'visible', + className: (config && config.className) || '', + id: (config && config.id) || '', }; - this.styledShapes.push(styledShape); - styles[i].elements.push(styledShape); - } - } -} - -CVShapeData.prototype.setAsAnimated = SVGShapeData.prototype.setAsAnimated; - -function CVShapeElement(data, globalData, comp) { - this.shapes = []; - this.shapesData = data.shapes; - this.stylesList = []; - this.itemsData = []; - this.prevViewData = []; - this.shapeModifiers = []; - this.processedElements = []; - this.transformsManager = new ShapeTransformManager(); - this.initElement(data, globalData, comp); -} - -extendPrototype([BaseElement, TransformElement, CVBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableElement], CVShapeElement); - -CVShapeElement.prototype.initElement = RenderableDOMElement.prototype.initElement; - -CVShapeElement.prototype.transformHelper = { opacity: 1, _opMdf: false }; - -CVShapeElement.prototype.dashResetter = []; - -CVShapeElement.prototype.createContent = function () { - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); -}; - -CVShapeElement.prototype.createStyleElement = function (data, transforms) { - var styleElem = { - data: data, - type: data.ty, - preTransforms: this.transformsManager.addTransformSequence(transforms), - transforms: [], - elements: [], - closed: data.hd === true, - }; - var elementData = {}; - if (data.ty === 'fl' || data.ty === 'st') { - elementData.c = PropertyFactory.getProp(this, data.c, 1, 255, this); - if (!elementData.c.k) { - styleElem.co = 'rgb(' + bmFloor(elementData.c.v[0]) + ',' + bmFloor(elementData.c.v[1]) + ',' + bmFloor(elementData.c.v[2]) + ')'; - } - } else if (data.ty === 'gf' || data.ty === 'gs') { - elementData.s = PropertyFactory.getProp(this, data.s, 1, null, this); - elementData.e = PropertyFactory.getProp(this, data.e, 1, null, this); - elementData.h = PropertyFactory.getProp(this, data.h || { k: 0 }, 0, 0.01, this); - elementData.a = PropertyFactory.getProp(this, data.a || { k: 0 }, 0, degToRads, this); - elementData.g = new GradientProperty(this, data.g, this); - } - elementData.o = PropertyFactory.getProp(this, data.o, 0, 0.01, this); - if (data.ty === 'st' || data.ty === 'gs') { - styleElem.lc = lineCapEnum[data.lc || 2]; - styleElem.lj = lineJoinEnum[data.lj || 2]; - if (data.lj == 1) { // eslint-disable-line eqeqeq - styleElem.ml = data.ml; - } - elementData.w = PropertyFactory.getProp(this, data.w, 0, null, this); - if (!elementData.w.k) { - styleElem.wi = elementData.w.v; - } - if (data.d) { - var d = new DashProperty(this, data.d, 'canvas', this); - elementData.d = d; - if (!elementData.d.k) { - styleElem.da = elementData.d.dashArray; - styleElem.do = elementData.d.dashoffset[0]; + this.renderConfig.dpr = (config && config.dpr) || 1; + if (this.animationItem.wrapper) { + this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; } + this.renderedFrame = -1; + this.globalData = { + frameNum: -1, + _mdf: false, + renderConfig: this.renderConfig, + currentGlobalAlpha: -1, + }; + this.contextData = new CVContextData(); + this.elements = []; + this.pendingElements = []; + this.transformMat = new Matrix(); + this.completeLayers = false; + this.rendererType = 'canvas'; } - } else { - styleElem.r = data.r === 2 ? 'evenodd' : 'nonzero'; - } - this.stylesList.push(styleElem); - elementData.style = styleElem; - return elementData; -}; - -CVShapeElement.prototype.createGroupElement = function () { - var elementData = { - it: [], - prevViewData: [], - }; - return elementData; -}; - -CVShapeElement.prototype.createTransformElement = function (data) { - var elementData = { - transform: { - opacity: 1, - _opMdf: false, - key: this.transformsManager.getNewKey(), - op: PropertyFactory.getProp(this, data.o, 0, 0.01, this), - mProps: TransformPropertyFactory.getTransformProperty(this, data, this), - }, - }; - return elementData; -}; - -CVShapeElement.prototype.createShapeElement = function (data) { - var elementData = new CVShapeData(this, data, this.stylesList, this.transformsManager); - - this.shapes.push(elementData); - this.addShapeToModifiers(elementData); - return elementData; -}; - -CVShapeElement.prototype.reloadShapes = function () { - this._isFirstFrame = true; - var i; - var len = this.itemsData.length; - for (i = 0; i < len; i += 1) { - this.prevViewData[i] = this.itemsData[i]; - } - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); - len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - this.dynamicProperties[i].getValue(); - } - this.renderModifiers(); - this.transformsManager.processSequences(this._isFirstFrame); -}; - -CVShapeElement.prototype.addTransformToStyleList = function (transform) { - var i; - var len = this.stylesList.length; - for (i = 0; i < len; i += 1) { - if (!this.stylesList[i].closed) { - this.stylesList[i].transforms.push(transform); - } - } -}; - -CVShapeElement.prototype.removeTransformFromStyleList = function () { - var i; - var len = this.stylesList.length; - for (i = 0; i < len; i += 1) { - if (!this.stylesList[i].closed) { - this.stylesList[i].transforms.pop(); - } - } -}; - -CVShapeElement.prototype.closeStyles = function (styles) { - var i; - var len = styles.length; - for (i = 0; i < len; i += 1) { - styles[i].closed = true; - } -}; - -CVShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, shouldRender, transforms) { - var i; - var len = arr.length - 1; - var j; - var jLen; - var ownStyles = []; - var ownModifiers = []; - var processedPos; - var modifier; - var currentTransform; - var ownTransforms = [].concat(transforms); - for (i = len; i >= 0; i -= 1) { - processedPos = this.searchProcessedElement(arr[i]); - if (!processedPos) { - arr[i]._shouldRender = shouldRender; - } else { - itemsData[i] = prevViewData[processedPos - 1]; - } - if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs') { - if (!processedPos) { - itemsData[i] = this.createStyleElement(arr[i], ownTransforms); + extendPrototype([BaseRenderer], CanvasRendererBase); + + CanvasRendererBase.prototype.createShape = function (data) { + return new CVShapeElement(data, this.globalData, this); + }; + + CanvasRendererBase.prototype.createText = function (data) { + return new CVTextElement(data, this.globalData, this); + }; + + CanvasRendererBase.prototype.createImage = function (data) { + return new CVImageElement(data, this.globalData, this); + }; + + CanvasRendererBase.prototype.createSolid = function (data) { + return new CVSolidElement(data, this.globalData, this); + }; + + CanvasRendererBase.prototype.createNull = SVGRenderer.prototype.createNull; + + CanvasRendererBase.prototype.ctxTransform = function (props) { + if (props[0] === 1 && props[1] === 0 && props[4] === 0 && props[5] === 1 && props[12] === 0 && props[13] === 0) { + return; + } + if (!this.renderConfig.clearCanvas) { + this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13]); + return; + } + this.transformMat.cloneFromProps(props); + var cProps = this.contextData.cTr.props; + this.transformMat.transform(cProps[0], cProps[1], cProps[2], cProps[3], cProps[4], cProps[5], cProps[6], cProps[7], cProps[8], cProps[9], cProps[10], cProps[11], cProps[12], cProps[13], cProps[14], cProps[15]); + // this.contextData.cTr.transform(props[0],props[1],props[2],props[3],props[4],props[5],props[6],props[7],props[8],props[9],props[10],props[11],props[12],props[13],props[14],props[15]); + this.contextData.cTr.cloneFromProps(this.transformMat.props); + var trProps = this.contextData.cTr.props; + this.canvasContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13]); + }; + + CanvasRendererBase.prototype.ctxOpacity = function (op) { + /* if(op === 1){ + return; + } */ + if (!this.renderConfig.clearCanvas) { + this.canvasContext.globalAlpha *= op < 0 ? 0 : op; + this.globalData.currentGlobalAlpha = this.contextData.cO; + return; + } + this.contextData.cO *= op < 0 ? 0 : op; + if (this.globalData.currentGlobalAlpha !== this.contextData.cO) { + this.canvasContext.globalAlpha = this.contextData.cO; + this.globalData.currentGlobalAlpha = this.contextData.cO; + } + }; + + CanvasRendererBase.prototype.reset = function () { + if (!this.renderConfig.clearCanvas) { + this.canvasContext.restore(); + return; + } + this.contextData.reset(); + }; + + CanvasRendererBase.prototype.save = function (actionFlag) { + if (!this.renderConfig.clearCanvas) { + this.canvasContext.save(); + return; + } + if (actionFlag) { + this.canvasContext.save(); + } + var props = this.contextData.cTr.props; + if (this.contextData._length <= this.contextData.cArrPos) { + this.contextData.duplicate(); + } + var i; + var arr = this.contextData.saved[this.contextData.cArrPos]; + for (i = 0; i < 16; i += 1) { + arr[i] = props[i]; + } + this.contextData.savedOp[this.contextData.cArrPos] = this.contextData.cO; + this.contextData.cArrPos += 1; + }; + + CanvasRendererBase.prototype.restore = function (actionFlag) { + if (!this.renderConfig.clearCanvas) { + this.canvasContext.restore(); + return; + } + if (actionFlag) { + this.canvasContext.restore(); + this.globalData.blendMode = 'source-over'; + } + this.contextData.cArrPos -= 1; + var popped = this.contextData.saved[this.contextData.cArrPos]; + var i; + var arr = this.contextData.cTr.props; + for (i = 0; i < 16; i += 1) { + arr[i] = popped[i]; + } + this.canvasContext.setTransform(popped[0], popped[1], popped[4], popped[5], popped[12], popped[13]); + popped = this.contextData.savedOp[this.contextData.cArrPos]; + this.contextData.cO = popped; + if (this.globalData.currentGlobalAlpha !== popped) { + this.canvasContext.globalAlpha = popped; + this.globalData.currentGlobalAlpha = popped; + } + }; + + CanvasRendererBase.prototype.configAnimation = function (animData) { + if (this.animationItem.wrapper) { + this.animationItem.container = createTag('canvas'); + var containerStyle = this.animationItem.container.style; + containerStyle.width = '100%'; + containerStyle.height = '100%'; + var origin = '0px 0px 0px'; + containerStyle.transformOrigin = origin; + containerStyle.mozTransformOrigin = origin; + containerStyle.webkitTransformOrigin = origin; + containerStyle['-webkit-transform'] = origin; + containerStyle.contentVisibility = this.renderConfig.contentVisibility; + this.animationItem.wrapper.appendChild(this.animationItem.container); + this.canvasContext = this.animationItem.container.getContext('2d'); + if (this.renderConfig.className) { + this.animationItem.container.setAttribute('class', this.renderConfig.className); + } + if (this.renderConfig.id) { + this.animationItem.container.setAttribute('id', this.renderConfig.id); + } + } else { + this.canvasContext = this.renderConfig.context; + } + this.data = animData; + this.layers = animData.layers; + this.transformCanvas = { + w: animData.w, + h: animData.h, + sx: 0, + sy: 0, + tx: 0, + ty: 0, + }; + this.setupGlobalData(animData, document.body); + this.globalData.canvasContext = this.canvasContext; + this.globalData.renderer = this; + this.globalData.isDashed = false; + this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; + this.globalData.transformCanvas = this.transformCanvas; + this.elements = createSizedArray(animData.layers.length); + + this.updateContainerSize(); + }; + + CanvasRendererBase.prototype.updateContainerSize = function () { + this.reset(); + var elementWidth; + var elementHeight; + if (this.animationItem.wrapper && this.animationItem.container) { + elementWidth = this.animationItem.wrapper.offsetWidth; + elementHeight = this.animationItem.wrapper.offsetHeight; + this.animationItem.container.setAttribute('width', elementWidth * this.renderConfig.dpr); + this.animationItem.container.setAttribute('height', elementHeight * this.renderConfig.dpr); + } else { + elementWidth = this.canvasContext.canvas.width * this.renderConfig.dpr; + elementHeight = this.canvasContext.canvas.height * this.renderConfig.dpr; + } + var elementRel; + var animationRel; + if (this.renderConfig.preserveAspectRatio.indexOf('meet') !== -1 || this.renderConfig.preserveAspectRatio.indexOf('slice') !== -1) { + var par = this.renderConfig.preserveAspectRatio.split(' '); + var fillType = par[1] || 'meet'; + var pos = par[0] || 'xMidYMid'; + var xPos = pos.substr(0, 4); + var yPos = pos.substr(4); + elementRel = elementWidth / elementHeight; + animationRel = this.transformCanvas.w / this.transformCanvas.h; + if ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice')) { + this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); + this.transformCanvas.sy = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); + } else { + this.transformCanvas.sx = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); + this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); + } + + if (xPos === 'xMid' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { + this.transformCanvas.tx = ((elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) / 2) * this.renderConfig.dpr; + } else if (xPos === 'xMax' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { + this.transformCanvas.tx = (elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) * this.renderConfig.dpr; + } else { + this.transformCanvas.tx = 0; + } + if (yPos === 'YMid' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { + this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w)) / 2) * this.renderConfig.dpr; + } else if (yPos === 'YMax' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { + this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w))) * this.renderConfig.dpr; + } else { + this.transformCanvas.ty = 0; + } + } else if (this.renderConfig.preserveAspectRatio === 'none') { + this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); + this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); + this.transformCanvas.tx = 0; + this.transformCanvas.ty = 0; } else { - itemsData[i].style.closed = false; + this.transformCanvas.sx = this.renderConfig.dpr; + this.transformCanvas.sy = this.renderConfig.dpr; + this.transformCanvas.tx = 0; + this.transformCanvas.ty = 0; + } + this.transformCanvas.props = [this.transformCanvas.sx, 0, 0, 0, 0, this.transformCanvas.sy, 0, 0, 0, 0, 1, 0, this.transformCanvas.tx, this.transformCanvas.ty, 0, 1]; + /* var i, len = this.elements.length; + for(i=0;i= 0; i -= 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + this.elements.length = 0; + this.globalData.canvasContext = null; + this.animationItem.container = null; + this.destroyed = true; + }; + + CanvasRendererBase.prototype.renderFrame = function (num, forceRender) { + if ((this.renderedFrame === num && this.renderConfig.clearCanvas === true && !forceRender) || this.destroyed || num === -1) { + return; + } + this.renderedFrame = num; + this.globalData.frameNum = num - this.animationItem._isFirstFrame; + this.globalData.frameId += 1; + this.globalData._mdf = !this.renderConfig.clearCanvas || forceRender; + this.globalData.projectInterface.currentFrame = num; + + // console.log('--------'); + // console.log('NEW: ',num); + var i; + var len = this.layers.length; + if (!this.completeLayers) { + this.checkLayers(num); + } + + for (i = 0; i < len; i += 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].prepareFrame(num - this.layers[i].st); + } + } + if (this.globalData._mdf) { + if (this.renderConfig.clearCanvas === true) { + this.canvasContext.clearRect(0, 0, this.transformCanvas.w, this.transformCanvas.h); + } else { + this.save(); + } + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } + if (this.renderConfig.clearCanvas !== true) { + this.restore(); + } + } + }; + + CanvasRendererBase.prototype.buildItem = function (pos) { + var elements = this.elements; + if (elements[pos] || this.layers[pos].ty === 99) { + return; + } + var element = this.createItem(this.layers[pos], this, this.globalData); + elements[pos] = element; + element.initExpressions(); + /* if(this.layers[pos].ty === 0){ + element.resize(this.globalData.transformCanvas); + } */ + }; + + CanvasRendererBase.prototype.checkPendingElements = function () { + while (this.pendingElements.length) { + var element = this.pendingElements.pop(); + element.checkParenting(); + } + }; + + CanvasRendererBase.prototype.hide = function () { + this.animationItem.container.style.display = 'none'; + }; + + CanvasRendererBase.prototype.show = function () { + this.animationItem.container.style.display = 'block'; + }; + + function CVCompElement(data, globalData, comp) { + this.completeLayers = false; + this.layers = data.layers; + this.pendingElements = []; + this.elements = createSizedArray(this.layers.length); + this.initElement(data, globalData, comp); + this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; + } + + extendPrototype([CanvasRendererBase, ICompElement, CVBaseElement], CVCompElement); + + CVCompElement.prototype.renderInnerContent = function () { + var ctx = this.canvasContext; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(this.data.w, 0); + ctx.lineTo(this.data.w, this.data.h); + ctx.lineTo(0, this.data.h); + ctx.lineTo(0, 0); + ctx.clip(); + var i; + var len = this.layers.length; + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } + }; + + CVCompElement.prototype.destroy = function () { + var i; + var len = this.layers.length; + for (i = len - 1; i >= 0; i -= 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + this.layers = null; + this.elements = null; + }; + + CVCompElement.prototype.createComp = function (data) { + return new CVCompElement(data, this.globalData, this); + }; + + function CanvasRenderer(animationItem, config) { + this.animationItem = animationItem; + this.renderConfig = { + clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, + context: (config && config.context) || null, + progressiveLoad: (config && config.progressiveLoad) || false, + preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', + imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', + contentVisibility: (config && config.contentVisibility) || 'visible', + className: (config && config.className) || '', + id: (config && config.id) || '', + }; + this.renderConfig.dpr = (config && config.dpr) || 1; + if (this.animationItem.wrapper) { + this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; + } + this.renderedFrame = -1; + this.globalData = { + frameNum: -1, + _mdf: false, + renderConfig: this.renderConfig, + currentGlobalAlpha: -1, + }; + this.contextData = new CVContextData(); + this.elements = []; + this.pendingElements = []; + this.transformMat = new Matrix(); + this.completeLayers = false; + this.rendererType = 'canvas'; + } + extendPrototype([CanvasRendererBase], CanvasRenderer); + + CanvasRenderer.prototype.createComp = function (data) { + return new CVCompElement(data, this.globalData, this); + }; + + // Registering renderers + registerRenderer('canvas', CanvasRenderer); + + // Registering shape modifiers + ShapeModifiers.registerModifier('tm', TrimModifier); + ShapeModifiers.registerModifier('pb', PuckerAndBloatModifier); + ShapeModifiers.registerModifier('rp', RepeaterModifier); + ShapeModifiers.registerModifier('rd', RoundCornersModifier); + + const Expressions = (function () { + var ob = {}; + ob.initExpressions = initExpressions; + + function initExpressions(animation) { + var stackCount = 0; + var registers = []; + + function pushExpression() { + stackCount += 1; + } + + function popExpression() { + stackCount -= 1; + if (stackCount === 0) { + releaseInstances(); + } + } + + function registerExpressionProperty(expression) { + if (registers.indexOf(expression) === -1) { + registers.push(expression); + } + } + + function releaseInstances() { + var i; + var len = registers.length; + for (i = 0; i < len; i += 1) { + registers[i].release(); + } + registers.length = 0; + } + + animation.renderer.compInterface = CompExpressionInterface(animation.renderer); + animation.renderer.globalData.projectInterface.registerComposition(animation.renderer); + animation.renderer.globalData.pushExpression = pushExpression; + animation.renderer.globalData.popExpression = popExpression; + animation.renderer.globalData.registerExpressionProperty = registerExpressionProperty; } + return ob; + }()); + + /* eslint-disable */ + /* + Copyright 2014 David Bau. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + + function seedRandom(pool, math) { + // + // The following constants are related to IEEE 754 limits. + // + var global = this, + width = 256, // each RC4 output is 0 <= x < 256 + chunks = 6, // at least six RC4 outputs for each double + digits = 52, // there are 52 significant digits in a double + rngname = 'random', // rngname: name for Math.random and Math.seedrandom + startdenom = math.pow(width, chunks), + significance = math.pow(2, digits), + overflow = significance * 2, + mask = width - 1, + nodecrypto; // node.js crypto module, initialized at the bottom. + + // + // seedrandom() + // This is the seedrandom function described above. + // + function seedrandom(seed, options, callback) { + var key = []; + options = (options === true) ? { entropy: true } : (options || {}); + + // Flatten the seed string or build one from local entropy if needed. + var shortseed = mixkey(flatten( + options.entropy ? [seed, tostring(pool)] : + (seed === null) ? autoseed() : seed, 3), key); + + // Use the seed to initialize an ARC4 generator. + var arc4 = new ARC4(key); + + // This function returns a random double in [0, 1) that contains + // randomness in every bit of the mantissa of the IEEE 754 value. + var prng = function() { + var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48 + d = startdenom, // and denominator d = 2 ^ 48. + x = 0; // and no 'extra last byte'. + while (n < significance) { // Fill up all significant digits by + n = (n + x) * width; // shifting numerator and + d *= width; // denominator and generating a + x = arc4.g(1); // new least-significant-byte. + } + while (n >= overflow) { // To avoid rounding up, before adding + n /= 2; // last byte, shift everything + d /= 2; // right using integer math until + x >>>= 1; // we have exactly the desired bits. + } + return (n + x) / d; // Form the number within [0, 1). + }; + + prng.int32 = function() { return arc4.g(4) | 0; }; + prng.quick = function() { return arc4.g(4) / 0x100000000; }; + prng.double = prng; + + // Mix the randomness into accumulated entropy. + mixkey(tostring(arc4.S), pool); + + // Calling convention: what to return as a function of prng, seed, is_math. + return (options.pass || callback || + function(prng, seed, is_math_call, state) { + if (state) { + // Load the arc4 state from the given state if it has an S array. + if (state.S) { copy(state, arc4); } + // Only provide the .state method if requested via options.state. + prng.state = function() { return copy(arc4, {}); }; + } + + // If called as a method of Math (Math.seedrandom()), mutate + // Math.random because that is how seedrandom.js has worked since v1.0. + if (is_math_call) { math[rngname] = prng; return seed; } + + // Otherwise, it is a newer calling convention, so return the + // prng directly. + else return prng; + })( + prng, + shortseed, + 'global' in options ? options.global : (this == math), + options.state); + } + math['seed' + rngname] = seedrandom; + + // + // ARC4 + // + // An ARC4 implementation. The constructor takes a key in the form of + // an array of at most (width) integers that should be 0 <= x < (width). + // + // The g(count) method returns a pseudorandom integer that concatenates + // the next (count) outputs from ARC4. Its return value is a number x + // that is in the range 0 <= x < (width ^ count). + // + function ARC4(key) { + var t, keylen = key.length, + me = this, i = 0, j = me.i = me.j = 0, s = me.S = []; - ownStyles.push(itemsData[i].style); - } else if (arr[i].ty === 'gr') { - if (!processedPos) { - itemsData[i] = this.createGroupElement(arr[i]); - } else { - jLen = itemsData[i].it.length; - for (j = 0; j < jLen; j += 1) { - itemsData[i].prevViewData[j] = itemsData[i].it[j]; - } - } - this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, shouldRender, ownTransforms); - } else if (arr[i].ty === 'tr') { - if (!processedPos) { - currentTransform = this.createTransformElement(arr[i]); - itemsData[i] = currentTransform; - } - ownTransforms.push(itemsData[i]); - this.addTransformToStyleList(itemsData[i]); - } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { - if (!processedPos) { - itemsData[i] = this.createShapeElement(arr[i]); - } - } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'pb') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - modifier.init(this, arr[i]); - itemsData[i] = modifier; - this.shapeModifiers.push(modifier); - } else { - modifier = itemsData[i]; - modifier.closed = false; - } - ownModifiers.push(modifier); - } else if (arr[i].ty === 'rp') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - itemsData[i] = modifier; - modifier.init(this, arr, i, itemsData); - this.shapeModifiers.push(modifier); - shouldRender = false; - } else { - modifier = itemsData[i]; - modifier.closed = true; - } - ownModifiers.push(modifier); - } - this.addProcessedElement(arr[i], i + 1); - } - this.removeTransformFromStyleList(); - this.closeStyles(ownStyles); - len = ownModifiers.length; - for (i = 0; i < len; i += 1) { - ownModifiers[i].closed = true; - } -}; - -CVShapeElement.prototype.renderInnerContent = function () { - this.transformHelper.opacity = 1; - this.transformHelper._opMdf = false; - this.renderModifiers(); - this.transformsManager.processSequences(this._isFirstFrame); - this.renderShape(this.transformHelper, this.shapesData, this.itemsData, true); -}; - -CVShapeElement.prototype.renderShapeTransform = function (parentTransform, groupTransform) { - if (parentTransform._opMdf || groupTransform.op._mdf || this._isFirstFrame) { - groupTransform.opacity = parentTransform.opacity; - groupTransform.opacity *= groupTransform.op.v; - groupTransform._opMdf = true; - } -}; - -CVShapeElement.prototype.drawLayer = function () { - var i; - var len = this.stylesList.length; - var j; - var jLen; - var k; - var kLen; - var elems; - var nodes; - var renderer = this.globalData.renderer; - var ctx = this.globalData.canvasContext; - var type; - var currentStyle; - for (i = 0; i < len; i += 1) { - currentStyle = this.stylesList[i]; - type = currentStyle.type; - - // Skipping style when - // Stroke width equals 0 - // style should not be rendered (extra unused repeaters) - // current opacity equals 0 - // global opacity equals 0 - if (!(((type === 'st' || type === 'gs') && currentStyle.wi === 0) || !currentStyle.data._shouldRender || currentStyle.coOp === 0 || this.globalData.currentGlobalAlpha === 0)) { - renderer.save(); - elems = currentStyle.elements; - if (type === 'st' || type === 'gs') { - ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd; - ctx.lineWidth = currentStyle.wi; - ctx.lineCap = currentStyle.lc; - ctx.lineJoin = currentStyle.lj; - ctx.miterLimit = currentStyle.ml || 0; - } else { - ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd; - } - renderer.ctxOpacity(currentStyle.coOp); - if (type !== 'st' && type !== 'gs') { - ctx.beginPath(); - } - renderer.ctxTransform(currentStyle.preTransforms.finalTransform.props); - jLen = elems.length; - for (j = 0; j < jLen; j += 1) { - if (type === 'st' || type === 'gs') { - ctx.beginPath(); - if (currentStyle.da) { - ctx.setLineDash(currentStyle.da); - ctx.lineDashOffset = currentStyle.do; - } + // The empty key [] is treated as [0]. + if (!keylen) { key = [keylen++]; } + + // Set up S using the standard key scheduling algorithm. + while (i < width) { + s[i] = i++; + } + for (i = 0; i < width; i++) { + s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))]; + s[j] = t; + } + + // The "g" method returns the next (count) outputs as one number. + me.g = function(count) { + // Using instance members instead of closure state nearly doubles speed. + var t, r = 0, + i = me.i, j = me.j, s = me.S; + while (count--) { + t = s[i = mask & (i + 1)]; + r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))]; + } + me.i = i; me.j = j; + return r; + // For robust unpredictability, the function call below automatically + // discards an initial batch of values. This is called RC4-drop[256]. + // See http://google.com/search?q=rsa+fluhrer+response&btnI + }; } - nodes = elems[j].trNodes; - kLen = nodes.length; - for (k = 0; k < kLen; k += 1) { - if (nodes[k].t === 'm') { - ctx.moveTo(nodes[k].p[0], nodes[k].p[1]); - } else if (nodes[k].t === 'c') { - ctx.bezierCurveTo(nodes[k].pts[0], nodes[k].pts[1], nodes[k].pts[2], nodes[k].pts[3], nodes[k].pts[4], nodes[k].pts[5]); - } else { - ctx.closePath(); - } + // + // copy() + // Copies internal state of ARC4 to or from a plain object. + // + function copy(f, t) { + t.i = f.i; + t.j = f.j; + t.S = f.S.slice(); + return t; } - if (type === 'st' || type === 'gs') { - ctx.stroke(); - if (currentStyle.da) { - ctx.setLineDash(this.dashResetter); - } + + // + // flatten() + // Converts an object tree to nested arrays of strings. + // + function flatten(obj, depth) { + var result = [], typ = (typeof obj), prop; + if (depth && typ == 'object') { + for (prop in obj) { + try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} + } + } + return (result.length ? result : typ == 'string' ? obj : obj + '\0'); } - } - if (type !== 'st' && type !== 'gs') { - ctx.fill(currentStyle.r); - } - renderer.restore(); - } - } -}; - -CVShapeElement.prototype.renderShape = function (parentTransform, items, data, isMain) { - var i; - var len = items.length - 1; - var groupTransform; - groupTransform = parentTransform; - for (i = len; i >= 0; i -= 1) { - if (items[i].ty === 'tr') { - groupTransform = data[i].transform; - this.renderShapeTransform(parentTransform, groupTransform); - } else if (items[i].ty === 'sh' || items[i].ty === 'el' || items[i].ty === 'rc' || items[i].ty === 'sr') { - this.renderPath(items[i], data[i]); - } else if (items[i].ty === 'fl') { - this.renderFill(items[i], data[i], groupTransform); - } else if (items[i].ty === 'st') { - this.renderStroke(items[i], data[i], groupTransform); - } else if (items[i].ty === 'gf' || items[i].ty === 'gs') { - this.renderGradientFill(items[i], data[i], groupTransform); - } else if (items[i].ty === 'gr') { - this.renderShape(groupTransform, items[i].it, data[i].it); - } else if (items[i].ty === 'tm') { - // - } - } - if (isMain) { - this.drawLayer(); - } -}; - -CVShapeElement.prototype.renderStyledShape = function (styledShape, shape) { - if (this._isFirstFrame || shape._mdf || styledShape.transforms._mdf) { - var shapeNodes = styledShape.trNodes; - var paths = shape.paths; - var i; - var len; - var j; - var jLen = paths._length; - shapeNodes.length = 0; - var groupTransformMat = styledShape.transforms.finalTransform; - for (j = 0; j < jLen; j += 1) { - var pathNodes = paths.shapes[j]; - if (pathNodes && pathNodes.v) { - len = pathNodes._length; - for (i = 1; i < len; i += 1) { - if (i === 1) { - shapeNodes.push({ - t: 'm', - p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), - }); - } - shapeNodes.push({ - t: 'c', - pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[i], pathNodes.v[i]), - }); + + // + // mixkey() + // Mixes a string seed into a key that is an array of integers, and + // returns a shortened string seed that is equivalent to the result key. + // + function mixkey(seed, key) { + var stringseed = seed + '', smear, j = 0; + while (j < stringseed.length) { + key[mask & j] = + mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++)); + } + return tostring(key); } - if (len === 1) { - shapeNodes.push({ - t: 'm', - p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), - }); + + // + // autoseed() + // Returns an object for autoseeding, using window.crypto and Node crypto + // module if available. + // + function autoseed() { + try { + if (nodecrypto) { return tostring(nodecrypto.randomBytes(width)); } + var out = new Uint8Array(width); + (global.crypto || global.msCrypto).getRandomValues(out); + return tostring(out); + } catch (e) { + var browser = global.navigator, + plugins = browser && browser.plugins; + return [+new Date(), global, plugins, global.screen, tostring(pool)]; + } } - if (pathNodes.c && len) { - shapeNodes.push({ - t: 'c', - pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[0], pathNodes.v[0]), - }); - shapeNodes.push({ - t: 'z', - }); + + // + // tostring() + // Converts an array of charcodes to a string + // + function tostring(a) { + return String.fromCharCode.apply(0, a); } - } - } - styledShape.trNodes = shapeNodes; - } -}; - -CVShapeElement.prototype.renderPath = function (pathData, itemData) { - if (pathData.hd !== true && pathData._shouldRender) { - var i; - var len = itemData.styledShapes.length; - for (i = 0; i < len; i += 1) { - this.renderStyledShape(itemData.styledShapes[i], itemData.sh); - } - } -}; - -CVShapeElement.prototype.renderFill = function (styleData, itemData, groupTransform) { - var styleElem = itemData.style; - - if (itemData.c._mdf || this._isFirstFrame) { - styleElem.co = 'rgb(' - + bmFloor(itemData.c.v[0]) + ',' - + bmFloor(itemData.c.v[1]) + ',' - + bmFloor(itemData.c.v[2]) + ')'; - } - if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { - styleElem.coOp = itemData.o.v * groupTransform.opacity; - } -}; - -CVShapeElement.prototype.renderGradientFill = function (styleData, itemData, groupTransform) { - var styleElem = itemData.style; - var grd; - if (!styleElem.grd || itemData.g._mdf || itemData.s._mdf || itemData.e._mdf || (styleData.t !== 1 && (itemData.h._mdf || itemData.a._mdf))) { - var ctx = this.globalData.canvasContext; - var pt1 = itemData.s.v; - var pt2 = itemData.e.v; - if (styleData.t === 1) { - grd = ctx.createLinearGradient(pt1[0], pt1[1], pt2[0], pt2[1]); - } else { - var rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); - var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); - - var percent = itemData.h.v; - if (percent >= 1) { - percent = 0.99; - } else if (percent <= -1) { - percent = -0.99; - } - var dist = rad * percent; - var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; - var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; - grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad); - } - var i; - var len = styleData.g.p; - var cValues = itemData.g.c; - var opacity = 1; + // + // When seedrandom.js is loaded, we immediately mix a few bits + // from the built-in RNG into the entropy pool. Because we do + // not want to interfere with deterministic PRNG state later, + // seedrandom will not call math.random on its own again after + // initialization. + // + mixkey(math.random(), pool); - for (i = 0; i < len; i += 1) { - if (itemData.g._hasOpacity && itemData.g._collapsable) { - opacity = itemData.g.o[i * 2 + 1]; - } - grd.addColorStop(cValues[i * 4] / 100, 'rgba(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ',' + opacity + ')'); - } - styleElem.grd = grd; - } - styleElem.coOp = itemData.o.v * groupTransform.opacity; -}; - -CVShapeElement.prototype.renderStroke = function (styleData, itemData, groupTransform) { - var styleElem = itemData.style; - var d = itemData.d; - if (d && (d._mdf || this._isFirstFrame)) { - styleElem.da = d.dashArray; - styleElem.do = d.dashoffset[0]; - } - if (itemData.c._mdf || this._isFirstFrame) { - styleElem.co = 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'; - } - if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { - styleElem.coOp = itemData.o.v * groupTransform.opacity; - } - if (itemData.w._mdf || this._isFirstFrame) { - styleElem.wi = itemData.w.v; - } -}; - -CVShapeElement.prototype.destroy = function () { - this.shapesData = null; - this.globalData = null; - this.canvasContext = null; - this.stylesList.length = 0; - this.itemsData.length = 0; -}; - -function CVTextElement(data, globalData, comp) { - this.textSpans = []; - this.yOffset = 0; - this.fillColorAnim = false; - this.strokeColorAnim = false; - this.strokeWidthAnim = false; - this.stroke = false; - this.fill = false; - this.justifyOffset = 0; - this.currentRender = null; - this.renderType = 'canvas'; - this.values = { - fill: 'rgba(0,0,0,0)', - stroke: 'rgba(0,0,0,0)', - sWidth: 0, - fValue: '', - }; - this.initElement(data, globalData, comp); -} -extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement, ITextElement], CVTextElement); - -CVTextElement.prototype.tHelper = createTag('canvas').getContext('2d'); - -CVTextElement.prototype.buildNewText = function () { - var documentData = this.textProperty.currentData; - this.renderedLetters = createSizedArray(documentData.l ? documentData.l.length : 0); - - var hasFill = false; - if (documentData.fc) { - hasFill = true; - this.values.fill = this.buildColor(documentData.fc); - } else { - this.values.fill = 'rgba(0,0,0,0)'; - } - this.fill = hasFill; - var hasStroke = false; - if (documentData.sc) { - hasStroke = true; - this.values.stroke = this.buildColor(documentData.sc); - this.values.sWidth = documentData.sw; - } - var fontData = this.globalData.fontManager.getFontByName(documentData.f); - var i; - var len; - var letters = documentData.l; - var matrixHelper = this.mHelper; - this.stroke = hasStroke; - this.values.fValue = documentData.finalSize + 'px ' + this.globalData.fontManager.getFontByName(documentData.f).fFamily; - len = documentData.finalText.length; - // this.tHelper.font = this.values.fValue; - var charData; - var shapeData; - var k; - var kLen; - var shapes; - var j; - var jLen; - var pathNodes; - var commands; - var pathArr; - var singleShape = this.data.singleShape; - var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; - var xPos = 0; - var yPos = 0; - var firstLine = true; - var cnt = 0; - for (i = 0; i < len; i += 1) { - charData = this.globalData.fontManager.getCharData(documentData.finalText[i], fontData.fStyle, this.globalData.fontManager.getFontByName(documentData.f).fFamily); - shapeData = (charData && charData.data) || {}; - matrixHelper.reset(); - if (singleShape && letters[i].n) { - xPos = -trackingOffset; - yPos += documentData.yOffset; - yPos += firstLine ? 1 : 0; - firstLine = false; - } - shapes = shapeData.shapes ? shapeData.shapes[0].it : []; - jLen = shapes.length; - matrixHelper.scale(documentData.finalSize / 100, documentData.finalSize / 100); - if (singleShape) { - this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); - } - commands = createSizedArray(jLen - 1); - var commandsCounter = 0; - for (j = 0; j < jLen; j += 1) { - if (shapes[j].ty === 'sh') { - kLen = shapes[j].ks.k.i.length; - pathNodes = shapes[j].ks.k; - pathArr = []; - for (k = 1; k < kLen; k += 1) { - if (k === 1) { - pathArr.push(matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); - } - pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToY(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToX(pathNodes.v[k][0], pathNodes.v[k][1], 0), matrixHelper.applyToY(pathNodes.v[k][0], pathNodes.v[k][1], 0)); - } - pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToY(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); - commands[commandsCounter] = pathArr; - commandsCounter += 1; - } - } - if (singleShape) { - xPos += letters[i].l; - xPos += trackingOffset; - } - if (this.textSpans[cnt]) { - this.textSpans[cnt].elem = commands; - } else { - this.textSpans[cnt] = { elem: commands }; - } - cnt += 1; - } -}; - -CVTextElement.prototype.renderInnerContent = function () { - var ctx = this.canvasContext; - ctx.font = this.values.fValue; - ctx.lineCap = 'butt'; - ctx.lineJoin = 'miter'; - ctx.miterLimit = 4; - - if (!this.data.singleShape) { - this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); - } - - var i; - var len; - var j; - var jLen; - var k; - var kLen; - var renderedLetters = this.textAnimator.renderedLetters; - - var letters = this.textProperty.currentData.l; - - len = letters.length; - var renderedLetter; - var lastFill = null; - var lastStroke = null; - var lastStrokeW = null; - var commands; - var pathArr; - for (i = 0; i < len; i += 1) { - if (!letters[i].n) { - renderedLetter = renderedLetters[i]; - if (renderedLetter) { - this.globalData.renderer.save(); - this.globalData.renderer.ctxTransform(renderedLetter.p); - this.globalData.renderer.ctxOpacity(renderedLetter.o); - } - if (this.fill) { - if (renderedLetter && renderedLetter.fc) { - if (lastFill !== renderedLetter.fc) { - lastFill = renderedLetter.fc; - ctx.fillStyle = renderedLetter.fc; - } - } else if (lastFill !== this.values.fill) { - lastFill = this.values.fill; - ctx.fillStyle = this.values.fill; - } - commands = this.textSpans[i].elem; - jLen = commands.length; - this.globalData.canvasContext.beginPath(); - for (j = 0; j < jLen; j += 1) { - pathArr = commands[j]; - kLen = pathArr.length; - this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); - for (k = 2; k < kLen; k += 6) { - this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); - } - } - this.globalData.canvasContext.closePath(); - this.globalData.canvasContext.fill(); - /// ctx.fillText(this.textSpans[i].val,0,0); - } - if (this.stroke) { - if (renderedLetter && renderedLetter.sw) { - if (lastStrokeW !== renderedLetter.sw) { - lastStrokeW = renderedLetter.sw; - ctx.lineWidth = renderedLetter.sw; - } - } else if (lastStrokeW !== this.values.sWidth) { - lastStrokeW = this.values.sWidth; - ctx.lineWidth = this.values.sWidth; - } - if (renderedLetter && renderedLetter.sc) { - if (lastStroke !== renderedLetter.sc) { - lastStroke = renderedLetter.sc; - ctx.strokeStyle = renderedLetter.sc; - } - } else if (lastStroke !== this.values.stroke) { - lastStroke = this.values.stroke; - ctx.strokeStyle = this.values.stroke; - } - commands = this.textSpans[i].elem; - jLen = commands.length; - this.globalData.canvasContext.beginPath(); - for (j = 0; j < jLen; j += 1) { - pathArr = commands[j]; - kLen = pathArr.length; - this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); - for (k = 2; k < kLen; k += 6) { - this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); - } - } - this.globalData.canvasContext.closePath(); - this.globalData.canvasContext.stroke(); - /// ctx.strokeText(letters[i].val,0,0); - } - if (renderedLetter) { - this.globalData.renderer.restore(); - } - } - } -}; - -function CVImageElement(data, globalData, comp) { - this.assetData = globalData.getAssetData(data.refId); - this.img = globalData.imageLoader.getAsset(this.assetData); - this.initElement(data, globalData, comp); -} -extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVImageElement); - -CVImageElement.prototype.initElement = SVGShapeElement.prototype.initElement; -CVImageElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; - -CVImageElement.prototype.createContent = function () { - if (this.img.width && (this.assetData.w !== this.img.width || this.assetData.h !== this.img.height)) { - var canvas = createTag('canvas'); - canvas.width = this.assetData.w; - canvas.height = this.assetData.h; - var ctx = canvas.getContext('2d'); - - var imgW = this.img.width; - var imgH = this.img.height; - var imgRel = imgW / imgH; - var canvasRel = this.assetData.w / this.assetData.h; - var widthCrop; - var heightCrop; - var par = this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio; - if ((imgRel > canvasRel && par === 'xMidYMid slice') || (imgRel < canvasRel && par !== 'xMidYMid slice')) { - heightCrop = imgH; - widthCrop = heightCrop * canvasRel; - } else { - widthCrop = imgW; - heightCrop = widthCrop / canvasRel; - } - ctx.drawImage(this.img, (imgW - widthCrop) / 2, (imgH - heightCrop) / 2, widthCrop, heightCrop, 0, 0, this.assetData.w, this.assetData.h); - this.img = canvas; - } -}; - -CVImageElement.prototype.renderInnerContent = function () { - this.canvasContext.drawImage(this.img, 0, 0); -}; - -CVImageElement.prototype.destroy = function () { - this.img = null; -}; - -function CVSolidElement(data, globalData, comp) { - this.initElement(data, globalData, comp); -} -extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVSolidElement); - -CVSolidElement.prototype.initElement = SVGShapeElement.prototype.initElement; -CVSolidElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; - -CVSolidElement.prototype.renderInnerContent = function () { - var ctx = this.canvasContext; - ctx.fillStyle = this.data.sc; - ctx.fillRect(0, 0, this.data.sw, this.data.sh); - // -}; - -function CanvasRendererBase(animationItem, config) { - this.animationItem = animationItem; - this.renderConfig = { - clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, - context: (config && config.context) || null, - progressiveLoad: (config && config.progressiveLoad) || false, - preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', - imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', - contentVisibility: (config && config.contentVisibility) || 'visible', - className: (config && config.className) || '', - id: (config && config.id) || '', - }; - this.renderConfig.dpr = (config && config.dpr) || 1; - if (this.animationItem.wrapper) { - this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; - } - this.renderedFrame = -1; - this.globalData = { - frameNum: -1, - _mdf: false, - renderConfig: this.renderConfig, - currentGlobalAlpha: -1, - }; - this.contextData = new CVContextData(); - this.elements = []; - this.pendingElements = []; - this.transformMat = new Matrix(); - this.completeLayers = false; - this.rendererType = 'canvas'; -} -extendPrototype([BaseRenderer], CanvasRendererBase); - -CanvasRendererBase.prototype.createShape = function (data) { - return new CVShapeElement(data, this.globalData, this); -}; - -CanvasRendererBase.prototype.createText = function (data) { - return new CVTextElement(data, this.globalData, this); -}; - -CanvasRendererBase.prototype.createImage = function (data) { - return new CVImageElement(data, this.globalData, this); -}; - -CanvasRendererBase.prototype.createSolid = function (data) { - return new CVSolidElement(data, this.globalData, this); -}; - -CanvasRendererBase.prototype.createNull = SVGRenderer.prototype.createNull; - -CanvasRendererBase.prototype.ctxTransform = function (props) { - if (props[0] === 1 && props[1] === 0 && props[4] === 0 && props[5] === 1 && props[12] === 0 && props[13] === 0) { - return; - } - if (!this.renderConfig.clearCanvas) { - this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13]); - return; - } - this.transformMat.cloneFromProps(props); - var cProps = this.contextData.cTr.props; - this.transformMat.transform(cProps[0], cProps[1], cProps[2], cProps[3], cProps[4], cProps[5], cProps[6], cProps[7], cProps[8], cProps[9], cProps[10], cProps[11], cProps[12], cProps[13], cProps[14], cProps[15]); - // this.contextData.cTr.transform(props[0],props[1],props[2],props[3],props[4],props[5],props[6],props[7],props[8],props[9],props[10],props[11],props[12],props[13],props[14],props[15]); - this.contextData.cTr.cloneFromProps(this.transformMat.props); - var trProps = this.contextData.cTr.props; - this.canvasContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13]); -}; - -CanvasRendererBase.prototype.ctxOpacity = function (op) { - /* if(op === 1){ - return; - } */ - if (!this.renderConfig.clearCanvas) { - this.canvasContext.globalAlpha *= op < 0 ? 0 : op; - this.globalData.currentGlobalAlpha = this.contextData.cO; - return; - } - this.contextData.cO *= op < 0 ? 0 : op; - if (this.globalData.currentGlobalAlpha !== this.contextData.cO) { - this.canvasContext.globalAlpha = this.contextData.cO; - this.globalData.currentGlobalAlpha = this.contextData.cO; - } -}; - -CanvasRendererBase.prototype.reset = function () { - if (!this.renderConfig.clearCanvas) { - this.canvasContext.restore(); - return; - } - this.contextData.reset(); -}; - -CanvasRendererBase.prototype.save = function (actionFlag) { - if (!this.renderConfig.clearCanvas) { - this.canvasContext.save(); - return; - } - if (actionFlag) { - this.canvasContext.save(); - } - var props = this.contextData.cTr.props; - if (this.contextData._length <= this.contextData.cArrPos) { - this.contextData.duplicate(); - } - var i; - var arr = this.contextData.saved[this.contextData.cArrPos]; - for (i = 0; i < 16; i += 1) { - arr[i] = props[i]; - } - this.contextData.savedOp[this.contextData.cArrPos] = this.contextData.cO; - this.contextData.cArrPos += 1; -}; - -CanvasRendererBase.prototype.restore = function (actionFlag) { - if (!this.renderConfig.clearCanvas) { - this.canvasContext.restore(); - return; - } - if (actionFlag) { - this.canvasContext.restore(); - this.globalData.blendMode = 'source-over'; - } - this.contextData.cArrPos -= 1; - var popped = this.contextData.saved[this.contextData.cArrPos]; - var i; - var arr = this.contextData.cTr.props; - for (i = 0; i < 16; i += 1) { - arr[i] = popped[i]; - } - this.canvasContext.setTransform(popped[0], popped[1], popped[4], popped[5], popped[12], popped[13]); - popped = this.contextData.savedOp[this.contextData.cArrPos]; - this.contextData.cO = popped; - if (this.globalData.currentGlobalAlpha !== popped) { - this.canvasContext.globalAlpha = popped; - this.globalData.currentGlobalAlpha = popped; - } -}; - -CanvasRendererBase.prototype.configAnimation = function (animData) { - if (this.animationItem.wrapper) { - this.animationItem.container = createTag('canvas'); - var containerStyle = this.animationItem.container.style; - containerStyle.width = '100%'; - containerStyle.height = '100%'; - var origin = '0px 0px 0px'; - containerStyle.transformOrigin = origin; - containerStyle.mozTransformOrigin = origin; - containerStyle.webkitTransformOrigin = origin; - containerStyle['-webkit-transform'] = origin; - containerStyle.contentVisibility = this.renderConfig.contentVisibility; - this.animationItem.wrapper.appendChild(this.animationItem.container); - this.canvasContext = this.animationItem.container.getContext('2d'); - if (this.renderConfig.className) { - this.animationItem.container.setAttribute('class', this.renderConfig.className); - } - if (this.renderConfig.id) { - this.animationItem.container.setAttribute('id', this.renderConfig.id); - } - } else { - this.canvasContext = this.renderConfig.context; - } - this.data = animData; - this.layers = animData.layers; - this.transformCanvas = { - w: animData.w, - h: animData.h, - sx: 0, - sy: 0, - tx: 0, - ty: 0, - }; - this.setupGlobalData(animData, document.body); - this.globalData.canvasContext = this.canvasContext; - this.globalData.renderer = this; - this.globalData.isDashed = false; - this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; - this.globalData.transformCanvas = this.transformCanvas; - this.elements = createSizedArray(animData.layers.length); - - this.updateContainerSize(); -}; - -CanvasRendererBase.prototype.updateContainerSize = function () { - this.reset(); - var elementWidth; - var elementHeight; - if (this.animationItem.wrapper && this.animationItem.container) { - elementWidth = this.animationItem.wrapper.offsetWidth; - elementHeight = this.animationItem.wrapper.offsetHeight; - this.animationItem.container.setAttribute('width', elementWidth * this.renderConfig.dpr); - this.animationItem.container.setAttribute('height', elementHeight * this.renderConfig.dpr); - } else { - elementWidth = this.canvasContext.canvas.width * this.renderConfig.dpr; - elementHeight = this.canvasContext.canvas.height * this.renderConfig.dpr; - } - var elementRel; - var animationRel; - if (this.renderConfig.preserveAspectRatio.indexOf('meet') !== -1 || this.renderConfig.preserveAspectRatio.indexOf('slice') !== -1) { - var par = this.renderConfig.preserveAspectRatio.split(' '); - var fillType = par[1] || 'meet'; - var pos = par[0] || 'xMidYMid'; - var xPos = pos.substr(0, 4); - var yPos = pos.substr(4); - elementRel = elementWidth / elementHeight; - animationRel = this.transformCanvas.w / this.transformCanvas.h; - if ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice')) { - this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); - this.transformCanvas.sy = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); - } else { - this.transformCanvas.sx = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); - this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); - } + // + // Nodejs and AMD support: export the implementation as a module using + // either convention. + // - if (xPos === 'xMid' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { - this.transformCanvas.tx = ((elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) / 2) * this.renderConfig.dpr; - } else if (xPos === 'xMax' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { - this.transformCanvas.tx = (elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) * this.renderConfig.dpr; - } else { - this.transformCanvas.tx = 0; - } - if (yPos === 'YMid' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { - this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w)) / 2) * this.renderConfig.dpr; - } else if (yPos === 'YMax' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { - this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w))) * this.renderConfig.dpr; - } else { - this.transformCanvas.ty = 0; - } - } else if (this.renderConfig.preserveAspectRatio === 'none') { - this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); - this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); - this.transformCanvas.tx = 0; - this.transformCanvas.ty = 0; - } else { - this.transformCanvas.sx = this.renderConfig.dpr; - this.transformCanvas.sy = this.renderConfig.dpr; - this.transformCanvas.tx = 0; - this.transformCanvas.ty = 0; - } - this.transformCanvas.props = [this.transformCanvas.sx, 0, 0, 0, 0, this.transformCanvas.sy, 0, 0, 0, 0, 1, 0, this.transformCanvas.tx, this.transformCanvas.ty, 0, 1]; - /* var i, len = this.elements.length; - for(i=0;i= 0; i -= 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - this.elements.length = 0; - this.globalData.canvasContext = null; - this.animationItem.container = null; - this.destroyed = true; -}; - -CanvasRendererBase.prototype.renderFrame = function (num, forceRender) { - if ((this.renderedFrame === num && this.renderConfig.clearCanvas === true && !forceRender) || this.destroyed || num === -1) { - return; - } - this.renderedFrame = num; - this.globalData.frameNum = num - this.animationItem._isFirstFrame; - this.globalData.frameId += 1; - this.globalData._mdf = !this.renderConfig.clearCanvas || forceRender; - this.globalData.projectInterface.currentFrame = num; - - // console.log('--------'); - // console.log('NEW: ',num); - var i; - var len = this.layers.length; - if (!this.completeLayers) { - this.checkLayers(num); - } - - for (i = 0; i < len; i += 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].prepareFrame(num - this.layers[i].st); - } - } - if (this.globalData._mdf) { - if (this.renderConfig.clearCanvas === true) { - this.canvasContext.clearRect(0, 0, this.transformCanvas.w, this.transformCanvas.h); - } else { - this.save(); - } - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } - if (this.renderConfig.clearCanvas !== true) { - this.restore(); - } - } -}; - -CanvasRendererBase.prototype.buildItem = function (pos) { - var elements = this.elements; - if (elements[pos] || this.layers[pos].ty === 99) { - return; - } - var element = this.createItem(this.layers[pos], this, this.globalData); - elements[pos] = element; - element.initExpressions(); - /* if(this.layers[pos].ty === 0){ - element.resize(this.globalData.transformCanvas); - } */ -}; - -CanvasRendererBase.prototype.checkPendingElements = function () { - while (this.pendingElements.length) { - var element = this.pendingElements.pop(); - element.checkParenting(); - } -}; - -CanvasRendererBase.prototype.hide = function () { - this.animationItem.container.style.display = 'none'; -}; - -CanvasRendererBase.prototype.show = function () { - this.animationItem.container.style.display = 'block'; -}; - -function CVCompElement(data, globalData, comp) { - this.completeLayers = false; - this.layers = data.layers; - this.pendingElements = []; - this.elements = createSizedArray(this.layers.length); - this.initElement(data, globalData, comp); - this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; -} - -extendPrototype([CanvasRendererBase, ICompElement, CVBaseElement], CVCompElement); - -CVCompElement.prototype.renderInnerContent = function () { - var ctx = this.canvasContext; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo(this.data.w, 0); - ctx.lineTo(this.data.w, this.data.h); - ctx.lineTo(0, this.data.h); - ctx.lineTo(0, 0); - ctx.clip(); - var i; - var len = this.layers.length; - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } -}; - -CVCompElement.prototype.destroy = function () { - var i; - var len = this.layers.length; - for (i = len - 1; i >= 0; i -= 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - this.layers = null; - this.elements = null; -}; - -CVCompElement.prototype.createComp = function (data) { - return new CVCompElement(data, this.globalData, this); -}; - -function CanvasRenderer(animationItem, config) { - this.animationItem = animationItem; - this.renderConfig = { - clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, - context: (config && config.context) || null, - progressiveLoad: (config && config.progressiveLoad) || false, - preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', - imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', - contentVisibility: (config && config.contentVisibility) || 'visible', - className: (config && config.className) || '', - id: (config && config.id) || '', - }; - this.renderConfig.dpr = (config && config.dpr) || 1; - if (this.animationItem.wrapper) { - this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; - } - this.renderedFrame = -1; - this.globalData = { - frameNum: -1, - _mdf: false, - renderConfig: this.renderConfig, - currentGlobalAlpha: -1, - }; - this.contextData = new CVContextData(); - this.elements = []; - this.pendingElements = []; - this.transformMat = new Matrix(); - this.completeLayers = false; - this.rendererType = 'canvas'; -} -extendPrototype([CanvasRendererBase], CanvasRenderer); - -CanvasRenderer.prototype.createComp = function (data) { - return new CVCompElement(data, this.globalData, this); -}; - -// Registering renderers -registerRenderer('canvas', CanvasRenderer); - -// Registering shape modifiers -ShapeModifiers.registerModifier('tm', TrimModifier); -ShapeModifiers.registerModifier('pb', PuckerAndBloatModifier); -ShapeModifiers.registerModifier('rp', RepeaterModifier); -ShapeModifiers.registerModifier('rd', RoundCornersModifier); - -const Expressions = (function () { - var ob = {}; - ob.initExpressions = initExpressions; - - function initExpressions(animation) { - var stackCount = 0; - var registers = []; - - function pushExpression() { - stackCount += 1; - } + // End anonymous scope, and pass initial values. + }; - function popExpression() { - stackCount -= 1; - if (stackCount === 0) { - releaseInstances(); - } + function initialize$2(BMMath) { + seedRandom([], BMMath); } - function registerExpressionProperty(expression) { - if (registers.indexOf(expression) === -1) { - registers.push(expression); - } - } + var propTypes = { + SHAPE: 'shape', + }; - function releaseInstances() { - var i; - var len = registers.length; - for (i = 0; i < len; i += 1) { - registers[i].release(); + /* eslint-disable camelcase */ + + const ExpressionManager = (function () { + 'use strict'; + + var ob = {}; + var Math = BMMath; + var window = null; + var document = null; + var XMLHttpRequest = null; + var fetch = null; + var frames = null; + initialize$2(BMMath); + + function $bm_isInstanceOfArray(arr) { + return arr.constructor === Array || arr.constructor === Float32Array; } - registers.length = 0; - } - animation.renderer.compInterface = CompExpressionInterface(animation.renderer); - animation.renderer.globalData.projectInterface.registerComposition(animation.renderer); - animation.renderer.globalData.pushExpression = pushExpression; - animation.renderer.globalData.popExpression = popExpression; - animation.renderer.globalData.registerExpressionProperty = registerExpressionProperty; - } - return ob; -}()); - -/* eslint-disable */ -/* - Copyright 2014 David Bau. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - */ - -function seedRandom(pool, math) { -// -// The following constants are related to IEEE 754 limits. -// - var global = this, - width = 256, // each RC4 output is 0 <= x < 256 - chunks = 6, // at least six RC4 outputs for each double - digits = 52, // there are 52 significant digits in a double - rngname = 'random', // rngname: name for Math.random and Math.seedrandom - startdenom = math.pow(width, chunks), - significance = math.pow(2, digits), - overflow = significance * 2, - mask = width - 1, - nodecrypto; // node.js crypto module, initialized at the bottom. - -// -// seedrandom() -// This is the seedrandom function described above. -// - function seedrandom(seed, options, callback) { - var key = []; - options = (options === true) ? { entropy: true } : (options || {}); - - // Flatten the seed string or build one from local entropy if needed. - var shortseed = mixkey(flatten( - options.entropy ? [seed, tostring(pool)] : - (seed === null) ? autoseed() : seed, 3), key); - - // Use the seed to initialize an ARC4 generator. - var arc4 = new ARC4(key); - - // This function returns a random double in [0, 1) that contains - // randomness in every bit of the mantissa of the IEEE 754 value. - var prng = function() { - var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48 - d = startdenom, // and denominator d = 2 ^ 48. - x = 0; // and no 'extra last byte'. - while (n < significance) { // Fill up all significant digits by - n = (n + x) * width; // shifting numerator and - d *= width; // denominator and generating a - x = arc4.g(1); // new least-significant-byte. - } - while (n >= overflow) { // To avoid rounding up, before adding - n /= 2; // last byte, shift everything - d /= 2; // right using integer math until - x >>>= 1; // we have exactly the desired bits. - } - return (n + x) / d; // Form the number within [0, 1). - }; + function isNumerable(tOfV, v) { + return tOfV === 'number' || tOfV === 'boolean' || tOfV === 'string' || v instanceof Number; + } - prng.int32 = function() { return arc4.g(4) | 0; }; - prng.quick = function() { return arc4.g(4) / 0x100000000; }; - prng.double = prng; - - // Mix the randomness into accumulated entropy. - mixkey(tostring(arc4.S), pool); - - // Calling convention: what to return as a function of prng, seed, is_math. - return (options.pass || callback || - function(prng, seed, is_math_call, state) { - if (state) { - // Load the arc4 state from the given state if it has an S array. - if (state.S) { copy(state, arc4); } - // Only provide the .state method if requested via options.state. - prng.state = function() { return copy(arc4, {}); }; - } - - // If called as a method of Math (Math.seedrandom()), mutate - // Math.random because that is how seedrandom.js has worked since v1.0. - if (is_math_call) { math[rngname] = prng; return seed; } - - // Otherwise, it is a newer calling convention, so return the - // prng directly. - else return prng; - })( - prng, - shortseed, - 'global' in options ? options.global : (this == math), - options.state); - } - math['seed' + rngname] = seedrandom; - -// -// ARC4 -// -// An ARC4 implementation. The constructor takes a key in the form of -// an array of at most (width) integers that should be 0 <= x < (width). -// -// The g(count) method returns a pseudorandom integer that concatenates -// the next (count) outputs from ARC4. Its return value is a number x -// that is in the range 0 <= x < (width ^ count). -// - function ARC4(key) { - var t, keylen = key.length, - me = this, i = 0, j = me.i = me.j = 0, s = me.S = []; - - // The empty key [] is treated as [0]. - if (!keylen) { key = [keylen++]; } - - // Set up S using the standard key scheduling algorithm. - while (i < width) { - s[i] = i++; - } - for (i = 0; i < width; i++) { - s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))]; - s[j] = t; - } - - // The "g" method returns the next (count) outputs as one number. - me.g = function(count) { - // Using instance members instead of closure state nearly doubles speed. - var t, r = 0, - i = me.i, j = me.j, s = me.S; - while (count--) { - t = s[i = mask & (i + 1)]; - r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))]; - } - me.i = i; me.j = j; - return r; - // For robust unpredictability, the function call below automatically - // discards an initial batch of values. This is called RC4-drop[256]. - // See http://google.com/search?q=rsa+fluhrer+response&btnI - }; - } + function $bm_neg(a) { + var tOfA = typeof a; + if (tOfA === 'number' || tOfA === 'boolean' || a instanceof Number) { + return -a; + } + if ($bm_isInstanceOfArray(a)) { + var i; + var lenA = a.length; + var retArr = []; + for (i = 0; i < lenA; i += 1) { + retArr[i] = -a[i]; + } + return retArr; + } + if (a.propType) { + return a.v; + } + return -a; + } -// -// copy() -// Copies internal state of ARC4 to or from a plain object. -// - function copy(f, t) { - t.i = f.i; - t.j = f.j; - t.S = f.S.slice(); - return t; - } + var easeInBez = BezierFactory.getBezierEasing(0.333, 0, 0.833, 0.833, 'easeIn').get; + var easeOutBez = BezierFactory.getBezierEasing(0.167, 0.167, 0.667, 1, 'easeOut').get; + var easeInOutBez = BezierFactory.getBezierEasing(0.33, 0, 0.667, 1, 'easeInOut').get; -// -// flatten() -// Converts an object tree to nested arrays of strings. -// - function flatten(obj, depth) { - var result = [], typ = (typeof obj), prop; - if (depth && typ == 'object') { - for (prop in obj) { - try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} + function sum(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + if (tOfA === 'string' || tOfB === 'string') { + return a + b; + } + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + return a + b; + } + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + a = a.slice(0); + a[0] += b; + return a; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + b = b.slice(0); + b[0] = a + b[0]; + return b; + } + if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { + var i = 0; + var lenA = a.length; + var lenB = b.length; + var retArr = []; + while (i < lenA || i < lenB) { + if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { + retArr[i] = a[i] + b[i]; + } else { + retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; } + i += 1; + } + return retArr; } - return (result.length ? result : typ == 'string' ? obj : obj + '\0'); - } - -// -// mixkey() -// Mixes a string seed into a key that is an array of integers, and -// returns a shortened string seed that is equivalent to the result key. -// - function mixkey(seed, key) { - var stringseed = seed + '', smear, j = 0; - while (j < stringseed.length) { - key[mask & j] = - mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++)); - } - return tostring(key); - } + return 0; + } + var add = sum; -// -// autoseed() -// Returns an object for autoseeding, using window.crypto and Node crypto -// module if available. -// - function autoseed() { - try { - if (nodecrypto) { return tostring(nodecrypto.randomBytes(width)); } - var out = new Uint8Array(width); - (global.crypto || global.msCrypto).getRandomValues(out); - return tostring(out); - } catch (e) { - var browser = global.navigator, - plugins = browser && browser.plugins; - return [+new Date(), global, plugins, global.screen, tostring(pool)]; + function sub(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + if (tOfA === 'string') { + a = parseInt(a, 10); + } + if (tOfB === 'string') { + b = parseInt(b, 10); + } + return a - b; + } + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + a = a.slice(0); + a[0] -= b; + return a; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + b = b.slice(0); + b[0] = a - b[0]; + return b; + } + if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { + var i = 0; + var lenA = a.length; + var lenB = b.length; + var retArr = []; + while (i < lenA || i < lenB) { + if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { + retArr[i] = a[i] - b[i]; + } else { + retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; + } + i += 1; + } + return retArr; } - } + return 0; + } -// -// tostring() -// Converts an array of charcodes to a string -// - function tostring(a) { - return String.fromCharCode.apply(0, a); - } + function mul(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + var arr; + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + return a * b; + } -// -// When seedrandom.js is loaded, we immediately mix a few bits -// from the built-in RNG into the entropy pool. Because we do -// not want to interfere with deterministic PRNG state later, -// seedrandom will not call math.random on its own again after -// initialization. -// - mixkey(math.random(), pool); - -// -// Nodejs and AMD support: export the implementation as a module using -// either convention. -// - -// End anonymous scope, and pass initial values. -}; - -function initialize$2(BMMath) { - seedRandom([], BMMath); -} - -var propTypes = { - SHAPE: 'shape', -}; - -/* eslint-disable camelcase */ - -const ExpressionManager = (function () { - 'use strict'; - - var ob = {}; - var Math = BMMath; - var window = null; - var document = null; - var XMLHttpRequest = null; - var fetch = null; - var frames = null; - initialize$2(BMMath); - - function $bm_isInstanceOfArray(arr) { - return arr.constructor === Array || arr.constructor === Float32Array; - } - - function isNumerable(tOfV, v) { - return tOfV === 'number' || tOfV === 'boolean' || tOfV === 'string' || v instanceof Number; - } - - function $bm_neg(a) { - var tOfA = typeof a; - if (tOfA === 'number' || tOfA === 'boolean' || a instanceof Number) { - return -a; - } - if ($bm_isInstanceOfArray(a)) { - var i; - var lenA = a.length; - var retArr = []; - for (i = 0; i < lenA; i += 1) { - retArr[i] = -a[i]; - } - return retArr; - } - if (a.propType) { - return a.v; - } - return -a; - } - - var easeInBez = BezierFactory.getBezierEasing(0.333, 0, 0.833, 0.833, 'easeIn').get; - var easeOutBez = BezierFactory.getBezierEasing(0.167, 0.167, 0.667, 1, 'easeOut').get; - var easeInOutBez = BezierFactory.getBezierEasing(0.33, 0, 0.667, 1, 'easeInOut').get; - - function sum(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - if (tOfA === 'string' || tOfB === 'string') { - return a + b; - } - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - return a + b; - } - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - a = a.slice(0); - a[0] += b; - return a; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - b = b.slice(0); - b[0] = a + b[0]; - return b; - } - if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { - var i = 0; - var lenA = a.length; - var lenB = b.length; - var retArr = []; - while (i < lenA || i < lenB) { - if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { - retArr[i] = a[i] + b[i]; - } else { - retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; + var i; + var len; + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + len = a.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a[i] * b; + } + return arr; } - i += 1; + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + len = b.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a * b[i]; + } + return arr; + } + return 0; } - return retArr; - } - return 0; - } - var add = sum; - function sub(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - if (tOfA === 'string') { - a = parseInt(a, 10); + function div(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + var arr; + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + return a / b; + } + var i; + var len; + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + len = a.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a[i] / b; + } + return arr; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + len = b.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a / b[i]; + } + return arr; + } + return 0; } - if (tOfB === 'string') { - b = parseInt(b, 10); + function mod(a, b) { + if (typeof a === 'string') { + a = parseInt(a, 10); + } + if (typeof b === 'string') { + b = parseInt(b, 10); + } + return a % b; } - return a - b; - } - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - a = a.slice(0); - a[0] -= b; - return a; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - b = b.slice(0); - b[0] = a - b[0]; - return b; - } - if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { - var i = 0; - var lenA = a.length; - var lenB = b.length; - var retArr = []; - while (i < lenA || i < lenB) { - if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { - retArr[i] = a[i] - b[i]; - } else { - retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; + var $bm_sum = sum; + var $bm_sub = sub; + var $bm_mul = mul; + var $bm_div = div; + var $bm_mod = mod; + + function clamp(num, min, max) { + if (min > max) { + var mm = max; + max = min; + min = mm; } - i += 1; + return Math.min(Math.max(num, min), max); } - return retArr; - } - return 0; - } - - function mul(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - var arr; - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - return a * b; - } - var i; - var len; - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - len = a.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a[i] * b; + function radiansToDegrees(val) { + return val / degToRads; } - return arr; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - len = b.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a * b[i]; + var radians_to_degrees = radiansToDegrees; + + function degreesToRadians(val) { + return val * degToRads; } - return arr; - } - return 0; - } - - function div(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - var arr; - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - return a / b; - } - var i; - var len; - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - len = a.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a[i] / b; + var degrees_to_radians = radiansToDegrees; + + var helperLengthArray = [0, 0, 0, 0, 0, 0]; + + function length(arr1, arr2) { + if (typeof arr1 === 'number' || arr1 instanceof Number) { + arr2 = arr2 || 0; + return Math.abs(arr1 - arr2); + } + if (!arr2) { + arr2 = helperLengthArray; + } + var i; + var len = Math.min(arr1.length, arr2.length); + var addedLength = 0; + for (i = 0; i < len; i += 1) { + addedLength += Math.pow(arr2[i] - arr1[i], 2); + } + return Math.sqrt(addedLength); } - return arr; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - len = b.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a / b[i]; + + function normalize(vec) { + return div(vec, length(vec)); } - return arr; - } - return 0; - } - function mod(a, b) { - if (typeof a === 'string') { - a = parseInt(a, 10); - } - if (typeof b === 'string') { - b = parseInt(b, 10); - } - return a % b; - } - var $bm_sum = sum; - var $bm_sub = sub; - var $bm_mul = mul; - var $bm_div = div; - var $bm_mod = mod; - - function clamp(num, min, max) { - if (min > max) { - var mm = max; - max = min; - min = mm; - } - return Math.min(Math.max(num, min), max); - } - function radiansToDegrees(val) { - return val / degToRads; - } - var radians_to_degrees = radiansToDegrees; + function rgbToHsl(val) { + var r = val[0]; var g = val[1]; var b = val[2]; + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var h; + var s; + var l = (max + min) / 2; - function degreesToRadians(val) { - return val * degToRads; - } - var degrees_to_radians = radiansToDegrees; + if (max === min) { + h = 0; // achromatic + s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + default: break; + } + h /= 6; + } - var helperLengthArray = [0, 0, 0, 0, 0, 0]; + return [h, s, l, val[3]]; + } - function length(arr1, arr2) { - if (typeof arr1 === 'number' || arr1 instanceof Number) { - arr2 = arr2 || 0; - return Math.abs(arr1 - arr2); - } - if (!arr2) { - arr2 = helperLengthArray; - } - var i; - var len = Math.min(arr1.length, arr2.length); - var addedLength = 0; - for (i = 0; i < len; i += 1) { - addedLength += Math.pow(arr2[i] - arr1[i], 2); - } - return Math.sqrt(addedLength); - } - - function normalize(vec) { - return div(vec, length(vec)); - } - - function rgbToHsl(val) { - var r = val[0]; var g = val[1]; var b = val[2]; - var max = Math.max(r, g, b); - var min = Math.min(r, g, b); - var h; - var s; - var l = (max + min) / 2; - - if (max === min) { - h = 0; // achromatic - s = 0; // achromatic - } else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - default: break; + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; } - h /= 6; - } - return [h, s, l, val[3]]; - } - - function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - } - - function hslToRgb(val) { - var h = val[0]; - var s = val[1]; - var l = val[2]; - - var r; - var g; - var b; - - if (s === 0) { - r = l; // achromatic - b = l; // achromatic - g = l; // achromatic - } else { - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } + function hslToRgb(val) { + var h = val[0]; + var s = val[1]; + var l = val[2]; - return [r, g, b, val[3]]; - } + var r; + var g; + var b; - function linear(t, tMin, tMax, value1, value2) { - if (value1 === undefined || value2 === undefined) { - value1 = tMin; - value2 = tMax; - tMin = 0; - tMax = 1; - } - if (tMax < tMin) { - var _tMin = tMax; - tMax = tMin; - tMin = _tMin; - } - if (t <= tMin) { - return value1; - } if (t >= tMax) { - return value2; - } - var perc = tMax === tMin ? 0 : (t - tMin) / (tMax - tMin); - if (!value1.length) { - return value1 + (value2 - value1) * perc; - } - var i; - var len = value1.length; - var arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = value1[i] + (value2[i] - value1[i]) * perc; - } - return arr; - } - function random(min, max) { - if (max === undefined) { - if (min === undefined) { - min = 0; - max = 1; - } else { - max = min; - min = undefined; - } - } - if (max.length) { - var i; - var len = max.length; - if (!min) { - min = createTypedArray('float32', len); - } - var arr = createTypedArray('float32', len); - var rnd = BMMath.random(); - for (i = 0; i < len; i += 1) { - arr[i] = min[i] + rnd * (max[i] - min[i]); + if (s === 0) { + r = l; // achromatic + b = l; // achromatic + g = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r, g, b, val[3]]; } - return arr; - } - if (min === undefined) { - min = 0; - } - var rndm = BMMath.random(); - return min + rndm * (max - min); - } - - function createPath(points, inTangents, outTangents, closed) { - var i; - var len = points.length; - var path = shapePool.newElement(); - path.setPathData(!!closed, len); - var arrPlaceholder = [0, 0]; - var inVertexPoint; - var outVertexPoint; - for (i = 0; i < len; i += 1) { - inVertexPoint = (inTangents && inTangents[i]) ? inTangents[i] : arrPlaceholder; - outVertexPoint = (outTangents && outTangents[i]) ? outTangents[i] : arrPlaceholder; - path.setTripleAt(points[i][0], points[i][1], outVertexPoint[0] + points[i][0], outVertexPoint[1] + points[i][1], inVertexPoint[0] + points[i][0], inVertexPoint[1] + points[i][1], i, true); - } - return path; - } - - function initiateExpression(elem, data, property) { - var val = data.x; - var needsVelocity = /velocity(?![\w\d])/.test(val); - var _needsRandom = val.indexOf('random') !== -1; - var elemType = elem.data.ty; - var transform; - var $bm_transform; - var content; - var effect; - var thisProperty = property; - thisProperty.valueAtTime = thisProperty.getValueAtTime; - Object.defineProperty(thisProperty, 'value', { - get: function () { - return thisProperty.v; - }, - }); - elem.comp.frameDuration = 1 / elem.comp.globalData.frameRate; - elem.comp.displayStartTime = 0; - var inPoint = elem.data.ip / elem.comp.globalData.frameRate; - var outPoint = elem.data.op / elem.comp.globalData.frameRate; - var width = elem.data.sw ? elem.data.sw : 0; - var height = elem.data.sh ? elem.data.sh : 0; - var name = elem.data.nm; - var loopIn; - var loop_in; - var loopOut; - var loop_out; - var smooth; - var toWorld; - var fromWorld; - var fromComp; - var toComp; - var fromCompToSurface; - var position; - var rotation; - var anchorPoint; - var scale; - var thisLayer; - var thisComp; - var mask; - var valueAtTime; - var velocityAtTime; - - var scoped_bm_rt; - // val = val.replace(/(\\?"|')((http)(s)?(:\/))?\/.*?(\\?"|')/g, "\"\""); // deter potential network calls - var expression_function = eval('[function _expression_function(){' + val + ';scoped_bm_rt=$bm_rt}]')[0]; // eslint-disable-line no-eval - var numKeys = property.kf ? data.k.length : 0; - - var active = !this.data || this.data.hd !== true; - - var wiggle = function wiggle(freq, amp) { - var iWiggle; - var j; - var lenWiggle = this.pv.length ? this.pv.length : 1; - var addedAmps = createTypedArray('float32', lenWiggle); - freq = 5; - var iterations = Math.floor(time * freq); - iWiggle = 0; - j = 0; - while (iWiggle < iterations) { - // var rnd = BMMath.random(); - for (j = 0; j < lenWiggle; j += 1) { - addedAmps[j] += -amp + amp * 2 * BMMath.random(); - // addedAmps[j] += -amp + amp*2*rnd; - } - iWiggle += 1; - } - // var rnd2 = BMMath.random(); - var periods = time * freq; - var perc = periods - Math.floor(periods); - var arr = createTypedArray('float32', lenWiggle); - if (lenWiggle > 1) { - for (j = 0; j < lenWiggle; j += 1) { - arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp * 2 * BMMath.random()) * perc; - // arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp*2*rnd)*perc; - // arr[i] = this.pv[i] + addedAmp + amp1*perc + amp2*(1-perc); + + function linear(t, tMin, tMax, value1, value2) { + if (value1 === undefined || value2 === undefined) { + value1 = tMin; + value2 = tMax; + tMin = 0; + tMax = 1; + } + if (tMax < tMin) { + var _tMin = tMax; + tMax = tMin; + tMin = _tMin; + } + if (t <= tMin) { + return value1; + } if (t >= tMax) { + return value2; + } + var perc = tMax === tMin ? 0 : (t - tMin) / (tMax - tMin); + if (!value1.length) { + return value1 + (value2 - value1) * perc; + } + var i; + var len = value1.length; + var arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = value1[i] + (value2[i] - value1[i]) * perc; } return arr; } - return this.pv + addedAmps[0] + (-amp + amp * 2 * BMMath.random()) * perc; - }.bind(this); + function random(min, max) { + if (max === undefined) { + if (min === undefined) { + min = 0; + max = 1; + } else { + max = min; + min = undefined; + } + } + if (max.length) { + var i; + var len = max.length; + if (!min) { + min = createTypedArray('float32', len); + } + var arr = createTypedArray('float32', len); + var rnd = BMMath.random(); + for (i = 0; i < len; i += 1) { + arr[i] = min[i] + rnd * (max[i] - min[i]); + } + return arr; + } + if (min === undefined) { + min = 0; + } + var rndm = BMMath.random(); + return min + rndm * (max - min); + } - if (thisProperty.loopIn) { - loopIn = thisProperty.loopIn.bind(thisProperty); - loop_in = loopIn; - } + function createPath(points, inTangents, outTangents, closed) { + var i; + var len = points.length; + var path = shapePool.newElement(); + path.setPathData(!!closed, len); + var arrPlaceholder = [0, 0]; + var inVertexPoint; + var outVertexPoint; + for (i = 0; i < len; i += 1) { + inVertexPoint = (inTangents && inTangents[i]) ? inTangents[i] : arrPlaceholder; + outVertexPoint = (outTangents && outTangents[i]) ? outTangents[i] : arrPlaceholder; + path.setTripleAt(points[i][0], points[i][1], outVertexPoint[0] + points[i][0], outVertexPoint[1] + points[i][1], inVertexPoint[0] + points[i][0], inVertexPoint[1] + points[i][1], i, true); + } + return path; + } + + function initiateExpression(elem, data, property) { + var val = data.x; + var needsVelocity = /velocity(?![\w\d])/.test(val); + var _needsRandom = val.indexOf('random') !== -1; + var elemType = elem.data.ty; + var transform; + var $bm_transform; + var content; + var effect; + var thisProperty = property; + thisProperty.valueAtTime = thisProperty.getValueAtTime; + Object.defineProperty(thisProperty, 'value', { + get: function () { + return thisProperty.v; + }, + }); + elem.comp.frameDuration = 1 / elem.comp.globalData.frameRate; + elem.comp.displayStartTime = 0; + var inPoint = elem.data.ip / elem.comp.globalData.frameRate; + var outPoint = elem.data.op / elem.comp.globalData.frameRate; + var width = elem.data.sw ? elem.data.sw : 0; + var height = elem.data.sh ? elem.data.sh : 0; + var name = elem.data.nm; + var loopIn; + var loop_in; + var loopOut; + var loop_out; + var smooth; + var toWorld; + var fromWorld; + var fromComp; + var toComp; + var fromCompToSurface; + var position; + var rotation; + var anchorPoint; + var scale; + var thisLayer; + var thisComp; + var mask; + var valueAtTime; + var velocityAtTime; + + var scoped_bm_rt; + // val = val.replace(/(\\?"|')((http)(s)?(:\/))?\/.*?(\\?"|')/g, "\"\""); // deter potential network calls + var expression_function = eval('[function _expression_function(){' + val + ';scoped_bm_rt=$bm_rt}]')[0]; // eslint-disable-line no-eval + var numKeys = property.kf ? data.k.length : 0; + + var active = !this.data || this.data.hd !== true; + + var wiggle = function wiggle(freq, amp) { + var iWiggle; + var j; + var lenWiggle = this.pv.length ? this.pv.length : 1; + var addedAmps = createTypedArray('float32', lenWiggle); + freq = 5; + var iterations = Math.floor(time * freq); + iWiggle = 0; + j = 0; + while (iWiggle < iterations) { + // var rnd = BMMath.random(); + for (j = 0; j < lenWiggle; j += 1) { + addedAmps[j] += -amp + amp * 2 * BMMath.random(); + // addedAmps[j] += -amp + amp*2*rnd; + } + iWiggle += 1; + } + // var rnd2 = BMMath.random(); + var periods = time * freq; + var perc = periods - Math.floor(periods); + var arr = createTypedArray('float32', lenWiggle); + if (lenWiggle > 1) { + for (j = 0; j < lenWiggle; j += 1) { + arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp * 2 * BMMath.random()) * perc; + // arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp*2*rnd)*perc; + // arr[i] = this.pv[i] + addedAmp + amp1*perc + amp2*(1-perc); + } + return arr; + } + return this.pv + addedAmps[0] + (-amp + amp * 2 * BMMath.random()) * perc; + }.bind(this); + + if (thisProperty.loopIn) { + loopIn = thisProperty.loopIn.bind(thisProperty); + loop_in = loopIn; + } - if (thisProperty.loopOut) { - loopOut = thisProperty.loopOut.bind(thisProperty); - loop_out = loopOut; - } + if (thisProperty.loopOut) { + loopOut = thisProperty.loopOut.bind(thisProperty); + loop_out = loopOut; + } - if (thisProperty.smooth) { - smooth = thisProperty.smooth.bind(thisProperty); - } + if (thisProperty.smooth) { + smooth = thisProperty.smooth.bind(thisProperty); + } - function loopInDuration(type, duration) { - return loopIn(type, duration, true); - } + function loopInDuration(type, duration) { + return loopIn(type, duration, true); + } - function loopOutDuration(type, duration) { - return loopOut(type, duration, true); - } + function loopOutDuration(type, duration) { + return loopOut(type, duration, true); + } - if (this.getValueAtTime) { - valueAtTime = this.getValueAtTime.bind(this); - } + if (this.getValueAtTime) { + valueAtTime = this.getValueAtTime.bind(this); + } - if (this.getVelocityAtTime) { - velocityAtTime = this.getVelocityAtTime.bind(this); - } + if (this.getVelocityAtTime) { + velocityAtTime = this.getVelocityAtTime.bind(this); + } - var comp = elem.comp.globalData.projectInterface.bind(elem.comp.globalData.projectInterface); + var comp = elem.comp.globalData.projectInterface.bind(elem.comp.globalData.projectInterface); - function lookAt(elem1, elem2) { - var fVec = [elem2[0] - elem1[0], elem2[1] - elem1[1], elem2[2] - elem1[2]]; - var pitch = Math.atan2(fVec[0], Math.sqrt(fVec[1] * fVec[1] + fVec[2] * fVec[2])) / degToRads; - var yaw = -Math.atan2(fVec[1], fVec[2]) / degToRads; - return [yaw, pitch, 0]; - } + function lookAt(elem1, elem2) { + var fVec = [elem2[0] - elem1[0], elem2[1] - elem1[1], elem2[2] - elem1[2]]; + var pitch = Math.atan2(fVec[0], Math.sqrt(fVec[1] * fVec[1] + fVec[2] * fVec[2])) / degToRads; + var yaw = -Math.atan2(fVec[1], fVec[2]) / degToRads; + return [yaw, pitch, 0]; + } - function easeOut(t, tMin, tMax, val1, val2) { - return applyEase(easeOutBez, t, tMin, tMax, val1, val2); - } + function easeOut(t, tMin, tMax, val1, val2) { + return applyEase(easeOutBez, t, tMin, tMax, val1, val2); + } - function easeIn(t, tMin, tMax, val1, val2) { - return applyEase(easeInBez, t, tMin, tMax, val1, val2); - } + function easeIn(t, tMin, tMax, val1, val2) { + return applyEase(easeInBez, t, tMin, tMax, val1, val2); + } - function ease(t, tMin, tMax, val1, val2) { - return applyEase(easeInOutBez, t, tMin, tMax, val1, val2); - } + function ease(t, tMin, tMax, val1, val2) { + return applyEase(easeInOutBez, t, tMin, tMax, val1, val2); + } - function applyEase(fn, t, tMin, tMax, val1, val2) { - if (val1 === undefined) { - val1 = tMin; - val2 = tMax; - } else { - t = (t - tMin) / (tMax - tMin); - } - if (t > 1) { - t = 1; - } else if (t < 0) { - t = 0; - } - var mult = fn(t); - if ($bm_isInstanceOfArray(val1)) { - var iKey; - var lenKey = val1.length; - var arr = createTypedArray('float32', lenKey); - for (iKey = 0; iKey < lenKey; iKey += 1) { - arr[iKey] = (val2[iKey] - val1[iKey]) * mult + val1[iKey]; + function applyEase(fn, t, tMin, tMax, val1, val2) { + if (val1 === undefined) { + val1 = tMin; + val2 = tMax; + } else { + t = (t - tMin) / (tMax - tMin); + } + if (t > 1) { + t = 1; + } else if (t < 0) { + t = 0; + } + var mult = fn(t); + if ($bm_isInstanceOfArray(val1)) { + var iKey; + var lenKey = val1.length; + var arr = createTypedArray('float32', lenKey); + for (iKey = 0; iKey < lenKey; iKey += 1) { + arr[iKey] = (val2[iKey] - val1[iKey]) * mult + val1[iKey]; + } + return arr; + } + return (val2 - val1) * mult + val1; } - return arr; - } - return (val2 - val1) * mult + val1; - } - function nearestKey(time) { - var iKey; - var lenKey = data.k.length; - var index; - var keyTime; - if (!data.k.length || typeof (data.k[0]) === 'number') { - index = 0; - keyTime = 0; - } else { - index = -1; - time *= elem.comp.globalData.frameRate; - if (time < data.k[0].t) { - index = 1; - keyTime = data.k[0].t; - } else { - for (iKey = 0; iKey < lenKey - 1; iKey += 1) { - if (time === data.k[iKey].t) { - index = iKey + 1; - keyTime = data.k[iKey].t; - break; - } else if (time > data.k[iKey].t && time < data.k[iKey + 1].t) { - if (time - data.k[iKey].t > data.k[iKey + 1].t - time) { - index = iKey + 2; - keyTime = data.k[iKey + 1].t; - } else { + function nearestKey(time) { + var iKey; + var lenKey = data.k.length; + var index; + var keyTime; + if (!data.k.length || typeof (data.k[0]) === 'number') { + index = 0; + keyTime = 0; + } else { + index = -1; + time *= elem.comp.globalData.frameRate; + if (time < data.k[0].t) { + index = 1; + keyTime = data.k[0].t; + } else { + for (iKey = 0; iKey < lenKey - 1; iKey += 1) { + if (time === data.k[iKey].t) { + index = iKey + 1; + keyTime = data.k[iKey].t; + break; + } else if (time > data.k[iKey].t && time < data.k[iKey + 1].t) { + if (time - data.k[iKey].t > data.k[iKey + 1].t - time) { + index = iKey + 2; + keyTime = data.k[iKey + 1].t; + } else { + index = iKey + 1; + keyTime = data.k[iKey].t; + } + break; + } + } + if (index === -1) { index = iKey + 1; keyTime = data.k[iKey].t; } - break; } } - if (index === -1) { - index = iKey + 1; - keyTime = data.k[iKey].t; + var obKey = {}; + obKey.index = index; + obKey.time = keyTime / elem.comp.globalData.frameRate; + return obKey; + } + + function key(ind) { + var obKey; + var iKey; + var lenKey; + if (!data.k.length || typeof (data.k[0]) === 'number') { + throw new Error('The property has no keyframe at index ' + ind); } + ind -= 1; + obKey = { + time: data.k[ind].t / elem.comp.globalData.frameRate, + value: [], + }; + var arr = Object.prototype.hasOwnProperty.call(data.k[ind], 's') ? data.k[ind].s : data.k[ind - 1].e; + + lenKey = arr.length; + for (iKey = 0; iKey < lenKey; iKey += 1) { + obKey[iKey] = arr[iKey]; + obKey.value[iKey] = arr[iKey]; + } + return obKey; } - } - var obKey = {}; - obKey.index = index; - obKey.time = keyTime / elem.comp.globalData.frameRate; - return obKey; - } - function key(ind) { - var obKey; - var iKey; - var lenKey; - if (!data.k.length || typeof (data.k[0]) === 'number') { - throw new Error('The property has no keyframe at index ' + ind); - } - ind -= 1; - obKey = { - time: data.k[ind].t / elem.comp.globalData.frameRate, - value: [], - }; - var arr = Object.prototype.hasOwnProperty.call(data.k[ind], 's') ? data.k[ind].s : data.k[ind - 1].e; + function framesToTime(fr, fps) { + if (!fps) { + fps = elem.comp.globalData.frameRate; + } + return fr / fps; + } - lenKey = arr.length; - for (iKey = 0; iKey < lenKey; iKey += 1) { - obKey[iKey] = arr[iKey]; - obKey.value[iKey] = arr[iKey]; - } - return obKey; - } + function timeToFrames(t, fps) { + if (!t && t !== 0) { + t = time; + } + if (!fps) { + fps = elem.comp.globalData.frameRate; + } + return t * fps; + } - function framesToTime(fr, fps) { - if (!fps) { - fps = elem.comp.globalData.frameRate; - } - return fr / fps; - } + function seedRandom(seed) { + BMMath.seedrandom(randSeed + seed); + } - function timeToFrames(t, fps) { - if (!t && t !== 0) { - t = time; - } - if (!fps) { - fps = elem.comp.globalData.frameRate; - } - return t * fps; - } + function sourceRectAtTime() { + return elem.sourceRectAtTime(); + } - function seedRandom(seed) { - BMMath.seedrandom(randSeed + seed); - } + function substring(init, end) { + if (typeof value === 'string') { + if (end === undefined) { + return value.substring(init); + } + return value.substring(init, end); + } + return ''; + } - function sourceRectAtTime() { - return elem.sourceRectAtTime(); - } + function substr(init, end) { + if (typeof value === 'string') { + if (end === undefined) { + return value.substr(init); + } + return value.substr(init, end); + } + return ''; + } + + function posterizeTime(framesPerSecond) { + time = framesPerSecond === 0 ? 0 : Math.floor(time * framesPerSecond) / framesPerSecond; + value = valueAtTime(time); + } + + var time; + var velocity; + var value; + var text; + var textIndex; + var textTotal; + var selectorValue; + var index = elem.data.ind; + var hasParent = !!(elem.hierarchy && elem.hierarchy.length); + var parent; + var randSeed = Math.floor(Math.random() * 1000000); + var globalData = elem.globalData; + function executeExpression(_value) { + // globalData.pushExpression(); + value = _value; + if (this.frameExpressionId === elem.globalData.frameId && this.propType !== 'textSelector') { + return value; + } + if (this.propType === 'textSelector') { + textIndex = this.textIndex; + textTotal = this.textTotal; + selectorValue = this.selectorValue; + } + if (!thisLayer) { + text = elem.layerInterface.text; + thisLayer = elem.layerInterface; + thisComp = elem.comp.compInterface; + toWorld = thisLayer.toWorld.bind(thisLayer); + fromWorld = thisLayer.fromWorld.bind(thisLayer); + fromComp = thisLayer.fromComp.bind(thisLayer); + toComp = thisLayer.toComp.bind(thisLayer); + mask = thisLayer.mask ? thisLayer.mask.bind(thisLayer) : null; + fromCompToSurface = fromComp; + } + if (!transform) { + transform = elem.layerInterface('ADBE Transform Group'); + $bm_transform = transform; + if (transform) { + anchorPoint = transform.anchorPoint; + /* position = transform.position; + rotation = transform.rotation; + scale = transform.scale; */ + } + } - function substring(init, end) { - if (typeof value === 'string') { - if (end === undefined) { - return value.substring(init); + if (elemType === 4 && !content) { + content = thisLayer('ADBE Root Vectors Group'); + } + if (!effect) { + effect = thisLayer(4); + } + hasParent = !!(elem.hierarchy && elem.hierarchy.length); + if (hasParent && !parent) { + parent = elem.hierarchy[0].layerInterface; + } + time = this.comp.renderedFrame / this.comp.globalData.frameRate; + if (_needsRandom) { + seedRandom(randSeed + time); + } + if (needsVelocity) { + velocity = velocityAtTime(time); + } + expression_function(); + this.frameExpressionId = elem.globalData.frameId; + + // TODO: Check if it's possible to return on ShapeInterface the .v value + // Changed this to a ternary operation because Rollup failed compiling it correctly + scoped_bm_rt = scoped_bm_rt.propType === propTypes.SHAPE + ? scoped_bm_rt.v + : scoped_bm_rt; + return scoped_bm_rt; + } + // Bundlers will see these as dead code and unless we reference them + executeExpression.__preventDeadCodeRemoval = [$bm_transform, anchorPoint, time, velocity, inPoint, outPoint, width, height, name, loop_in, loop_out, smooth, toComp, fromCompToSurface, toWorld, fromWorld, mask, position, rotation, scale, thisComp, numKeys, active, wiggle, loopInDuration, loopOutDuration, comp, lookAt, easeOut, easeIn, ease, nearestKey, key, text, textIndex, textTotal, selectorValue, framesToTime, timeToFrames, sourceRectAtTime, substring, substr, posterizeTime, index, globalData]; + return executeExpression; + } + + ob.initiateExpression = initiateExpression; + ob.__preventDeadCodeRemoval = [window, document, XMLHttpRequest, fetch, frames, $bm_neg, add, $bm_sum, $bm_sub, $bm_mul, $bm_div, $bm_mod, clamp, radians_to_degrees, degreesToRadians, degrees_to_radians, normalize, rgbToHsl, hslToRgb, linear, random, createPath]; + return ob; + }()); + + const expressionHelpers = (function () { + function searchExpressions(elem, data, prop) { + if (data.x) { + prop.k = true; + prop.x = true; + prop.initiateExpression = ExpressionManager.initiateExpression; + prop.effectsSequence.push(prop.initiateExpression(elem, data, prop).bind(prop)); + } + } + + function getValueAtTime(frameNum) { + frameNum *= this.elem.globalData.frameRate; + frameNum -= this.offsetTime; + if (frameNum !== this._cachingAtTime.lastFrame) { + this._cachingAtTime.lastIndex = this._cachingAtTime.lastFrame < frameNum ? this._cachingAtTime.lastIndex : 0; + this._cachingAtTime.value = this.interpolateValue(frameNum, this._cachingAtTime); + this._cachingAtTime.lastFrame = frameNum; + } + return this._cachingAtTime.value; + } + + function getSpeedAtTime(frameNum) { + var delta = -0.01; + var v1 = this.getValueAtTime(frameNum); + var v2 = this.getValueAtTime(frameNum + delta); + var speed = 0; + if (v1.length) { + var i; + for (i = 0; i < v1.length; i += 1) { + speed += Math.pow(v2[i] - v1[i], 2); + } + speed = Math.sqrt(speed) * 100; + } else { + speed = 0; + } + return speed; + } + + function getVelocityAtTime(frameNum) { + if (this.vel !== undefined) { + return this.vel; + } + var delta = -0.001; + // frameNum += this.elem.data.st; + var v1 = this.getValueAtTime(frameNum); + var v2 = this.getValueAtTime(frameNum + delta); + var velocity; + if (v1.length) { + velocity = createTypedArray('float32', v1.length); + var i; + for (i = 0; i < v1.length; i += 1) { + // removing frameRate + // if needed, don't add it here + // velocity[i] = this.elem.globalData.frameRate*((v2[i] - v1[i])/delta); + velocity[i] = (v2[i] - v1[i]) / delta; + } + } else { + velocity = (v2 - v1) / delta; } - return value.substring(init, end); + return velocity; } - return ''; - } - function substr(init, end) { - if (typeof value === 'string') { - if (end === undefined) { - return value.substr(init); - } - return value.substr(init, end); + function getStaticValueAtTime() { + return this.pv; } - return ''; - } - - function posterizeTime(framesPerSecond) { - time = framesPerSecond === 0 ? 0 : Math.floor(time * framesPerSecond) / framesPerSecond; - value = valueAtTime(time); - } - var time; - var velocity; - var value; - var text; - var textIndex; - var textTotal; - var selectorValue; - var index = elem.data.ind; - var hasParent = !!(elem.hierarchy && elem.hierarchy.length); - var parent; - var randSeed = Math.floor(Math.random() * 1000000); - var globalData = elem.globalData; - function executeExpression(_value) { - // globalData.pushExpression(); - value = _value; - if (this.frameExpressionId === elem.globalData.frameId && this.propType !== 'textSelector') { - return value; - } - if (this.propType === 'textSelector') { - textIndex = this.textIndex; - textTotal = this.textTotal; - selectorValue = this.selectorValue; - } - if (!thisLayer) { - text = elem.layerInterface.text; - thisLayer = elem.layerInterface; - thisComp = elem.comp.compInterface; - toWorld = thisLayer.toWorld.bind(thisLayer); - fromWorld = thisLayer.fromWorld.bind(thisLayer); - fromComp = thisLayer.fromComp.bind(thisLayer); - toComp = thisLayer.toComp.bind(thisLayer); - mask = thisLayer.mask ? thisLayer.mask.bind(thisLayer) : null; - fromCompToSurface = fromComp; - } - if (!transform) { - transform = elem.layerInterface('ADBE Transform Group'); - $bm_transform = transform; - if (transform) { - anchorPoint = transform.anchorPoint; - /* position = transform.position; - rotation = transform.rotation; - scale = transform.scale; */ - } - } - - if (elemType === 4 && !content) { - content = thisLayer('ADBE Root Vectors Group'); - } - if (!effect) { - effect = thisLayer(4); - } - hasParent = !!(elem.hierarchy && elem.hierarchy.length); - if (hasParent && !parent) { - parent = elem.hierarchy[0].layerInterface; - } - time = this.comp.renderedFrame / this.comp.globalData.frameRate; - if (_needsRandom) { - seedRandom(randSeed + time); - } - if (needsVelocity) { - velocity = velocityAtTime(time); - } - expression_function(); - this.frameExpressionId = elem.globalData.frameId; - - // TODO: Check if it's possible to return on ShapeInterface the .v value - // Changed this to a ternary operation because Rollup failed compiling it correctly - scoped_bm_rt = scoped_bm_rt.propType === propTypes.SHAPE - ? scoped_bm_rt.v - : scoped_bm_rt; - return scoped_bm_rt; - } - // Bundlers will see these as dead code and unless we reference them - executeExpression.__preventDeadCodeRemoval = [$bm_transform, anchorPoint, time, velocity, inPoint, outPoint, width, height, name, loop_in, loop_out, smooth, toComp, fromCompToSurface, toWorld, fromWorld, mask, position, rotation, scale, thisComp, numKeys, active, wiggle, loopInDuration, loopOutDuration, comp, lookAt, easeOut, easeIn, ease, nearestKey, key, text, textIndex, textTotal, selectorValue, framesToTime, timeToFrames, sourceRectAtTime, substring, substr, posterizeTime, index, globalData]; - return executeExpression; - } - - ob.initiateExpression = initiateExpression; - ob.__preventDeadCodeRemoval = [window, document, XMLHttpRequest, fetch, frames, $bm_neg, add, $bm_sum, $bm_sub, $bm_mul, $bm_div, $bm_mod, clamp, radians_to_degrees, degreesToRadians, degrees_to_radians, normalize, rgbToHsl, hslToRgb, linear, random, createPath]; - return ob; -}()); - -const expressionHelpers = (function () { - function searchExpressions(elem, data, prop) { - if (data.x) { - prop.k = true; - prop.x = true; - prop.initiateExpression = ExpressionManager.initiateExpression; - prop.effectsSequence.push(prop.initiateExpression(elem, data, prop).bind(prop)); - } - } - - function getValueAtTime(frameNum) { - frameNum *= this.elem.globalData.frameRate; - frameNum -= this.offsetTime; - if (frameNum !== this._cachingAtTime.lastFrame) { - this._cachingAtTime.lastIndex = this._cachingAtTime.lastFrame < frameNum ? this._cachingAtTime.lastIndex : 0; - this._cachingAtTime.value = this.interpolateValue(frameNum, this._cachingAtTime); - this._cachingAtTime.lastFrame = frameNum; - } - return this._cachingAtTime.value; - } - - function getSpeedAtTime(frameNum) { - var delta = -0.01; - var v1 = this.getValueAtTime(frameNum); - var v2 = this.getValueAtTime(frameNum + delta); - var speed = 0; - if (v1.length) { - var i; - for (i = 0; i < v1.length; i += 1) { - speed += Math.pow(v2[i] - v1[i], 2); + function setGroupProperty(propertyGroup) { + this.propertyGroup = propertyGroup; } - speed = Math.sqrt(speed) * 100; - } else { - speed = 0; - } - return speed; - } - function getVelocityAtTime(frameNum) { - if (this.vel !== undefined) { - return this.vel; - } - var delta = -0.001; - // frameNum += this.elem.data.st; - var v1 = this.getValueAtTime(frameNum); - var v2 = this.getValueAtTime(frameNum + delta); - var velocity; - if (v1.length) { - velocity = createTypedArray('float32', v1.length); - var i; - for (i = 0; i < v1.length; i += 1) { - // removing frameRate - // if needed, don't add it here - // velocity[i] = this.elem.globalData.frameRate*((v2[i] - v1[i])/delta); - velocity[i] = (v2[i] - v1[i]) / delta; - } - } else { - velocity = (v2 - v1) / delta; - } - return velocity; - } - - function getStaticValueAtTime() { - return this.pv; - } - - function setGroupProperty(propertyGroup) { - this.propertyGroup = propertyGroup; - } - - return { - searchExpressions: searchExpressions, - getSpeedAtTime: getSpeedAtTime, - getVelocityAtTime: getVelocityAtTime, - getValueAtTime: getValueAtTime, - getStaticValueAtTime: getStaticValueAtTime, - setGroupProperty: setGroupProperty, - }; -}()); - -function addPropertyDecorator() { - function loopOut(type, duration, durationFlag) { - if (!this.k || !this.keyframes) { - return this.pv; - } - type = type ? type.toLowerCase() : ''; - var currentFrame = this.comp.renderedFrame; - var keyframes = this.keyframes; - var lastKeyFrame = keyframes[keyframes.length - 1].t; - if (currentFrame <= lastKeyFrame) { - return this.pv; - } - var cycleDuration; - var firstKeyFrame; - if (!durationFlag) { - if (!duration || duration > keyframes.length - 1) { - duration = keyframes.length - 1; - } - firstKeyFrame = keyframes[keyframes.length - 1 - duration].t; - cycleDuration = lastKeyFrame - firstKeyFrame; - } else { - if (!duration) { - cycleDuration = Math.max(0, lastKeyFrame - this.elem.data.ip); - } else { - cycleDuration = Math.abs(lastKeyFrame - this.elem.comp.globalData.frameRate * duration); - } - firstKeyFrame = lastKeyFrame - cycleDuration; - } - var i; - var len; - var ret; - if (type === 'pingpong') { - var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); - if (iterations % 2 !== 0) { - return this.getValueAtTime(((cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line - } - } else if (type === 'offset') { - var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); - var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); - var current = this.getValueAtTime(((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame) / this.comp.globalData.frameRate, 0); // eslint-disable-line - var repeats = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); - if (this.pv.length) { - ret = new Array(initV.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = (endV[i] - initV[i]) * repeats + current[i]; + return { + searchExpressions: searchExpressions, + getSpeedAtTime: getSpeedAtTime, + getVelocityAtTime: getVelocityAtTime, + getValueAtTime: getValueAtTime, + getStaticValueAtTime: getStaticValueAtTime, + setGroupProperty: setGroupProperty, + }; + }()); + + function addPropertyDecorator() { + function loopOut(type, duration, durationFlag) { + if (!this.k || !this.keyframes) { + return this.pv; + } + type = type ? type.toLowerCase() : ''; + var currentFrame = this.comp.renderedFrame; + var keyframes = this.keyframes; + var lastKeyFrame = keyframes[keyframes.length - 1].t; + if (currentFrame <= lastKeyFrame) { + return this.pv; + } + var cycleDuration; + var firstKeyFrame; + if (!durationFlag) { + if (!duration || duration > keyframes.length - 1) { + duration = keyframes.length - 1; + } + firstKeyFrame = keyframes[keyframes.length - 1 - duration].t; + cycleDuration = lastKeyFrame - firstKeyFrame; + } else { + if (!duration) { + cycleDuration = Math.max(0, lastKeyFrame - this.elem.data.ip); + } else { + cycleDuration = Math.abs(lastKeyFrame - this.elem.comp.globalData.frameRate * duration); + } + firstKeyFrame = lastKeyFrame - cycleDuration; } - return ret; - } - return (endV - initV) * repeats + current; - } else if (type === 'continue') { - var lastValue = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); - var nextLastValue = this.getValueAtTime((lastKeyFrame - 0.001) / this.comp.globalData.frameRate, 0); - if (this.pv.length) { - ret = new Array(lastValue.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = lastValue[i] + (lastValue[i] - nextLastValue[i]) * ((currentFrame - lastKeyFrame) / this.comp.globalData.frameRate) / 0.0005; // eslint-disable-line + var i; + var len; + var ret; + if (type === 'pingpong') { + var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); + if (iterations % 2 !== 0) { + return this.getValueAtTime(((cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line + } + } else if (type === 'offset') { + var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); + var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); + var current = this.getValueAtTime(((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame) / this.comp.globalData.frameRate, 0); // eslint-disable-line + var repeats = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); + if (this.pv.length) { + ret = new Array(initV.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = (endV[i] - initV[i]) * repeats + current[i]; + } + return ret; + } + return (endV - initV) * repeats + current; + } else if (type === 'continue') { + var lastValue = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); + var nextLastValue = this.getValueAtTime((lastKeyFrame - 0.001) / this.comp.globalData.frameRate, 0); + if (this.pv.length) { + ret = new Array(lastValue.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = lastValue[i] + (lastValue[i] - nextLastValue[i]) * ((currentFrame - lastKeyFrame) / this.comp.globalData.frameRate) / 0.0005; // eslint-disable-line + } + return ret; + } + return lastValue + (lastValue - nextLastValue) * (((currentFrame - lastKeyFrame)) / 0.001); } - return ret; + return this.getValueAtTime((((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line + } - return lastValue + (lastValue - nextLastValue) * (((currentFrame - lastKeyFrame)) / 0.001); - } - return this.getValueAtTime((((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line - } + function loopIn(type, duration, durationFlag) { + if (!this.k) { + return this.pv; + } + type = type ? type.toLowerCase() : ''; + var currentFrame = this.comp.renderedFrame; + var keyframes = this.keyframes; + var firstKeyFrame = keyframes[0].t; + if (currentFrame >= firstKeyFrame) { + return this.pv; + } + var cycleDuration; + var lastKeyFrame; + if (!durationFlag) { + if (!duration || duration > keyframes.length - 1) { + duration = keyframes.length - 1; + } + lastKeyFrame = keyframes[duration].t; + cycleDuration = lastKeyFrame - firstKeyFrame; + } else { + if (!duration) { + cycleDuration = Math.max(0, this.elem.data.op - firstKeyFrame); + } else { + cycleDuration = Math.abs(this.elem.comp.globalData.frameRate * duration); + } + lastKeyFrame = firstKeyFrame + cycleDuration; + } + var i; + var len; + var ret; + if (type === 'pingpong') { + var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration); + if (iterations % 2 === 0) { + return this.getValueAtTime((((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line + } + } else if (type === 'offset') { + var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); + var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); + var current = this.getValueAtTime((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate, 0); + var repeats = Math.floor((firstKeyFrame - currentFrame) / cycleDuration) + 1; + if (this.pv.length) { + ret = new Array(initV.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = current[i] - (endV[i] - initV[i]) * repeats; + } + return ret; + } + return current - (endV - initV) * repeats; + } else if (type === 'continue') { + var firstValue = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); + var nextFirstValue = this.getValueAtTime((firstKeyFrame + 0.001) / this.comp.globalData.frameRate, 0); + if (this.pv.length) { + ret = new Array(firstValue.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = firstValue[i] + ((firstValue[i] - nextFirstValue[i]) * (firstKeyFrame - currentFrame)) / 0.001; + } + return ret; + } + return firstValue + ((firstValue - nextFirstValue) * (firstKeyFrame - currentFrame)) / 0.001; + } + return this.getValueAtTime(((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame))) / this.comp.globalData.frameRate, 0); // eslint-disable-line - function loopIn(type, duration, durationFlag) { - if (!this.k) { - return this.pv; - } - type = type ? type.toLowerCase() : ''; - var currentFrame = this.comp.renderedFrame; - var keyframes = this.keyframes; - var firstKeyFrame = keyframes[0].t; - if (currentFrame >= firstKeyFrame) { - return this.pv; - } - var cycleDuration; - var lastKeyFrame; - if (!durationFlag) { - if (!duration || duration > keyframes.length - 1) { - duration = keyframes.length - 1; - } - lastKeyFrame = keyframes[duration].t; - cycleDuration = lastKeyFrame - firstKeyFrame; - } else { - if (!duration) { - cycleDuration = Math.max(0, this.elem.data.op - firstKeyFrame); - } else { - cycleDuration = Math.abs(this.elem.comp.globalData.frameRate * duration); } - lastKeyFrame = firstKeyFrame + cycleDuration; - } - var i; - var len; - var ret; - if (type === 'pingpong') { - var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration); - if (iterations % 2 === 0) { - return this.getValueAtTime((((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line - } - } else if (type === 'offset') { - var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); - var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); - var current = this.getValueAtTime((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate, 0); - var repeats = Math.floor((firstKeyFrame - currentFrame) / cycleDuration) + 1; - if (this.pv.length) { - ret = new Array(initV.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = current[i] - (endV[i] - initV[i]) * repeats; + + function smooth(width, samples) { + if (!this.k) { + return this.pv; } - return ret; - } - return current - (endV - initV) * repeats; - } else if (type === 'continue') { - var firstValue = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); - var nextFirstValue = this.getValueAtTime((firstKeyFrame + 0.001) / this.comp.globalData.frameRate, 0); - if (this.pv.length) { - ret = new Array(firstValue.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = firstValue[i] + ((firstValue[i] - nextFirstValue[i]) * (firstKeyFrame - currentFrame)) / 0.001; + width = (width || 0.4) * 0.5; + samples = Math.floor(samples || 5); + if (samples <= 1) { + return this.pv; + } + var currentTime = this.comp.renderedFrame / this.comp.globalData.frameRate; + var initFrame = currentTime - width; + var endFrame = currentTime + width; + var sampleFrequency = samples > 1 ? (endFrame - initFrame) / (samples - 1) : 1; + var i = 0; + var j = 0; + var value; + if (this.pv.length) { + value = createTypedArray('float32', this.pv.length); + } else { + value = 0; + } + var sampleValue; + while (i < samples) { + sampleValue = this.getValueAtTime(initFrame + i * sampleFrequency); + if (this.pv.length) { + for (j = 0; j < this.pv.length; j += 1) { + value[j] += sampleValue[j]; + } + } else { + value += sampleValue; + } + i += 1; + } + if (this.pv.length) { + for (j = 0; j < this.pv.length; j += 1) { + value[j] /= samples; + } + } else { + value /= samples; } - return ret; + return value; } - return firstValue + ((firstValue - nextFirstValue) * (firstKeyFrame - currentFrame)) / 0.001; - } - return this.getValueAtTime(((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame))) / this.comp.globalData.frameRate, 0); // eslint-disable-line - - } - function smooth(width, samples) { - if (!this.k) { - return this.pv; - } - width = (width || 0.4) * 0.5; - samples = Math.floor(samples || 5); - if (samples <= 1) { - return this.pv; - } - var currentTime = this.comp.renderedFrame / this.comp.globalData.frameRate; - var initFrame = currentTime - width; - var endFrame = currentTime + width; - var sampleFrequency = samples > 1 ? (endFrame - initFrame) / (samples - 1) : 1; - var i = 0; - var j = 0; - var value; - if (this.pv.length) { - value = createTypedArray('float32', this.pv.length); - } else { - value = 0; - } - var sampleValue; - while (i < samples) { - sampleValue = this.getValueAtTime(initFrame + i * sampleFrequency); - if (this.pv.length) { - for (j = 0; j < this.pv.length; j += 1) { - value[j] += sampleValue[j]; + function getTransformValueAtTime(time) { + if (!this._transformCachingAtTime) { + this._transformCachingAtTime = { + v: new Matrix(), + }; } - } else { - value += sampleValue; + /// / + var matrix = this._transformCachingAtTime.v; + matrix.cloneFromProps(this.pre.props); + if (this.appliedTransformations < 1) { + var anchor = this.a.getValueAtTime(time); + matrix.translate( + -anchor[0] * this.a.mult, + -anchor[1] * this.a.mult, + anchor[2] * this.a.mult + ); + } + if (this.appliedTransformations < 2) { + var scale = this.s.getValueAtTime(time); + matrix.scale( + scale[0] * this.s.mult, + scale[1] * this.s.mult, + scale[2] * this.s.mult + ); + } + if (this.sk && this.appliedTransformations < 3) { + var skew = this.sk.getValueAtTime(time); + var skewAxis = this.sa.getValueAtTime(time); + matrix.skewFromAxis(-skew * this.sk.mult, skewAxis * this.sa.mult); + } + if (this.r && this.appliedTransformations < 4) { + var rotation = this.r.getValueAtTime(time); + matrix.rotate(-rotation * this.r.mult); + } else if (!this.r && this.appliedTransformations < 4) { + var rotationZ = this.rz.getValueAtTime(time); + var rotationY = this.ry.getValueAtTime(time); + var rotationX = this.rx.getValueAtTime(time); + var orientation = this.or.getValueAtTime(time); + matrix.rotateZ(-rotationZ * this.rz.mult) + .rotateY(rotationY * this.ry.mult) + .rotateX(rotationX * this.rx.mult) + .rotateZ(-orientation[2] * this.or.mult) + .rotateY(orientation[1] * this.or.mult) + .rotateX(orientation[0] * this.or.mult); + } + if (this.data.p && this.data.p.s) { + var positionX = this.px.getValueAtTime(time); + var positionY = this.py.getValueAtTime(time); + if (this.data.p.z) { + var positionZ = this.pz.getValueAtTime(time); + matrix.translate( + positionX * this.px.mult, + positionY * this.py.mult, + -positionZ * this.pz.mult + ); + } else { + matrix.translate(positionX * this.px.mult, positionY * this.py.mult, 0); + } + } else { + var position = this.p.getValueAtTime(time); + matrix.translate( + position[0] * this.p.mult, + position[1] * this.p.mult, + -position[2] * this.p.mult + ); + } + return matrix; + /// / } - i += 1; - } - if (this.pv.length) { - for (j = 0; j < this.pv.length; j += 1) { - value[j] /= samples; + + function getTransformStaticValueAtTime() { + return this.v.clone(new Matrix()); } - } else { - value /= samples; - } - return value; - } - function getTransformValueAtTime(time) { - if (!this._transformCachingAtTime) { - this._transformCachingAtTime = { - v: new Matrix(), + var getTransformProperty = TransformPropertyFactory.getTransformProperty; + TransformPropertyFactory.getTransformProperty = function (elem, data, container) { + var prop = getTransformProperty(elem, data, container); + if (prop.dynamicProperties.length) { + prop.getValueAtTime = getTransformValueAtTime.bind(prop); + } else { + prop.getValueAtTime = getTransformStaticValueAtTime.bind(prop); + } + prop.setGroupProperty = expressionHelpers.setGroupProperty; + return prop; }; - } - /// / - var matrix = this._transformCachingAtTime.v; - matrix.cloneFromProps(this.pre.props); - if (this.appliedTransformations < 1) { - var anchor = this.a.getValueAtTime(time); - matrix.translate( - -anchor[0] * this.a.mult, - -anchor[1] * this.a.mult, - anchor[2] * this.a.mult - ); - } - if (this.appliedTransformations < 2) { - var scale = this.s.getValueAtTime(time); - matrix.scale( - scale[0] * this.s.mult, - scale[1] * this.s.mult, - scale[2] * this.s.mult - ); - } - if (this.sk && this.appliedTransformations < 3) { - var skew = this.sk.getValueAtTime(time); - var skewAxis = this.sa.getValueAtTime(time); - matrix.skewFromAxis(-skew * this.sk.mult, skewAxis * this.sa.mult); - } - if (this.r && this.appliedTransformations < 4) { - var rotation = this.r.getValueAtTime(time); - matrix.rotate(-rotation * this.r.mult); - } else if (!this.r && this.appliedTransformations < 4) { - var rotationZ = this.rz.getValueAtTime(time); - var rotationY = this.ry.getValueAtTime(time); - var rotationX = this.rx.getValueAtTime(time); - var orientation = this.or.getValueAtTime(time); - matrix.rotateZ(-rotationZ * this.rz.mult) - .rotateY(rotationY * this.ry.mult) - .rotateX(rotationX * this.rx.mult) - .rotateZ(-orientation[2] * this.or.mult) - .rotateY(orientation[1] * this.or.mult) - .rotateX(orientation[0] * this.or.mult); - } - if (this.data.p && this.data.p.s) { - var positionX = this.px.getValueAtTime(time); - var positionY = this.py.getValueAtTime(time); - if (this.data.p.z) { - var positionZ = this.pz.getValueAtTime(time); - matrix.translate( - positionX * this.px.mult, - positionY * this.py.mult, - -positionZ * this.pz.mult - ); - } else { - matrix.translate(positionX * this.px.mult, positionY * this.py.mult, 0); - } - } else { - var position = this.p.getValueAtTime(time); - matrix.translate( - position[0] * this.p.mult, - position[1] * this.p.mult, - -position[2] * this.p.mult - ); - } - return matrix; - /// / - } - - function getTransformStaticValueAtTime() { - return this.v.clone(new Matrix()); - } - - var getTransformProperty = TransformPropertyFactory.getTransformProperty; - TransformPropertyFactory.getTransformProperty = function (elem, data, container) { - var prop = getTransformProperty(elem, data, container); - if (prop.dynamicProperties.length) { - prop.getValueAtTime = getTransformValueAtTime.bind(prop); - } else { - prop.getValueAtTime = getTransformStaticValueAtTime.bind(prop); - } - prop.setGroupProperty = expressionHelpers.setGroupProperty; - return prop; - }; - - var propertyGetProp = PropertyFactory.getProp; - PropertyFactory.getProp = function (elem, data, type, mult, container) { - var prop = propertyGetProp(elem, data, type, mult, container); - // prop.getVelocityAtTime = getVelocityAtTime; - // prop.loopOut = loopOut; - // prop.loopIn = loopIn; - if (prop.kf) { - prop.getValueAtTime = expressionHelpers.getValueAtTime.bind(prop); - } else { - prop.getValueAtTime = expressionHelpers.getStaticValueAtTime.bind(prop); - } - prop.setGroupProperty = expressionHelpers.setGroupProperty; - prop.loopOut = loopOut; - prop.loopIn = loopIn; - prop.smooth = smooth; - prop.getVelocityAtTime = expressionHelpers.getVelocityAtTime.bind(prop); - prop.getSpeedAtTime = expressionHelpers.getSpeedAtTime.bind(prop); - prop.numKeys = data.a === 1 ? data.k.length : 0; - prop.propertyIndex = data.ix; - var value = 0; - if (type !== 0) { - value = createTypedArray('float32', data.a === 1 ? data.k[0].s.length : data.k.length); - } - prop._cachingAtTime = { - lastFrame: initialDefaultFrame, - lastIndex: 0, - value: value, - }; - expressionHelpers.searchExpressions(elem, data, prop); - if (prop.k) { - container.addDynamicProperty(prop); - } - return prop; - }; + var propertyGetProp = PropertyFactory.getProp; + PropertyFactory.getProp = function (elem, data, type, mult, container) { + var prop = propertyGetProp(elem, data, type, mult, container); + // prop.getVelocityAtTime = getVelocityAtTime; + // prop.loopOut = loopOut; + // prop.loopIn = loopIn; + if (prop.kf) { + prop.getValueAtTime = expressionHelpers.getValueAtTime.bind(prop); + } else { + prop.getValueAtTime = expressionHelpers.getStaticValueAtTime.bind(prop); + } + prop.setGroupProperty = expressionHelpers.setGroupProperty; + prop.loopOut = loopOut; + prop.loopIn = loopIn; + prop.smooth = smooth; + prop.getVelocityAtTime = expressionHelpers.getVelocityAtTime.bind(prop); + prop.getSpeedAtTime = expressionHelpers.getSpeedAtTime.bind(prop); + prop.numKeys = data.a === 1 ? data.k.length : 0; + prop.propertyIndex = data.ix; + var value = 0; + if (type !== 0) { + value = createTypedArray('float32', data.a === 1 ? data.k[0].s.length : data.k.length); + } + prop._cachingAtTime = { + lastFrame: initialDefaultFrame, + lastIndex: 0, + value: value, + }; + expressionHelpers.searchExpressions(elem, data, prop); + if (prop.k) { + container.addDynamicProperty(prop); + } - function getShapeValueAtTime(frameNum) { - // For now this caching object is created only when needed instead of creating it when the shape is initialized. - if (!this._cachingAtTime) { - this._cachingAtTime = { - shapeValue: shapePool.clone(this.pv), - lastIndex: 0, - lastTime: initialDefaultFrame, + return prop; }; - } - - frameNum *= this.elem.globalData.frameRate; - frameNum -= this.offsetTime; - if (frameNum !== this._cachingAtTime.lastTime) { - this._cachingAtTime.lastIndex = this._cachingAtTime.lastTime < frameNum ? this._caching.lastIndex : 0; - this._cachingAtTime.lastTime = frameNum; - this.interpolateShape(frameNum, this._cachingAtTime.shapeValue, this._cachingAtTime); - } - return this._cachingAtTime.shapeValue; - } - var ShapePropertyConstructorFunction = ShapePropertyFactory.getConstructorFunction(); - var KeyframedShapePropertyConstructorFunction = ShapePropertyFactory.getKeyframedConstructorFunction(); + function getShapeValueAtTime(frameNum) { + // For now this caching object is created only when needed instead of creating it when the shape is initialized. + if (!this._cachingAtTime) { + this._cachingAtTime = { + shapeValue: shapePool.clone(this.pv), + lastIndex: 0, + lastTime: initialDefaultFrame, + }; + } - function ShapeExpressions() {} - ShapeExpressions.prototype = { - vertices: function (prop, time) { - if (this.k) { - this.getValue(); - } - var shapePath = this.v; - if (time !== undefined) { - shapePath = this.getValueAtTime(time, 0); - } - var i; - var len = shapePath._length; - var vertices = shapePath[prop]; - var points = shapePath.v; - var arr = createSizedArray(len); - for (i = 0; i < len; i += 1) { - if (prop === 'i' || prop === 'o') { - arr[i] = [vertices[i][0] - points[i][0], vertices[i][1] - points[i][1]]; - } else { - arr[i] = [vertices[i][0], vertices[i][1]]; - } - } - return arr; - }, - points: function (time) { - return this.vertices('v', time); - }, - inTangents: function (time) { - return this.vertices('i', time); - }, - outTangents: function (time) { - return this.vertices('o', time); - }, - isClosed: function () { - return this.v.c; - }, - pointOnPath: function (perc, time) { - var shapePath = this.v; - if (time !== undefined) { - shapePath = this.getValueAtTime(time, 0); - } - if (!this._segmentsLength) { - this._segmentsLength = bez.getSegmentsLength(shapePath); - } - - var segmentsLength = this._segmentsLength; - var lengths = segmentsLength.lengths; - var lengthPos = segmentsLength.totalLength * perc; - var i = 0; - var len = lengths.length; - var accumulatedLength = 0; - var pt; - while (i < len) { - if (accumulatedLength + lengths[i].addedLength > lengthPos) { - var initIndex = i; - var endIndex = (shapePath.c && i === len - 1) ? 0 : i + 1; - var segmentPerc = (lengthPos - accumulatedLength) / lengths[i].addedLength; - pt = bez.getPointInSegment(shapePath.v[initIndex], shapePath.v[endIndex], shapePath.o[initIndex], shapePath.i[endIndex], segmentPerc, lengths[i]); - break; - } else { - accumulatedLength += lengths[i].addedLength; + frameNum *= this.elem.globalData.frameRate; + frameNum -= this.offsetTime; + if (frameNum !== this._cachingAtTime.lastTime) { + this._cachingAtTime.lastIndex = this._cachingAtTime.lastTime < frameNum ? this._caching.lastIndex : 0; + this._cachingAtTime.lastTime = frameNum; + this.interpolateShape(frameNum, this._cachingAtTime.shapeValue, this._cachingAtTime); } - i += 1; + return this._cachingAtTime.shapeValue; } - if (!pt) { - pt = shapePath.c ? [shapePath.v[0][0], shapePath.v[0][1]] : [shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1]]; - } - return pt; - }, - vectorOnPath: function (perc, time, vectorType) { - // perc doesn't use triple equality because it can be a Number object as well as a primitive. - if (perc == 1) { // eslint-disable-line eqeqeq - perc = this.v.c; - } else if (perc == 0) { // eslint-disable-line eqeqeq - perc = 0.999; - } - var pt1 = this.pointOnPath(perc, time); - var pt2 = this.pointOnPath(perc + 0.001, time); - var xLength = pt2[0] - pt1[0]; - var yLength = pt2[1] - pt1[1]; - var magnitude = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2)); - if (magnitude === 0) { - return [0, 0]; - } - var unitVector = vectorType === 'tangent' ? [xLength / magnitude, yLength / magnitude] : [-yLength / magnitude, xLength / magnitude]; - return unitVector; - }, - tangentOnPath: function (perc, time) { - return this.vectorOnPath(perc, time, 'tangent'); - }, - normalOnPath: function (perc, time) { - return this.vectorOnPath(perc, time, 'normal'); - }, - setGroupProperty: expressionHelpers.setGroupProperty, - getValueAtTime: expressionHelpers.getStaticValueAtTime, - }; - extendPrototype([ShapeExpressions], ShapePropertyConstructorFunction); - extendPrototype([ShapeExpressions], KeyframedShapePropertyConstructorFunction); - KeyframedShapePropertyConstructorFunction.prototype.getValueAtTime = getShapeValueAtTime; - KeyframedShapePropertyConstructorFunction.prototype.initiateExpression = ExpressionManager.initiateExpression; - - var propertyGetShapeProp = ShapePropertyFactory.getShapeProp; - ShapePropertyFactory.getShapeProp = function (elem, data, type, arr, trims) { - var prop = propertyGetShapeProp(elem, data, type, arr, trims); - prop.propertyIndex = data.ix; - prop.lock = false; - if (type === 3) { - expressionHelpers.searchExpressions(elem, data.pt, prop); - } else if (type === 4) { - expressionHelpers.searchExpressions(elem, data.ks, prop); - } - if (prop.k) { - elem.addDynamicProperty(prop); + + var ShapePropertyConstructorFunction = ShapePropertyFactory.getConstructorFunction(); + var KeyframedShapePropertyConstructorFunction = ShapePropertyFactory.getKeyframedConstructorFunction(); + + function ShapeExpressions() {} + ShapeExpressions.prototype = { + vertices: function (prop, time) { + if (this.k) { + this.getValue(); + } + var shapePath = this.v; + if (time !== undefined) { + shapePath = this.getValueAtTime(time, 0); + } + var i; + var len = shapePath._length; + var vertices = shapePath[prop]; + var points = shapePath.v; + var arr = createSizedArray(len); + for (i = 0; i < len; i += 1) { + if (prop === 'i' || prop === 'o') { + arr[i] = [vertices[i][0] - points[i][0], vertices[i][1] - points[i][1]]; + } else { + arr[i] = [vertices[i][0], vertices[i][1]]; + } + } + return arr; + }, + points: function (time) { + return this.vertices('v', time); + }, + inTangents: function (time) { + return this.vertices('i', time); + }, + outTangents: function (time) { + return this.vertices('o', time); + }, + isClosed: function () { + return this.v.c; + }, + pointOnPath: function (perc, time) { + var shapePath = this.v; + if (time !== undefined) { + shapePath = this.getValueAtTime(time, 0); + } + if (!this._segmentsLength) { + this._segmentsLength = bez.getSegmentsLength(shapePath); + } + + var segmentsLength = this._segmentsLength; + var lengths = segmentsLength.lengths; + var lengthPos = segmentsLength.totalLength * perc; + var i = 0; + var len = lengths.length; + var accumulatedLength = 0; + var pt; + while (i < len) { + if (accumulatedLength + lengths[i].addedLength > lengthPos) { + var initIndex = i; + var endIndex = (shapePath.c && i === len - 1) ? 0 : i + 1; + var segmentPerc = (lengthPos - accumulatedLength) / lengths[i].addedLength; + pt = bez.getPointInSegment(shapePath.v[initIndex], shapePath.v[endIndex], shapePath.o[initIndex], shapePath.i[endIndex], segmentPerc, lengths[i]); + break; + } else { + accumulatedLength += lengths[i].addedLength; + } + i += 1; + } + if (!pt) { + pt = shapePath.c ? [shapePath.v[0][0], shapePath.v[0][1]] : [shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1]]; + } + return pt; + }, + vectorOnPath: function (perc, time, vectorType) { + // perc doesn't use triple equality because it can be a Number object as well as a primitive. + if (perc == 1) { // eslint-disable-line eqeqeq + perc = this.v.c; + } else if (perc == 0) { // eslint-disable-line eqeqeq + perc = 0.999; + } + var pt1 = this.pointOnPath(perc, time); + var pt2 = this.pointOnPath(perc + 0.001, time); + var xLength = pt2[0] - pt1[0]; + var yLength = pt2[1] - pt1[1]; + var magnitude = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2)); + if (magnitude === 0) { + return [0, 0]; + } + var unitVector = vectorType === 'tangent' ? [xLength / magnitude, yLength / magnitude] : [-yLength / magnitude, xLength / magnitude]; + return unitVector; + }, + tangentOnPath: function (perc, time) { + return this.vectorOnPath(perc, time, 'tangent'); + }, + normalOnPath: function (perc, time) { + return this.vectorOnPath(perc, time, 'normal'); + }, + setGroupProperty: expressionHelpers.setGroupProperty, + getValueAtTime: expressionHelpers.getStaticValueAtTime, + }; + extendPrototype([ShapeExpressions], ShapePropertyConstructorFunction); + extendPrototype([ShapeExpressions], KeyframedShapePropertyConstructorFunction); + KeyframedShapePropertyConstructorFunction.prototype.getValueAtTime = getShapeValueAtTime; + KeyframedShapePropertyConstructorFunction.prototype.initiateExpression = ExpressionManager.initiateExpression; + + var propertyGetShapeProp = ShapePropertyFactory.getShapeProp; + ShapePropertyFactory.getShapeProp = function (elem, data, type, arr, trims) { + var prop = propertyGetShapeProp(elem, data, type, arr, trims); + prop.propertyIndex = data.ix; + prop.lock = false; + if (type === 3) { + expressionHelpers.searchExpressions(elem, data.pt, prop); + } else if (type === 4) { + expressionHelpers.searchExpressions(elem, data.ks, prop); + } + if (prop.k) { + elem.addDynamicProperty(prop); + } + return prop; + }; } - return prop; - }; -} - -function initialize$1() { - addPropertyDecorator(); -} - -function addDecorator() { - function searchExpressions() { - if (this.data.d.x) { - this.calculateExpression = ExpressionManager.initiateExpression.bind(this)(this.elem, this.data.d, this); - this.addEffect(this.getExpressionValue.bind(this)); - return true; + + function initialize$1() { + addPropertyDecorator(); } - return null; - } - - TextProperty.prototype.getExpressionValue = function (currentValue, text) { - var newValue = this.calculateExpression(text); - if (currentValue.t !== newValue) { - var newData = {}; - this.copyData(newData, currentValue); - newData.t = newValue.toString(); - newData.__complete = false; - return newData; + + function addDecorator() { + function searchExpressions() { + if (this.data.d.x) { + this.calculateExpression = ExpressionManager.initiateExpression.bind(this)(this.elem, this.data.d, this); + this.addEffect(this.getExpressionValue.bind(this)); + return true; + } + return null; + } + + TextProperty.prototype.getExpressionValue = function (currentValue, text) { + var newValue = this.calculateExpression(text); + if (currentValue.t !== newValue) { + var newData = {}; + this.copyData(newData, currentValue); + newData.t = newValue.toString(); + newData.__complete = false; + return newData; + } + return currentValue; + }; + + TextProperty.prototype.searchProperty = function () { + var isKeyframed = this.searchKeyframes(); + var hasExpressions = this.searchExpressions(); + this.kf = isKeyframed || hasExpressions; + return this.kf; + }; + + TextProperty.prototype.searchExpressions = searchExpressions; } - return currentValue; - }; - TextProperty.prototype.searchProperty = function () { - var isKeyframed = this.searchKeyframes(); - var hasExpressions = this.searchExpressions(); - this.kf = isKeyframed || hasExpressions; - return this.kf; - }; + function initialize() { + addDecorator(); + } - TextProperty.prototype.searchExpressions = searchExpressions; -} + // Registering expression plugin + setExpressionsPlugin(Expressions); + initialize$1(); + initialize(); -function initialize() { - addDecorator(); -} + return lottie; -// Registering expression plugin -setExpressionsPlugin(Expressions); -initialize$1(); -initialize(); +} ); -export { lottie as default }; +export default lottie; From ae2e81061a82f17066040b89d984a9b3b494d587 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:52:15 -0500 Subject: [PATCH 12/45] opentype: tree-shake as IIFE --- examples/jsm/libs/opentype.module.js | 26140 +++++++++++++------------ 1 file changed, 13073 insertions(+), 13067 deletions(-) diff --git a/examples/jsm/libs/opentype.module.js b/examples/jsm/libs/opentype.module.js index 71032872dbe7e4..71ca072a678ca9 100644 --- a/examples/jsm/libs/opentype.module.js +++ b/examples/jsm/libs/opentype.module.js @@ -2,14094 +2,14069 @@ * https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens */ -/*! https://mths.be/codepointat v0.2.0 by @mathias */ -if (!String.prototype.codePointAt) { - (function() { - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (index != index) { // better `isNaN` - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( // check if it’s the start of a surrogate pair - first >= 0xD800 && first <= 0xDBFF && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; - if (defineProperty) { - defineProperty(String.prototype, 'codePointAt', { - 'value': codePointAt, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.codePointAt = codePointAt; - } - }()); -} - -var TINF_OK = 0; -var TINF_DATA_ERROR = -3; - -function Tree() { - this.table = new Uint16Array(16); /* table of code length counts */ - this.trans = new Uint16Array(288); /* code -> symbol translation table */ -} - -function Data(source, dest) { - this.source = source; - this.sourceIndex = 0; - this.tag = 0; - this.bitcount = 0; - - this.dest = dest; - this.destLen = 0; - - this.ltree = new Tree(); /* dynamic length/symbol tree */ - this.dtree = new Tree(); /* dynamic distance tree */ -} - -/* --------------------------------------------------- * - * -- uninitialized global data (static structures) -- * - * --------------------------------------------------- */ - -var sltree = new Tree(); -var sdtree = new Tree(); - -/* extra bits and base tables for length codes */ -var length_bits = new Uint8Array(30); -var length_base = new Uint16Array(30); - -/* extra bits and base tables for distance codes */ -var dist_bits = new Uint8Array(30); -var dist_base = new Uint16Array(30); - -/* special ordering of code length codes */ -var clcidx = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, - 10, 5, 11, 4, 12, 3, 13, 2, - 14, 1, 15 -]); - -/* used by tinf_decode_trees, avoids allocations every call */ -var code_tree = new Tree(); -var lengths = new Uint8Array(288 + 32); - -/* ----------------------- * - * -- utility functions -- * - * ----------------------- */ - -/* build extra bits and base tables */ -function tinf_build_bits_base(bits, base, delta, first) { - var i, sum; - - /* build bits table */ - for (i = 0; i < delta; ++i) { bits[i] = 0; } - for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } - - /* build base table */ - for (sum = first, i = 0; i < 30; ++i) { - base[i] = sum; - sum += 1 << bits[i]; - } -} - -/* build the fixed huffman trees */ -function tinf_build_fixed_trees(lt, dt) { - var i; - - /* build fixed length tree */ - for (i = 0; i < 7; ++i) { lt.table[i] = 0; } - - lt.table[7] = 24; - lt.table[8] = 152; - lt.table[9] = 112; - - for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } - for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } - for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } - for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } - - /* build fixed distance tree */ - for (i = 0; i < 5; ++i) { dt.table[i] = 0; } - - dt.table[5] = 32; - - for (i = 0; i < 32; ++i) { dt.trans[i] = i; } -} - -/* given an array of code lengths, build a tree */ -var offs = new Uint16Array(16); - -function tinf_build_tree(t, lengths, off, num) { - var i, sum; - - /* clear code length count table */ - for (i = 0; i < 16; ++i) { t.table[i] = 0; } - - /* scan symbol lengths, and sum code length counts */ - for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } - - t.table[0] = 0; - - /* compute offset table for distribution sort */ - for (sum = 0, i = 0; i < 16; ++i) { - offs[i] = sum; - sum += t.table[i]; - } - - /* create code->symbol translation table (symbols sorted by code) */ - for (i = 0; i < num; ++i) { - if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } - } -} - -/* ---------------------- * - * -- decode functions -- * - * ---------------------- */ - -/* get one bit from source stream */ -function tinf_getbit(d) { - /* check if tag is empty */ - if (!d.bitcount--) { - /* load next tag */ - d.tag = d.source[d.sourceIndex++]; - d.bitcount = 7; - } - - /* shift bit out of tag */ - var bit = d.tag & 1; - d.tag >>>= 1; - - return bit; -} - -/* read a num bit value from a stream and add base */ -function tinf_read_bits(d, num, base) { - if (!num) - { return base; } - - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var val = d.tag & (0xffff >>> (16 - num)); - d.tag >>>= num; - d.bitcount -= num; - return val + base; -} - -/* given a data stream and a tree, decode a symbol */ -function tinf_decode_symbol(d, t) { - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var sum = 0, cur = 0, len = 0; - var tag = d.tag; - - /* get more bits while code value is above sum */ - do { - cur = 2 * cur + (tag & 1); - tag >>>= 1; - ++len; - - sum += t.table[len]; - cur -= t.table[len]; - } while (cur >= 0); - - d.tag = tag; - d.bitcount -= len; - - return t.trans[sum + cur]; -} - -/* given a data stream, decode dynamic trees from it */ -function tinf_decode_trees(d, lt, dt) { - var hlit, hdist, hclen; - var i, num, length; - - /* get 5 bits HLIT (257-286) */ - hlit = tinf_read_bits(d, 5, 257); - - /* get 5 bits HDIST (1-32) */ - hdist = tinf_read_bits(d, 5, 1); - - /* get 4 bits HCLEN (4-19) */ - hclen = tinf_read_bits(d, 4, 4); - - for (i = 0; i < 19; ++i) { lengths[i] = 0; } - - /* read code lengths for code length alphabet */ - for (i = 0; i < hclen; ++i) { - /* get 3 bits code length (0-7) */ - var clen = tinf_read_bits(d, 3, 0); - lengths[clcidx[i]] = clen; - } - - /* build code length tree */ - tinf_build_tree(code_tree, lengths, 0, 19); - - /* decode code lengths for the dynamic trees */ - for (num = 0; num < hlit + hdist;) { - var sym = tinf_decode_symbol(d, code_tree); - - switch (sym) { - case 16: - /* copy previous code length 3-6 times (read 2 bits) */ - var prev = lengths[num - 1]; - for (length = tinf_read_bits(d, 2, 3); length; --length) { - lengths[num++] = prev; - } - break; - case 17: - /* repeat code length 0 for 3-10 times (read 3 bits) */ - for (length = tinf_read_bits(d, 3, 3); length; --length) { - lengths[num++] = 0; - } - break; - case 18: - /* repeat code length 0 for 11-138 times (read 7 bits) */ - for (length = tinf_read_bits(d, 7, 11); length; --length) { - lengths[num++] = 0; - } - break; - default: - /* values 0-15 represent the actual code lengths */ - lengths[num++] = sym; - break; - } - } - - /* build dynamic trees */ - tinf_build_tree(lt, lengths, 0, hlit); - tinf_build_tree(dt, lengths, hlit, hdist); -} - -/* ----------------------------- * - * -- block inflate functions -- * - * ----------------------------- */ - -/* given a stream and two trees, inflate a block of data */ -function tinf_inflate_block_data(d, lt, dt) { - while (1) { - var sym = tinf_decode_symbol(d, lt); - - /* check for end of block */ - if (sym === 256) { - return TINF_OK; - } - - if (sym < 256) { - d.dest[d.destLen++] = sym; - } else { - var length, dist, offs; - var i; - - sym -= 257; - - /* possibly get more bits from length code */ - length = tinf_read_bits(d, length_bits[sym], length_base[sym]); - - dist = tinf_decode_symbol(d, dt); - - /* possibly get more bits from distance code */ - offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); - - /* copy match */ - for (i = offs; i < offs + length; ++i) { - d.dest[d.destLen++] = d.dest[i]; - } - } - } -} - -/* inflate an uncompressed block of data */ -function tinf_inflate_uncompressed_block(d) { - var length, invlength; - var i; - - /* unread from bitbuffer */ - while (d.bitcount > 8) { - d.sourceIndex--; - d.bitcount -= 8; - } - - /* get length */ - length = d.source[d.sourceIndex + 1]; - length = 256 * length + d.source[d.sourceIndex]; - - /* get one's complement of length */ - invlength = d.source[d.sourceIndex + 3]; - invlength = 256 * invlength + d.source[d.sourceIndex + 2]; - - /* check length */ - if (length !== (~invlength & 0x0000ffff)) - { return TINF_DATA_ERROR; } - - d.sourceIndex += 4; - - /* copy block */ - for (i = length; i; --i) - { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } - - /* make sure we start next block on a byte boundary */ - d.bitcount = 0; - - return TINF_OK; -} - -/* inflate stream from source to dest */ -function tinf_uncompress(source, dest) { - var d = new Data(source, dest); - var bfinal, btype, res; - - do { - /* read final block flag */ - bfinal = tinf_getbit(d); - - /* read block type (2 bits) */ - btype = tinf_read_bits(d, 2, 0); - - /* decompress block */ - switch (btype) { - case 0: - /* decompress uncompressed block */ - res = tinf_inflate_uncompressed_block(d); - break; - case 1: - /* decompress block with fixed huffman trees */ - res = tinf_inflate_block_data(d, sltree, sdtree); - break; - case 2: - /* decompress block with dynamic huffman trees */ - tinf_decode_trees(d, d.ltree, d.dtree); - res = tinf_inflate_block_data(d, d.ltree, d.dtree); - break; - default: - res = TINF_DATA_ERROR; - } - - if (res !== TINF_OK) - { throw new Error('Data error'); } - - } while (!bfinal); - - if (d.destLen < d.dest.length) { - if (typeof d.dest.slice === 'function') - { return d.dest.slice(0, d.destLen); } - else - { return d.dest.subarray(0, d.destLen); } - } - - return d.dest; -} - -/* -------------------- * - * -- initialization -- * - * -------------------- */ - -/* build fixed huffman trees */ -tinf_build_fixed_trees(sltree, sdtree); - -/* build extra bits and base tables */ -tinf_build_bits_base(length_bits, length_base, 4, 3); -tinf_build_bits_base(dist_bits, dist_base, 2, 1); - -/* fix a special case */ -length_bits[28] = 0; -length_base[28] = 258; - -var tinyInflate = tinf_uncompress; - -// The Bounding Box object - -function derive(v0, v1, v2, v3, t) { - return Math.pow(1 - t, 3) * v0 + - 3 * Math.pow(1 - t, 2) * t * v1 + - 3 * (1 - t) * Math.pow(t, 2) * v2 + - Math.pow(t, 3) * v3; -} -/** - * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. - * It is used to calculate the bounding box of a glyph or text path. - * - * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. - * - * @exports opentype.BoundingBox - * @class - * @constructor - */ -function BoundingBox() { - this.x1 = Number.NaN; - this.y1 = Number.NaN; - this.x2 = Number.NaN; - this.y2 = Number.NaN; -} +const { BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer } = /* @__PURE__ */ ( () => { + + /*! https://mths.be/codepointat v0.2.0 by @mathias */ + if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); + } -/** - * Returns true if the bounding box is empty, that is, no points have been added to the box yet. - */ -BoundingBox.prototype.isEmpty = function() { - return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); -}; + var TINF_OK = 0; + var TINF_DATA_ERROR = -3; -/** - * Add the point to the bounding box. - * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. - * @param {number} x - The X coordinate of the point. - * @param {number} y - The Y coordinate of the point. - */ -BoundingBox.prototype.addPoint = function(x, y) { - if (typeof x === 'number') { - if (isNaN(this.x1) || isNaN(this.x2)) { - this.x1 = x; - this.x2 = x; - } - if (x < this.x1) { - this.x1 = x; - } - if (x > this.x2) { - this.x2 = x; - } + function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ } - if (typeof y === 'number') { - if (isNaN(this.y1) || isNaN(this.y2)) { - this.y1 = y; - this.y2 = y; - } - if (y < this.y1) { - this.y1 = y; - } - if (y > this.y2) { - this.y2 = y; - } + + function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ } -}; -/** - * Add a X coordinate to the bounding box. - * This extends the bounding box to include the X coordinate. - * This function is used internally inside of addBezier. - * @param {number} x - The X coordinate of the point. - */ -BoundingBox.prototype.addX = function(x) { - this.addPoint(x, null); -}; + /* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ -/** - * Add a Y coordinate to the bounding box. - * This extends the bounding box to include the Y coordinate. - * This function is used internally inside of addBezier. - * @param {number} y - The Y coordinate of the point. - */ -BoundingBox.prototype.addY = function(y) { - this.addPoint(null, y); -}; + var sltree = new Tree(); + var sdtree = new Tree(); -/** - * Add a Bézier curve to the bounding box. - * This extends the bounding box to include the entire Bézier. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the first control point. - * @param {number} y1 - The Y coordinate of the first control point. - * @param {number} x2 - The X coordinate of the second control point. - * @param {number} y2 - The Y coordinate of the second control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ -BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { - // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html - // and https://github.com/icons8/svg-path-bounding-box + /* extra bits and base tables for length codes */ + var length_bits = new Uint8Array(30); + var length_base = new Uint16Array(30); - var p0 = [x0, y0]; - var p1 = [x1, y1]; - var p2 = [x2, y2]; - var p3 = [x, y]; + /* extra bits and base tables for distance codes */ + var dist_bits = new Uint8Array(30); + var dist_base = new Uint16Array(30); - this.addPoint(x0, y0); - this.addPoint(x, y); + /* special ordering of code length codes */ + var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 + ]); - for (var i = 0; i <= 1; i++) { - var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; - var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; - var c = 3 * p1[i] - 3 * p0[i]; + /* used by tinf_decode_trees, avoids allocations every call */ + var code_tree = new Tree(); + var lengths = new Uint8Array(288 + 32); - if (a === 0) { - if (b === 0) { continue; } - var t = -c / b; - if (0 < t && t < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } - } - continue; - } + /* ----------------------- * + * -- utility functions -- * + * ----------------------- */ - var b2ac = Math.pow(b, 2) - 4 * c * a; - if (b2ac < 0) { continue; } - var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); - if (0 < t1 && t1 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - } - var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); - if (0 < t2 && t2 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - } + /* build extra bits and base tables */ + function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } } -}; -/** - * Add a quadratic curve to the bounding box. - * This extends the bounding box to include the entire quadratic curve. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the control point. - * @param {number} y1 - The Y coordinate of the control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ -BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { - var cp1x = x0 + 2 / 3 * (x1 - x0); - var cp1y = y0 + 2 / 3 * (y1 - y0); - var cp2x = cp1x + 1 / 3 * (x - x0); - var cp2y = cp1y + 1 / 3 * (y - y0); - this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); -}; + /* build the fixed huffman trees */ + function tinf_build_fixed_trees(lt, dt) { + var i; -// Geometric objects + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } -/** - * A bézier path containing a set of path commands similar to a SVG path. - * Paths can be drawn on a context using `draw`. - * @exports opentype.Path - * @class - * @constructor - */ -function Path() { - this.commands = []; - this.fill = 'black'; - this.stroke = null; - this.strokeWidth = 1; -} + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; -/** - * @param {number} x - * @param {number} y - */ -Path.prototype.moveTo = function(x, y) { - this.commands.push({ - type: 'M', - x: x, - y: y - }); -}; + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } -/** - * @param {number} x - * @param {number} y - */ -Path.prototype.lineTo = function(x, y) { - this.commands.push({ - type: 'L', - x: x, - y: y - }); -}; + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } -/** - * Draws cubic curve - * @function - * curveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - */ + dt.table[5] = 32; -/** - * Draws cubic curve - * @function - * bezierCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - * @see curveTo - */ -Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { - this.commands.push({ - type: 'C', - x1: x1, - y1: y1, - x2: x2, - y2: y2, - x: x, - y: y - }); -}; + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } + } -/** - * Draws quadratic curve - * @function - * quadraticCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ + /* given an array of code lengths, build a tree */ + var offs = new Uint16Array(16); -/** - * Draws quadratic curve - * @function - * quadTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ -Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { - this.commands.push({ - type: 'Q', - x1: x1, - y1: y1, - x: x, - y: y - }); -}; + function tinf_build_tree(t, lengths, off, num) { + var i, sum; -/** - * Closes the path - * @function closePath - * @memberof opentype.Path.prototype - */ + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } -/** - * Close the path - * @function close - * @memberof opentype.Path.prototype - */ -Path.prototype.close = Path.prototype.closePath = function() { - this.commands.push({ - type: 'Z' - }); -}; + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } -/** - * Add the given path or list of commands to the commands of this path. - * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. - */ -Path.prototype.extend = function(pathOrCommands) { - if (pathOrCommands.commands) { - pathOrCommands = pathOrCommands.commands; - } else if (pathOrCommands instanceof BoundingBox) { - var box = pathOrCommands; - this.moveTo(box.x1, box.y1); - this.lineTo(box.x2, box.y1); - this.lineTo(box.x2, box.y2); - this.lineTo(box.x1, box.y2); - this.close(); - return; - } - - Array.prototype.push.apply(this.commands, pathOrCommands); -}; + t.table[0] = 0; -/** - * Calculate the bounding box of the path. - * @returns {opentype.BoundingBox} - */ -Path.prototype.getBoundingBox = function() { - var box = new BoundingBox(); - - var startX = 0; - var startY = 0; - var prevX = 0; - var prevY = 0; - for (var i = 0; i < this.commands.length; i++) { - var cmd = this.commands[i]; - switch (cmd.type) { - case 'M': - box.addPoint(cmd.x, cmd.y); - startX = prevX = cmd.x; - startY = prevY = cmd.y; - break; - case 'L': - box.addPoint(cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Q': - box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'C': - box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Z': - prevX = startX; - prevY = startY; - break; - default: - throw new Error('Unexpected path command ' + cmd.type); - } - } - if (box.isEmpty()) { - box.addPoint(0, 0); + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; } - return box; -}; -/** - * Draw the path to a 2D context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. - */ -Path.prototype.draw = function(ctx) { - ctx.beginPath(); - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - ctx.moveTo(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - ctx.lineTo(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - ctx.closePath(); - } + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } } - - if (this.fill) { - ctx.fillStyle = this.fill; - ctx.fill(); } - if (this.stroke) { - ctx.strokeStyle = this.stroke; - ctx.lineWidth = this.strokeWidth; - ctx.stroke(); + /* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + + /* get one bit from source stream */ + function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; } -}; -/** - * Convert the Path to a string of path data instructions - * See http://www.w3.org/TR/SVG/paths.html#PathData - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ -Path.prototype.toPathData = function(decimalPlaces) { - decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; - function floatToString(v) { - if (Math.round(v) === v) { - return '' + Math.round(v); - } else { - return v.toFixed(decimalPlaces); - } + return bit; } - function packValues() { - var arguments$1 = arguments; - - var s = ''; - for (var i = 0; i < arguments.length; i += 1) { - var v = arguments$1[i]; - if (v >= 0 && i > 0) { - s += ' '; - } + /* read a num bit value from a stream and add base */ + function tinf_read_bits(d, num, base) { + if (!num) + { return base; } - s += floatToString(v); - } + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } - return s; + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; } - var d = ''; - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - d += 'M' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - d += 'L' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - d += 'Z'; - } + /* given a data stream and a tree, decode a symbol */ + function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; - return d; -}; + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; -/** - * Convert the path to an SVG element, as a string. - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ -Path.prototype.toSVG = function(decimalPlaces) { - var svg = '= 0); + + d.tag = tag; + d.bitcount -= len; - if (this.stroke) { - svg += ' stroke="' + this.stroke + '" stroke-width="' + this.strokeWidth + '"'; + return t.trans[sum + cur]; } - svg += '/>'; - return svg; -}; - -/** - * Convert the path to a DOM element. - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {SVGPathElement} - */ -Path.prototype.toDOMElement = function(decimalPlaces) { - var temporaryPath = this.toPathData(decimalPlaces); - var newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + /* given a data stream, decode dynamic trees from it */ + function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; - newPath.setAttribute('d', temporaryPath); + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); - return newPath; -}; + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); -// Run-time checking of preconditions. + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); -function fail(message) { - throw new Error(message); -} + for (i = 0; i < 19; ++i) { lengths[i] = 0; } -// Precondition function that checks if the given predicate is true. -// If not, it will throw an error. -function argument(predicate, message) { - if (!predicate) { - fail(message); + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; } -} -var check = { fail: fail, argument: argument, assert: argument }; -// Data types used in the OpenType font file. + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); -var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 -var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); -/** - * @exports opentype.decode - * @class - */ -var decode = {}; -/** - * @exports opentype.encode - * @class - */ -var encode = {}; -/** - * @exports opentype.sizeOf - * @class - */ -var sizeOf = {}; + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } -// Return a function that always returns the same value. -function constant(v) { - return function() { - return v; - }; -} + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); + } -// OpenType data types ////////////////////////////////////////////////////// + /* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ -/** - * Convert an 8-bit unsigned integer to a list of 1 byte. - * @param {number} - * @returns {Array} - */ -encode.BYTE = function(v) { - check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); - return [v]; -}; -/** - * @constant - * @type {number} - */ -sizeOf.BYTE = constant(1); + /* given a stream and two trees, inflate a block of data */ + function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); -/** - * Convert a 8-bit signed integer to a list of 1 byte. - * @param {string} - * @returns {Array} - */ -encode.CHAR = function(v) { - return [v.charCodeAt(0)]; -}; + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } -/** - * @constant - * @type {number} - */ -sizeOf.CHAR = constant(1); + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; -/** - * Convert an ASCII string to a list of bytes. - * @param {string} - * @returns {Array} - */ -encode.CHARARRAY = function(v) { - if (typeof v === 'undefined') { - v = ''; - console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); - } - var b = []; - for (var i = 0; i < v.length; i += 1) { - b[i] = v.charCodeAt(i); - } + sym -= 257; - return b; -}; + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); -/** - * @param {Array} - * @returns {number} - */ -sizeOf.CHARARRAY = function(v) { - if (typeof v === 'undefined') { - return 0; - } - return v.length; -}; + dist = tinf_decode_symbol(d, dt); -/** - * Convert a 16-bit unsigned integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ -encode.USHORT = function(v) { - return [(v >> 8) & 0xFF, v & 0xFF]; -}; + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); -/** - * @constant - * @type {number} - */ -sizeOf.USHORT = constant(2); + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } + } -/** - * Convert a 16-bit signed integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ -encode.SHORT = function(v) { - // Two's complement - if (v >= LIMIT16) { - v = -(2 * LIMIT16 - v); + /* inflate an uncompressed block of data */ + function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; } - return [(v >> 8) & 0xFF, v & 0xFF]; -}; + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; -/** - * @constant - * @type {number} - */ -sizeOf.SHORT = constant(2); + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; -/** - * Convert a 24-bit unsigned integer to a list of 3 bytes. - * @param {number} - * @returns {Array} - */ -encode.UINT24 = function(v) { - return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; -}; + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } -/** - * @constant - * @type {number} - */ -sizeOf.UINT24 = constant(3); + d.sourceIndex += 4; -/** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ -encode.ULONG = function(v) { - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; -}; + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } -/** - * @constant - * @type {number} - */ -sizeOf.ULONG = constant(4); + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; -/** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ -encode.LONG = function(v) { - // Two's complement - if (v >= LIMIT32) { - v = -(2 * LIMIT32 - v); + return TINF_OK; } - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; -}; - -/** - * @constant - * @type {number} - */ -sizeOf.LONG = constant(4); + /* inflate stream from source to dest */ + function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; -encode.FIXED = encode.ULONG; -sizeOf.FIXED = sizeOf.ULONG; + do { + /* read final block flag */ + bfinal = tinf_getbit(d); -encode.FWORD = encode.SHORT; -sizeOf.FWORD = sizeOf.SHORT; + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); -encode.UFWORD = encode.USHORT; -sizeOf.UFWORD = sizeOf.USHORT; + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } -/** - * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. - * @param {number} - * @returns {Array} - */ -encode.LONGDATETIME = function(v) { - return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; -}; + if (res !== TINF_OK) + { throw new Error('Data error'); } -/** - * @constant - * @type {number} - */ -sizeOf.LONGDATETIME = constant(8); + } while (!bfinal); -/** - * Convert a 4-char tag to a list of 4 bytes. - * @param {string} - * @returns {Array} - */ -encode.TAG = function(v) { - check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); - return [v.charCodeAt(0), - v.charCodeAt(1), - v.charCodeAt(2), - v.charCodeAt(3)]; -}; + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; + } -/** - * @constant - * @type {number} - */ -sizeOf.TAG = constant(4); + /* -------------------- * + * -- initialization -- * + * -------------------- */ -// CFF data types /////////////////////////////////////////////////////////// + /* build fixed huffman trees */ + tinf_build_fixed_trees(sltree, sdtree); -encode.Card8 = encode.BYTE; -sizeOf.Card8 = sizeOf.BYTE; + /* build extra bits and base tables */ + tinf_build_bits_base(length_bits, length_base, 4, 3); + tinf_build_bits_base(dist_bits, dist_base, 2, 1); -encode.Card16 = encode.USHORT; -sizeOf.Card16 = sizeOf.USHORT; + /* fix a special case */ + length_bits[28] = 0; + length_base[28] = 258; -encode.OffSize = encode.BYTE; -sizeOf.OffSize = sizeOf.BYTE; + var tinyInflate = tinf_uncompress; -encode.SID = encode.USHORT; -sizeOf.SID = sizeOf.USHORT; + // The Bounding Box object -// Convert a numeric operand or charstring number to a variable-size list of bytes. -/** - * Convert a numeric operand or charstring number to a variable-size list of bytes. - * @param {number} - * @returns {Array} - */ -encode.NUMBER = function(v) { - if (v >= -107 && v <= 107) { - return [v + 139]; - } else if (v >= 108 && v <= 1131) { - v = v - 108; - return [(v >> 8) + 247, v & 0xFF]; - } else if (v >= -1131 && v <= -108) { - v = -v - 108; - return [(v >> 8) + 251, v & 0xFF]; - } else if (v >= -32768 && v <= 32767) { - return encode.NUMBER16(v); - } else { - return encode.NUMBER32(v); + function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; + } + /** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ + function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; } -}; -/** - * @param {number} - * @returns {number} - */ -sizeOf.NUMBER = function(v) { - return encode.NUMBER(v).length; -}; + /** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ + BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); + }; -/** - * Convert a signed number between -32768 and +32767 to a three-byte value. - * This ensures we always use three bytes, but is not the most compact format. - * @param {number} - * @returns {Array} - */ -encode.NUMBER16 = function(v) { - return [28, (v >> 8) & 0xFF, v & 0xFF]; -}; + /** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ + BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } + } + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } + } + }; -/** - * @constant - * @type {number} - */ -sizeOf.NUMBER16 = constant(3); + /** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ + BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); + }; -/** - * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. - * This is useful if you want to be sure you always use four bytes, - * at the expense of wasting a few bytes for smaller numbers. - * @param {number} - * @returns {Array} - */ -encode.NUMBER32 = function(v) { - return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; -}; + /** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ + BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); + }; -/** - * @constant - * @type {number} - */ -sizeOf.NUMBER32 = constant(5); + /** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ + BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box + + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; + + this.addPoint(x0, y0); + this.addPoint(x, y); + + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } -/** - * @param {number} - * @returns {Array} - */ -encode.REAL = function(v) { - var value = v.toString(); - - // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) - // This code converts it back to a number without the epsilon. - var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); - if (m) { - var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); - value = (Math.round(v * epsilon) / epsilon).toString(); - } - - var nibbles = ''; - for (var i = 0, ii = value.length; i < ii; i += 1) { - var c = value[i]; - if (c === 'e') { - nibbles += value[++i] === '-' ? 'c' : 'b'; - } else if (c === '.') { - nibbles += 'a'; - } else if (c === '-') { - nibbles += 'e'; - } else { - nibbles += c; + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } } - } - - nibbles += (nibbles.length & 1) ? 'f' : 'ff'; - var out = [30]; - for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { - out.push(parseInt(nibbles.substr(i$1, 2), 16)); - } - - return out; -}; - -/** - * @param {number} - * @returns {number} - */ -sizeOf.REAL = function(v) { - return encode.REAL(v).length; -}; + }; -encode.NAME = encode.CHARARRAY; -sizeOf.NAME = sizeOf.CHARARRAY; + /** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ + BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); + }; -encode.STRING = encode.CHARARRAY; -sizeOf.STRING = sizeOf.CHARARRAY; + // Geometric objects -/** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ -decode.UTF8 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes; - for (var j = 0; j < numChars; j++, offset += 1) { - codePoints[j] = data.getUint8(offset); + /** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ + function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; } - return String.fromCharCode.apply(null, codePoints); -}; + /** + * @param {number} x + * @param {number} y + */ + Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); + }; -/** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ -decode.UTF16 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes / 2; - for (var j = 0; j < numChars; j++, offset += 2) { - codePoints[j] = data.getUint16(offset); - } + /** + * @param {number} x + * @param {number} y + */ + Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); + }; - return String.fromCharCode.apply(null, codePoints); -}; + /** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ -/** - * Convert a JavaScript string to UTF16-BE. - * @param {string} - * @returns {Array} - */ -encode.UTF16 = function(v) { - var b = []; - for (var i = 0; i < v.length; i += 1) { - var codepoint = v.charCodeAt(i); - b[b.length] = (codepoint >> 8) & 0xFF; - b[b.length] = codepoint & 0xFF; - } + /** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ + Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); + }; - return b; -}; + /** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ -/** - * @param {string} - * @returns {number} - */ -sizeOf.UTF16 = function(v) { - return v.length * 2; -}; + /** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); + }; -// Data for converting old eight-bit Macintosh encodings to Unicode. -// This representation is optimized for decoding; encoding is slower -// and needs more memory. The assumption is that all opentype.js users -// want to open fonts, but saving a font will be comparatively rare -// so it can be more expensive. Keyed by IANA character set name. -// -// Python script for generating these strings: -// -// s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) -// print(s.encode('utf-8')) -/** - * @private - */ -var eightBitMacEncodings = { - 'x-mac-croatian': // Python: 'mac_croatian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + - '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', - 'x-mac-cyrillic': // Python: 'mac_cyrillic' - 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + - 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', - 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + - 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', - 'x-mac-greek': // Python: 'mac_greek' - 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + - 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', - 'x-mac-icelandic': // Python: 'mac_iceland' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT - 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + - 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', - 'x-mac-ce': // Python: 'mac_latin2' - 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + - 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', - macintosh: // Python: 'mac_roman' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-romanian': // Python: 'mac_romanian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-turkish': // Python: 'mac_turkish' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' -}; + /** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ -/** - * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript - * string, or 'undefined' if the encoding is unsupported. For example, we do - * not support Chinese, Japanese or Korean because these would need large - * mapping tables. - * @param {DataView} dataView - * @param {number} offset - * @param {number} dataLength - * @param {string} encoding - * @returns {string} - */ -decode.MACSTRING = function(dataView, offset, dataLength, encoding) { - var table = eightBitMacEncodings[encoding]; - if (table === undefined) { - return undefined; - } + /** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ + Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); + }; - var result = ''; - for (var i = 0; i < dataLength; i++) { - var c = dataView.getUint8(offset + i); - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c <= 0x7F) { - result += String.fromCharCode(c); - } else { - result += table[c & 0x7F]; + /** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ + Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; } - } - return result; -}; + Array.prototype.push.apply(this.commands, pathOrCommands); + }; -// Helper function for encode.MACSTRING. Returns a dictionary for mapping -// Unicode character codes to their 8-bit MacOS equivalent. This table -// is not exactly a super cheap data structure, but we do not care because -// encoding Macintosh strings is only rarely needed in typical applications. -var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); -var macEncodingCacheKeys; -var getMacEncodingTable = function (encoding) { - // Since we use encoding as a cache key for WeakMap, it has to be - // a String object and not a literal. And at least on NodeJS 2.10.1, - // WeakMap requires that the same String instance is passed for cache hits. - if (!macEncodingCacheKeys) { - macEncodingCacheKeys = {}; - for (var e in eightBitMacEncodings) { - /*jshint -W053 */ // Suppress "Do not use String as a constructor." - macEncodingCacheKeys[e] = new String(e); - } - } - - var cacheKey = macEncodingCacheKeys[encoding]; - if (cacheKey === undefined) { - return undefined; - } + /** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ + Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } + } + if (box.isEmpty()) { + box.addPoint(0, 0); + } + return box; + }; - // We can't do "if (cache.has(key)) {return cache.get(key)}" here: - // since garbage collection may run at any time, it could also kick in - // between the calls to cache.has() and cache.get(). In that case, - // we would return 'undefined' even though we do support the encoding. - if (macEncodingTableCache) { - var cachedTable = macEncodingTableCache.get(cacheKey); - if (cachedTable !== undefined) { - return cachedTable; + /** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ + Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } } - } - var decodingTable = eightBitMacEncodings[encoding]; - if (decodingTable === undefined) { - return undefined; - } + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } - var encodingTable = {}; - for (var i = 0; i < decodingTable.length; i++) { - encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; - } + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } + }; - if (macEncodingTableCache) { - macEncodingTableCache.set(cacheKey, encodingTable); - } + /** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ + Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; - return encodingTable; -}; + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } -/** - * Encodes an old-style Macintosh string. Returns a byte array upon success. - * If the requested encoding is unsupported, or if the input string contains - * a character that cannot be expressed in the encoding, the function returns - * 'undefined'. - * @param {string} str - * @param {string} encoding - * @returns {Array} - */ -encode.MACSTRING = function(str, encoding) { - var table = getMacEncodingTable(encoding); - if (table === undefined) { - return undefined; - } + function packValues() { + var arguments$1 = arguments; - var result = []; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c >= 0x80) { - c = table[c]; - if (c === undefined) { - // str contains a Unicode character that cannot be encoded - // in the requested encoding. - return undefined; + s += floatToString(v); } - } - result[i] = c; - // result.push(c); - } - return result; -}; + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } -/** - * @param {string} str - * @param {string} encoding - * @returns {number} - */ -sizeOf.MACSTRING = function(str, encoding) { - var b = encode.MACSTRING(str, encoding); - if (b !== undefined) { - return b.length; - } else { - return 0; - } -}; + return d; + }; -// Helper for encode.VARDELTAS -function isByteEncodable(value) { - return value >= -128 && value <= 127; -} - -// Helper for encode.VARDELTAS -function encodeVarDeltaRunAsZeroes(deltas, pos, result) { - var runLength = 0; - var numDeltas = deltas.length; - while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { - ++pos; - ++runLength; - } - result.push(0x80 | (runLength - 1)); - return pos; -} - -// Helper for encode.VARDELTAS -function encodeVarDeltaRunAsBytes(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; - if (!isByteEncodable(value)) { - break; + /** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ + Path.prototype.toSVG = function(decimalPlaces) { + var svg = '> 8) & 0xff, (val + 0x100) & 0xff); + // Run-time checking of preconditions. + + function fail(message) { + throw new Error(message); } - return pos; -} -/** - * Encode a list of variation adjustment deltas. - * - * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. - * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted - * when generating instances of variation fonts. - * - * @see https://www.microsoft.com/typography/otspec/gvar.htm - * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html - * @param {Array} - * @return {Array} - */ -encode.VARDELTAS = function(deltas) { - var pos = 0; - var result = []; - while (pos < deltas.length) { - var value = deltas[pos]; - if (value === 0) { - pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); - } else if (value >= -128 && value <= 127) { - pos = encodeVarDeltaRunAsBytes(deltas, pos, result); - } else { - pos = encodeVarDeltaRunAsWords(deltas, pos, result); + // Precondition function that checks if the given predicate is true. + // If not, it will throw an error. + function argument(predicate, message) { + if (!predicate) { + fail(message); } } - return result; -}; + var check = { fail: fail, argument: argument, assert: argument }; -// Convert a list of values to a CFF INDEX structure. -// The values should be objects containing name / type / value. -/** - * @param {Array} l - * @returns {Array} - */ -encode.INDEX = function(l) { - //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, - // i, v; - // Because we have to know which data type to use to encode the offsets, - // we have to go through the values twice: once to encode the data and - // calculate the offsets, then again to encode the offsets using the fitting data type. - var offset = 1; // First offset is always 1. - var offsets = [offset]; - var data = []; - for (var i = 0; i < l.length; i += 1) { - var v = encode.OBJECT(l[i]); - Array.prototype.push.apply(data, v); - offset += v.length; - offsets.push(offset); - } - - if (data.length === 0) { - return [0, 0]; - } - - var encodedOffsets = []; - var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; - var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; - for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { - var encodedOffset = offsetEncoder(offsets[i$1]); - Array.prototype.push.apply(encodedOffsets, encodedOffset); - } - - return Array.prototype.concat(encode.Card16(l.length), - encode.OffSize(offSize), - encodedOffsets, - data); -}; + // Data types used in the OpenType font file. -/** - * @param {Array} - * @returns {number} - */ -sizeOf.INDEX = function(v) { - return encode.INDEX(v).length; -}; + var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 + var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 -/** - * Convert an object to a CFF DICT structure. - * The keys should be numeric. - * The values should be objects containing name / type / value. - * @param {Object} m - * @returns {Array} - */ -encode.DICT = function(m) { - var d = []; - var keys = Object.keys(m); - var length = keys.length; + /** + * @exports opentype.decode + * @class + */ + var decode = {}; + /** + * @exports opentype.encode + * @class + */ + var encode = {}; + /** + * @exports opentype.sizeOf + * @class + */ + var sizeOf = {}; - for (var i = 0; i < length; i += 1) { - // Object.keys() return string keys, but our keys are always numeric. - var k = parseInt(keys[i], 0); - var v = m[k]; - // Value comes before the key. - d = d.concat(encode.OPERAND(v.value, v.type)); - d = d.concat(encode.OPERATOR(k)); + // Return a function that always returns the same value. + function constant(v) { + return function() { + return v; + }; } - return d; -}; - -/** - * @param {Object} - * @returns {number} - */ -sizeOf.DICT = function(m) { - return encode.DICT(m).length; -}; + // OpenType data types ////////////////////////////////////////////////////// -/** - * @param {number} - * @returns {Array} - */ -encode.OPERATOR = function(v) { - if (v < 1200) { + /** + * Convert an 8-bit unsigned integer to a list of 1 byte. + * @param {number} + * @returns {Array} + */ + encode.BYTE = function(v) { + check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); return [v]; - } else { - return [12, v - 1200]; - } -}; - -/** - * @param {Array} v - * @param {string} - * @returns {Array} - */ -encode.OPERAND = function(v, type) { - var d = []; - if (Array.isArray(type)) { - for (var i = 0; i < type.length; i += 1) { - check.argument(v.length === type.length, 'Not enough arguments given for type' + type); - d = d.concat(encode.OPERAND(v[i], type[i])); - } - } else { - if (type === 'SID') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'offset') { - // We make it easy for ourselves and always encode offsets as - // 4 bytes. This makes offset calculation for the top dict easier. - d = d.concat(encode.NUMBER32(v)); - } else if (type === 'number') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'real') { - d = d.concat(encode.REAL(v)); - } else { - throw new Error('Unknown operand type ' + type); - // FIXME Add support for booleans - } - } - - return d; -}; + }; + /** + * @constant + * @type {number} + */ + sizeOf.BYTE = constant(1); -encode.OP = encode.BYTE; -sizeOf.OP = sizeOf.BYTE; + /** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ + encode.CHAR = function(v) { + return [v.charCodeAt(0)]; + }; -// memoize charstring encoding using WeakMap if available -var wmm = typeof WeakMap === 'function' && new WeakMap(); + /** + * @constant + * @type {number} + */ + sizeOf.CHAR = constant(1); -/** - * Convert a list of CharString operations to bytes. - * @param {Array} - * @returns {Array} - */ -encode.CHARSTRING = function(ops) { - // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". - if (wmm) { - var cachedValue = wmm.get(ops); - if (cachedValue !== undefined) { - return cachedValue; + /** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ + encode.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + v = ''; + console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); + } + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); } - } - - var d = []; - var length = ops.length; - for (var i = 0; i < length; i += 1) { - var op = ops[i]; - d = d.concat(encode[op.type](op.value)); - } + return b; + }; - if (wmm) { - wmm.set(ops, d); - } + /** + * @param {Array} + * @returns {number} + */ + sizeOf.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + return 0; + } + return v.length; + }; - return d; -}; + /** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ + encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; + }; -/** - * @param {Array} - * @returns {number} - */ -sizeOf.CHARSTRING = function(ops) { - return encode.CHARSTRING(ops).length; -}; + /** + * @constant + * @type {number} + */ + sizeOf.USHORT = constant(2); -// Utility functions //////////////////////////////////////////////////////// + /** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ + encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } -/** - * Convert an object containing name / type / value to bytes. - * @param {Object} - * @returns {Array} - */ -encode.OBJECT = function(v) { - var encodingFunction = encode[v.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); - return encodingFunction(v.value); -}; + return [(v >> 8) & 0xFF, v & 0xFF]; + }; -/** - * @param {Object} - * @returns {number} - */ -sizeOf.OBJECT = function(v) { - var sizeOfFunction = sizeOf[v.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); - return sizeOfFunction(v.value); -}; + /** + * @constant + * @type {number} + */ + sizeOf.SHORT = constant(2); -/** - * Convert a table object to bytes. - * A table contains a list of fields containing the metadata (name, type and default value). - * The table itself has the field values set as attributes. - * @param {opentype.Table} - * @returns {Array} - */ -encode.TABLE = function(table) { - var d = []; - var length = table.fields.length; - var subtables = []; - var subtableOffsets = []; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var encodingFunction = encode[field.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } - - var bytes = encodingFunction(value); - - if (field.type === 'TABLE') { - subtableOffsets.push(d.length); - d = d.concat([0, 0]); - subtables.push(bytes); - } else { - d = d.concat(bytes); - } - } + /** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ + encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; - for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { - var o = subtableOffsets[i$1]; - var offset = d.length; - check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); - d[o] = offset >> 8; - d[o + 1] = offset & 0xff; - d = d.concat(subtables[i$1]); - } + /** + * @constant + * @type {number} + */ + sizeOf.UINT24 = constant(3); - return d; -}; + /** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ + encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; -/** - * @param {opentype.Table} - * @returns {number} - */ -sizeOf.TABLE = function(table) { - var numBytes = 0; - var length = table.fields.length; + /** + * @constant + * @type {number} + */ + sizeOf.ULONG = constant(4); - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var sizeOfFunction = sizeOf[field.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; + /** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ + encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); } - numBytes += sizeOfFunction(value); + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; - // Subtables take 2 more bytes for offsets. - if (field.type === 'TABLE') { - numBytes += 2; - } - } + /** + * @constant + * @type {number} + */ + sizeOf.LONG = constant(4); - return numBytes; -}; + encode.FIXED = encode.ULONG; + sizeOf.FIXED = sizeOf.ULONG; -encode.RECORD = encode.TABLE; -sizeOf.RECORD = sizeOf.TABLE; + encode.FWORD = encode.SHORT; + sizeOf.FWORD = sizeOf.SHORT; -// Merge in a list of bytes. -encode.LITERAL = function(v) { - return v; -}; + encode.UFWORD = encode.USHORT; + sizeOf.UFWORD = sizeOf.USHORT; -sizeOf.LITERAL = function(v) { - return v.length; -}; + /** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ + encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; -// Table metadata + /** + * @constant + * @type {number} + */ + sizeOf.LONGDATETIME = constant(8); -/** - * @exports opentype.Table - * @class - * @param {string} tableName - * @param {Array} fields - * @param {Object} options - * @constructor - */ -function Table(tableName, fields, options) { - // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, - // as this will result in wrong encoding order of the coverage data on serialization to bytes. - // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. - if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { - for (var i = 0; i < fields.length; i += 1) { - var field = fields[i]; - this[field.name] = field.value; - } - } + /** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ + encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; + }; - this.tableName = tableName; - this.fields = fields; - if (options) { - var optionKeys = Object.keys(options); - for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { - var k = optionKeys[i$1]; - var v = options[k]; - if (this[k] !== undefined) { - this[k] = v; - } - } - } -} + /** + * @constant + * @type {number} + */ + sizeOf.TAG = constant(4); -/** - * Encodes the table and returns an array of bytes - * @return {Array} - */ -Table.prototype.encode = function() { - return encode.TABLE(this); -}; + // CFF data types /////////////////////////////////////////////////////////// -/** - * Get the size of the table. - * @return {number} - */ -Table.prototype.sizeOf = function() { - return sizeOf.TABLE(this); -}; + encode.Card8 = encode.BYTE; + sizeOf.Card8 = sizeOf.BYTE; -/** - * @private - */ -function ushortList(itemName, list, count) { - if (count === undefined) { - count = list.length; - } - var fields = new Array(list.length + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < list.length; i++) { - fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; - } - return fields; -} + encode.Card16 = encode.USHORT; + sizeOf.Card16 = sizeOf.USHORT; -/** - * @private - */ -function tableList(itemName, records, itemCallback) { - var count = records.length; - var fields = new Array(count + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; - } - return fields; -} + encode.OffSize = encode.BYTE; + sizeOf.OffSize = sizeOf.BYTE; -/** - * @private - */ -function recordList(itemName, records, itemCallback) { - var count = records.length; - var fields = []; - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields = fields.concat(itemCallback(records[i], i)); - } - return fields; -} + encode.SID = encode.USHORT; + sizeOf.SID = sizeOf.USHORT; -// Common Layout Tables + // Convert a numeric operand or charstring number to a variable-size list of bytes. + /** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ + encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } + }; -/** - * @exports opentype.Coverage - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ -function Coverage(coverageTable) { - if (coverageTable.format === 1) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 1}] - .concat(ushortList('glyph', coverageTable.glyphs)) - ); - } else if (coverageTable.format === 2) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 2}] - .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { - return [ - {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, - {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, - {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; - })) - ); - } else { - check.assert(false, 'Coverage format must be 1 or 2.'); - } -} -Coverage.prototype = Object.create(Table.prototype); -Coverage.prototype.constructor = Coverage; - -function ScriptList(scriptListTable) { - Table.call(this, 'scriptListTable', - recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { - var script = scriptRecord.script; - var defaultLangSys = script.defaultLangSys; - check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); - return [ - {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, - {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ - {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] - .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} - ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { - var langSys = langSysRecord.langSys; - return [ - {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, - {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} - ].concat(ushortList('featureIndex', langSys.featureIndexes)))} - ]; - })))} - ]; - }) - ); -} -ScriptList.prototype = Object.create(Table.prototype); -ScriptList.prototype.constructor = ScriptList; + /** + * @param {number} + * @returns {number} + */ + sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; + }; -/** - * @exports opentype.FeatureList - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ -function FeatureList(featureListTable) { - Table.call(this, 'featureListTable', - recordList('featureRecord', featureListTable, function(featureRecord, i) { - var feature = featureRecord.feature; - return [ - {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, - {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ - {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} - ]; - }) - ); -} -FeatureList.prototype = Object.create(Table.prototype); -FeatureList.prototype.constructor = FeatureList; + /** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ + encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; + }; -/** - * @exports opentype.LookupList - * @class - * @param {opentype.Table} - * @param {Object} - * @constructor - * @extends opentype.Table - */ -function LookupList(lookupListTable, subtableMakers) { - Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { - var subtableCallback = subtableMakers[lookupTable.lookupType]; - check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); - return new Table('lookupTable', [ - {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, - {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} - ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); - })); -} -LookupList.prototype = Object.create(Table.prototype); -LookupList.prototype.constructor = LookupList; - -// Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) -// Don't use offsets inside Records (probable bug), only in Tables. -var table = { - Table: Table, - Record: Table, - Coverage: Coverage, - ScriptList: ScriptList, - FeatureList: FeatureList, - LookupList: LookupList, - ushortList: ushortList, - tableList: tableList, - recordList: recordList, -}; + /** + * @constant + * @type {number} + */ + sizeOf.NUMBER16 = constant(3); -// Parsing utility functions - -// Retrieve an unsigned byte from the DataView. -function getByte(dataView, offset) { - return dataView.getUint8(offset); -} - -// Retrieve an unsigned 16-bit short from the DataView. -// The value is stored in big endian. -function getUShort(dataView, offset) { - return dataView.getUint16(offset, false); -} - -// Retrieve a signed 16-bit short from the DataView. -// The value is stored in big endian. -function getShort(dataView, offset) { - return dataView.getInt16(offset, false); -} - -// Retrieve an unsigned 32-bit long from the DataView. -// The value is stored in big endian. -function getULong(dataView, offset) { - return dataView.getUint32(offset, false); -} - -// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. -// The value is stored in big endian. -function getFixed(dataView, offset) { - var decimal = dataView.getInt16(offset, false); - var fraction = dataView.getUint16(offset + 2, false); - return decimal + fraction / 65535; -} - -// Retrieve a 4-character tag from the DataView. -// Tags are used to identify tables. -function getTag(dataView, offset) { - var tag = ''; - for (var i = offset; i < offset + 4; i += 1) { - tag += String.fromCharCode(dataView.getInt8(i)); - } - - return tag; -} - -// Retrieve an offset from the DataView. -// Offsets are 1 to 4 bytes in length, depending on the offSize argument. -function getOffset(dataView, offset, offSize) { - var v = 0; - for (var i = 0; i < offSize; i += 1) { - v <<= 8; - v += dataView.getUint8(offset + i); - } - - return v; -} - -// Retrieve a number of bytes from start offset to the end offset from the DataView. -function getBytes(dataView, startOffset, endOffset) { - var bytes = []; - for (var i = startOffset; i < endOffset; i += 1) { - bytes.push(dataView.getUint8(i)); - } - - return bytes; -} - -// Convert the list of bytes to a string. -function bytesToString(bytes) { - var s = ''; - for (var i = 0; i < bytes.length; i += 1) { - s += String.fromCharCode(bytes[i]); - } - - return s; -} - -var typeOffsets = { - byte: 1, - uShort: 2, - short: 2, - uLong: 4, - fixed: 4, - longDateTime: 8, - tag: 4 -}; + /** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ + encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; -// A stateful parser that changes the offset whenever a value is retrieved. -// The data is a DataView. -function Parser(data, offset) { - this.data = data; - this.offset = offset; - this.relativeOffset = 0; -} - -Parser.prototype.parseByte = function() { - var v = this.data.getUint8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; -}; + /** + * @constant + * @type {number} + */ + sizeOf.NUMBER32 = constant(5); -Parser.prototype.parseChar = function() { - var v = this.data.getInt8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; -}; + /** + * @param {number} + * @returns {Array} + */ + encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } -Parser.prototype.parseCard8 = Parser.prototype.parseByte; + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } -Parser.prototype.parseUShort = function() { - var v = this.data.getUint16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; -}; + return out; + }; -Parser.prototype.parseCard16 = Parser.prototype.parseUShort; -Parser.prototype.parseSID = Parser.prototype.parseUShort; -Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + /** + * @param {number} + * @returns {number} + */ + sizeOf.REAL = function(v) { + return encode.REAL(v).length; + }; -Parser.prototype.parseShort = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; -}; + encode.NAME = encode.CHARARRAY; + sizeOf.NAME = sizeOf.CHARARRAY; -Parser.prototype.parseF2Dot14 = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; - this.relativeOffset += 2; - return v; -}; + encode.STRING = encode.CHARARRAY; + sizeOf.STRING = sizeOf.CHARARRAY; -Parser.prototype.parseULong = function() { - var v = getULong(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; -}; + /** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ + decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } -Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + return String.fromCharCode.apply(null, codePoints); + }; -Parser.prototype.parseFixed = function() { - var v = getFixed(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; -}; + /** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ + decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } -Parser.prototype.parseString = function(length) { - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - var string = ''; - this.relativeOffset += length; - for (var i = 0; i < length; i++) { - string += String.fromCharCode(dataView.getUint8(offset + i)); - } + return String.fromCharCode.apply(null, codePoints); + }; - return string; -}; + /** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ + encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } -Parser.prototype.parseTag = function() { - return this.parseString(4); -}; + return b; + }; -// LONGDATETIME is a 64-bit integer. -// JavaScript and unix timestamps traditionally use 32 bits, so we -// only take the last 32 bits. -// + Since until 2038 those bits will be filled by zeros we can ignore them. -Parser.prototype.parseLongDateTime = function() { - var v = getULong(this.data, this.offset + this.relativeOffset + 4); - // Subtract seconds between 01/01/1904 and 01/01/1970 - // to convert Apple Mac timestamp to Standard Unix timestamp - v -= 2082844800; - this.relativeOffset += 8; - return v; -}; + /** + * @param {string} + * @returns {number} + */ + sizeOf.UTF16 = function(v) { + return v.length * 2; + }; -Parser.prototype.parseVersion = function(minorBase) { - var major = getUShort(this.data, this.offset + this.relativeOffset); + // Data for converting old eight-bit Macintosh encodings to Unicode. + // This representation is optimized for decoding; encoding is slower + // and needs more memory. The assumption is that all opentype.js users + // want to open fonts, but saving a font will be comparatively rare + // so it can be more expensive. Keyed by IANA character set name. + // + // Python script for generating these strings: + // + // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) + // print(s.encode('utf-8')) + /** + * @private + */ + var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' + }; - // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 - // Default returns the correct number if minor = 0xN000 where N is 0-9 - // Set minorBase to 1 for tables that use minor = N where N is 0-9 - var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); - this.relativeOffset += 4; - if (minorBase === undefined) { minorBase = 0x1000; } - return major + minor / minorBase / 10; -}; + /** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ + decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; + } + } -Parser.prototype.skip = function(type, amount) { - if (amount === undefined) { - amount = 1; - } + return result; + }; - this.relativeOffset += typeOffsets[type] * amount; -}; + // Helper function for encode.MACSTRING. Returns a dictionary for mapping + // Unicode character codes to their 8-bit MacOS equivalent. This table + // is not exactly a super cheap data structure, but we do not care because + // encoding Macintosh strings is only rarely needed in typical applications. + var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); + var macEncodingCacheKeys; + var getMacEncodingTable = function (encoding) { + // Since we use encoding as a cache key for WeakMap, it has to be + // a String object and not a literal. And at least on NodeJS 2.10.1, + // WeakMap requires that the same String instance is passed for cache hits. + if (!macEncodingCacheKeys) { + macEncodingCacheKeys = {}; + for (var e in eightBitMacEncodings) { + /*jshint -W053 */ // Suppress "Do not use String as a constructor." + macEncodingCacheKeys[e] = new String(e); + } + } -///// Parsing lists and records /////////////////////////////// + var cacheKey = macEncodingCacheKeys[encoding]; + if (cacheKey === undefined) { + return undefined; + } -// Parse a list of 32 bit unsigned integers. -Parser.prototype.parseULongList = function(count) { - if (count === undefined) { count = this.parseULong(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint32(offset); - offset += 4; - } + // We can't do "if (cache.has(key)) {return cache.get(key)}" here: + // since garbage collection may run at any time, it could also kick in + // between the calls to cache.has() and cache.get(). In that case, + // we would return 'undefined' even though we do support the encoding. + if (macEncodingTableCache) { + var cachedTable = macEncodingTableCache.get(cacheKey); + if (cachedTable !== undefined) { + return cachedTable; + } + } - this.relativeOffset += count * 4; - return offsets; -}; + var decodingTable = eightBitMacEncodings[encoding]; + if (decodingTable === undefined) { + return undefined; + } -// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream -// or provided as an argument. -Parser.prototype.parseOffset16List = -Parser.prototype.parseUShortList = function(count) { - if (count === undefined) { count = this.parseUShort(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint16(offset); - offset += 2; - } - - this.relativeOffset += count * 2; - return offsets; -}; + var encodingTable = {}; + for (var i = 0; i < decodingTable.length; i++) { + encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; + } -// Parses a list of 16 bit signed integers. -Parser.prototype.parseShortList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getInt16(offset); - offset += 2; - } + if (macEncodingTableCache) { + macEncodingTableCache.set(cacheKey, encodingTable); + } - this.relativeOffset += count * 2; - return list; -}; + return encodingTable; + }; -// Parses a list of bytes. -Parser.prototype.parseByteList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getUint8(offset++); - } + /** + * Encodes an old-style Macintosh string. Returns a byte array upon success. + * If the requested encoding is unsupported, or if the input string contains + * a character that cannot be expressed in the encoding, the function returns + * 'undefined'. + * @param {string} str + * @param {string} encoding + * @returns {Array} + */ + encode.MACSTRING = function(str, encoding) { + var table = getMacEncodingTable(encoding); + if (table === undefined) { + return undefined; + } + + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c >= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } + } + result[i] = c; + // result.push(c); + } - this.relativeOffset += count; - return list; -}; + return result; + }; -/** - * Parse a list of items. - * Record count is optional, if omitted it is read from the stream. - * itemCallback is one of the Parser methods. - */ -Parser.prototype.parseList = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseUShort(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; -}; + /** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ + sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; + } + }; -Parser.prototype.parseList32 = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseULong(); + // Helper for encode.VARDELTAS + function isByteEncodable(value) { + return value >= -128 && value <= 127; } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; -}; - -/** - * Parse a list of records. - * Record count is optional, if omitted it is read from the stream. - * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - */ -Parser.prototype.parseRecordList = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseUShort(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; - } - return records; -}; -Parser.prototype.parseRecordList32 = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseULong(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; } - records[i] = rec; - } - return records; -}; - -// Parse a data structure into an object -// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } -Parser.prototype.parseStruct = function(description) { - if (typeof description === 'function') { - return description.call(this); - } else { - var fields = Object.keys(description); - var struct = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = description[fieldName]; - struct[fieldName] = fieldType.call(this); - } - return struct; + result.push(0x80 | (runLength - 1)); + return pos; } -}; -/** - * Parse a GPOS valueRecord - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat is optional, if omitted it is read from the stream. - */ -Parser.prototype.parseValueRecord = function(valueFormat) { - if (valueFormat === undefined) { - valueFormat = this.parseUShort(); - } - if (valueFormat === 0) { - // valueFormat2 in kerning pairs is most often 0 - // in this case return undefined instead of an empty object, to save space - return; - } - var valueRecord = {}; - - if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } - if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } - if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } - if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } - - // Device table (non-variable font) / VariationIndex table (variable font) not supported - // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls - if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } - - return valueRecord; -}; + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } -/** - * Parse a list of GPOS valueRecords - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat and valueCount are read from the stream. - */ -Parser.prototype.parseValueRecordList = function() { - var valueFormat = this.parseUShort(); - var valueCount = this.parseUShort(); - var values = new Array(valueCount); - for (var i = 0; i < valueCount; i++) { - values[i] = this.parseValueRecord(valueFormat); - } - return values; -}; + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } -Parser.prototype.parsePointer = function(description) { - var structOffset = this.parseOffset16(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; } - return undefined; -}; -Parser.prototype.parsePointer32 = function(description) { - var structOffset = this.parseOffset32(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); - } - return undefined; -}; + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; -/** - * Parse a list of offsets to lists of 16-bit integers, - * or a list of offsets to lists of offsets to any kind of items. - * If itemCallback is not provided, a list of list of UShort is assumed. - * If provided, itemCallback is called on each item and must parse the item. - * See examples in tables/gsub.js - */ -Parser.prototype.parseListOfLists = function(itemCallback) { - var offsets = this.parseOffset16List(); - var count = offsets.length; - var relativeOffset = this.relativeOffset; - var list = new Array(count); - for (var i = 0; i < count; i++) { - var start = offsets[i]; - if (start === 0) { - // NULL offset - // Add i as owned property to list. Convenient with assert. - list[i] = undefined; - continue; - } - this.relativeOffset = start; - if (itemCallback) { - var subOffsets = this.parseOffset16List(); - var subList = new Array(subOffsets.length); - for (var j = 0; j < subOffsets.length; j++) { - this.relativeOffset = start + subOffsets[j]; - subList[j] = itemCallback.call(this); - } - list[i] = subList; - } else { - list[i] = this.parseUShortList(); - } - } - this.relativeOffset = relativeOffset; - return list; -}; + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; + } -///// Complex tables parsing ////////////////////////////////// + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; + } -// Parse a coverage table in a GSUB, GPOS or GDEF table. -// https://www.microsoft.com/typography/OTSPEC/chapter2.htm -// parser.offset must point to the start of the table containing the coverage. -Parser.prototype.parseCoverage = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - var count = this.parseUShort(); - if (format === 1) { - return { - format: 1, - glyphs: this.parseUShortList(count) - }; - } else if (format === 2) { - var ranges = new Array(count); - for (var i = 0; i < count; i++) { - ranges[i] = { - start: this.parseUShort(), - end: this.parseUShort(), - index: this.parseUShort() - }; + ++pos; + ++runLength; } - return { - format: 2, - ranges: ranges - }; - } - throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); -}; - -// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. -// https://www.microsoft.com/typography/OTSPEC/chapter2.htm -Parser.prototype.parseClassDef = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - if (format === 1) { - return { - format: 1, - startGlyph: this.parseUShort(), - classes: this.parseUShortList() - }; - } else if (format === 2) { - return { - format: 2, - ranges: this.parseRecordList({ - start: Parser.uShort, - end: Parser.uShort, - classId: Parser.uShort - }) - }; + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; } - throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); -}; -///// Static methods /////////////////////////////////// -// These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. - -Parser.list = function(count, itemCallback) { - return function() { - return this.parseList(count, itemCallback); + /** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ + encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); + } + } + return result; }; -}; -Parser.list32 = function(count, itemCallback) { - return function() { - return this.parseList32(count, itemCallback); + // Convert a list of values to a CFF INDEX structure. + // The values should be objects containing name / type / value. + /** + * @param {Array} l + * @returns {Array} + */ + encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); }; -}; -Parser.recordList = function(count, recordDescription) { - return function() { - return this.parseRecordList(count, recordDescription); + /** + * @param {Array} + * @returns {number} + */ + sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; }; -}; -Parser.recordList32 = function(count, recordDescription) { - return function() { - return this.parseRecordList32(count, recordDescription); - }; -}; + /** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ + encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; -Parser.pointer = function(description) { - return function() { - return this.parsePointer(description); - }; -}; + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } -Parser.pointer32 = function(description) { - return function() { - return this.parsePointer32(description); + return d; }; -}; -Parser.tag = Parser.prototype.parseTag; -Parser.byte = Parser.prototype.parseByte; -Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; -Parser.uShortList = Parser.prototype.parseUShortList; -Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; -Parser.uLongList = Parser.prototype.parseULongList; -Parser.struct = Parser.prototype.parseStruct; -Parser.coverage = Parser.prototype.parseCoverage; -Parser.classDef = Parser.prototype.parseClassDef; - -///// Script, Feature, Lookup lists /////////////////////////////////////////////// -// https://www.microsoft.com/typography/OTSPEC/chapter2.htm - -var langSysTable = { - reserved: Parser.uShort, - reqFeatureIndex: Parser.uShort, - featureIndexes: Parser.uShortList -}; + /** + * @param {Object} + * @returns {number} + */ + sizeOf.DICT = function(m) { + return encode.DICT(m).length; + }; -Parser.prototype.parseScriptList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - script: Parser.pointer({ - defaultLangSys: Parser.pointer(langSysTable), - langSysRecords: Parser.recordList({ - tag: Parser.tag, - langSys: Parser.pointer(langSysTable) - }) - }) - })) || []; -}; + /** + * @param {number} + * @returns {Array} + */ + encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } + }; -Parser.prototype.parseFeatureList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - feature: Parser.pointer({ - featureParams: Parser.offset16, - lookupListIndexes: Parser.uShortList - }) - })) || []; -}; + /** + * @param {Array} v + * @param {string} + * @returns {Array} + */ + encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } -Parser.prototype.parseLookupList = function(lookupTableParsers) { - return this.parsePointer(Parser.list(Parser.pointer(function() { - var lookupType = this.parseUShort(); - check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); - var lookupFlag = this.parseUShort(); - var useMarkFilteringSet = lookupFlag & 0x10; - return { - lookupType: lookupType, - lookupFlag: lookupFlag, - subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), - markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined - }; - }))) || []; -}; + return d; + }; -Parser.prototype.parseFeatureVariationsList = function() { - return this.parsePointer32(function() { - var majorVersion = this.parseUShort(); - var minorVersion = this.parseUShort(); - check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); - var featureVariations = this.parseRecordList32({ - conditionSetOffset: Parser.offset32, - featureTableSubstitutionOffset: Parser.offset32 - }); - return featureVariations; - }) || []; -}; + encode.OP = encode.BYTE; + sizeOf.OP = sizeOf.BYTE; -var parse = { - getByte: getByte, - getCard8: getByte, - getUShort: getUShort, - getCard16: getUShort, - getShort: getShort, - getULong: getULong, - getFixed: getFixed, - getTag: getTag, - getOffset: getOffset, - getBytes: getBytes, - bytesToString: bytesToString, - Parser: Parser, -}; + // memoize charstring encoding using WeakMap if available + var wmm = typeof WeakMap === 'function' && new WeakMap(); -// The `cmap` table stores the mappings from characters to glyphs. - -function parseCmapTableFormat12(cmap, p) { - //Skip reserved. - p.parseUShort(); - - // Length in bytes of the sub-tables. - cmap.length = p.parseULong(); - cmap.language = p.parseULong(); - - var groupCount; - cmap.groupCount = groupCount = p.parseULong(); - cmap.glyphIndexMap = {}; - - for (var i = 0; i < groupCount; i += 1) { - var startCharCode = p.parseULong(); - var endCharCode = p.parseULong(); - var startGlyphId = p.parseULong(); - - for (var c = startCharCode; c <= endCharCode; c += 1) { - cmap.glyphIndexMap[c] = startGlyphId; - startGlyphId++; - } - } -} - -function parseCmapTableFormat4(cmap, p, data, start, offset) { - // Length in bytes of the sub-tables. - cmap.length = p.parseUShort(); - cmap.language = p.parseUShort(); - - // segCount is stored x 2. - var segCount; - cmap.segCount = segCount = p.parseUShort() >> 1; - - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - - // The "unrolled" mapping from character codes to glyph indices. - cmap.glyphIndexMap = {}; - var endCountParser = new parse.Parser(data, start + offset + 14); - var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); - var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); - var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); - var glyphIndexOffset = start + offset + 16 + segCount * 8; - for (var i = 0; i < segCount - 1; i += 1) { - var glyphIndex = (void 0); - var endCount = endCountParser.parseUShort(); - var startCount = startCountParser.parseUShort(); - var idDelta = idDeltaParser.parseShort(); - var idRangeOffset = idRangeOffsetParser.parseUShort(); - for (var c = startCount; c <= endCount; c += 1) { - if (idRangeOffset !== 0) { - // The idRangeOffset is relative to the current position in the idRangeOffset array. - // Take the current offset in the idRangeOffset array. - glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); - - // Add the value of the idRangeOffset, which will move us into the glyphIndex array. - glyphIndexOffset += idRangeOffset; - - // Then add the character index of the current segment, multiplied by 2 for USHORTs. - glyphIndexOffset += (c - startCount) * 2; - glyphIndex = parse.getUShort(data, glyphIndexOffset); - if (glyphIndex !== 0) { - glyphIndex = (glyphIndex + idDelta) & 0xFFFF; - } - } else { - glyphIndex = (c + idDelta) & 0xFFFF; + /** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ + encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; } - - cmap.glyphIndexMap[c] = glyphIndex; } - } -} -// Parse the `cmap` table. This table stores the mappings from characters to glyphs. -// There are many available formats, but we only support the Windows format 4 and 12. -// This function returns a `CmapEncoding` object or null if no supported format could be found. -function parseCmapTable(data, start) { - var cmap = {}; - cmap.version = parse.getUShort(data, start); - check.argument(cmap.version === 0, 'cmap table version should be 0.'); + var d = []; + var length = ops.length; - // The cmap table can contain many sub-tables, each with their own format. - // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. - cmap.numTables = parse.getUShort(data, start + 2); - var offset = -1; - for (var i = cmap.numTables - 1; i >= 0; i -= 1) { - var platformId = parse.getUShort(data, start + 4 + (i * 8)); - var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); - if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || - (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { - offset = parse.getULong(data, start + 4 + (i * 8) + 4); - break; + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); } - } - - if (offset === -1) { - // There is no cmap table in the font that we support. - throw new Error('No valid cmap sub-tables found.'); - } - var p = new parse.Parser(data, start + offset); - cmap.format = p.parseUShort(); + if (wmm) { + wmm.set(ops, d); + } - if (cmap.format === 12) { - parseCmapTableFormat12(cmap, p); - } else if (cmap.format === 4) { - parseCmapTableFormat4(cmap, p, data, start, offset); - } else { - throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); - } + return d; + }; - return cmap; -} + /** + * @param {Array} + * @returns {number} + */ + sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; + }; -function addSegment(t, code, glyphIndex) { - t.segments.push({ - end: code, - start: code, - delta: -(code - glyphIndex), - offset: 0, - glyphIndex: glyphIndex - }); -} - -function addTerminatorSegment(t) { - t.segments.push({ - end: 0xFFFF, - start: 0xFFFF, - delta: 1, - offset: 0 - }); -} + // Utility functions //////////////////////////////////////////////////////// -// Make cmap table, format 4 by default, 12 if needed only -function makeCmapTable(glyphs) { - // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) - var isPlan0Only = true; - var i; - - // Check if we need to add cmap format 12 or if format 4 only is fine - for (i = glyphs.length - 1; i > 0; i -= 1) { - var g = glyphs.get(i); - if (g.unicode > 65535) { - console.log('Adding CMAP format 12 (needed!)'); - isPlan0Only = false; - break; - } - } - - var cmapTable = [ - {name: 'version', type: 'USHORT', value: 0}, - {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, - - // CMAP 4 header - {name: 'platformID', type: 'USHORT', value: 3}, - {name: 'encodingID', type: 'USHORT', value: 1}, - {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} - ]; + /** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ + encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); + }; - if (!isPlan0Only) - { cmapTable = cmapTable.concat([ - // CMAP 12 header - {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere - {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, - {name: 'cmap12Offset', type: 'ULONG', value: 0} - ]); } + /** + * @param {Object} + * @returns {number} + */ + sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); + }; - cmapTable = cmapTable.concat([ - // CMAP 4 Subtable - {name: 'format', type: 'USHORT', value: 4}, - {name: 'cmap4Length', type: 'USHORT', value: 0}, - {name: 'language', type: 'USHORT', value: 0}, - {name: 'segCountX2', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); + /** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ + encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } - var t = new table.Table('cmap', cmapTable); + var bytes = encodingFunction(value); - t.segments = []; - for (i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - addSegment(t, glyph.unicodes[j], i); + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); + } else { + d = d.concat(bytes); + } } - t.segments = t.segments.sort(function (a, b) { - return a.start - b.start; - }); - } - - addTerminatorSegment(t); - - var segCount = t.segments.length; - var segCountToRemove = 0; - - // CMAP 4 - // Set up parallel segment arrays. - var endCounts = []; - var startCounts = []; - var idDeltas = []; - var idRangeOffsets = []; - var glyphIds = []; - - // CMAP 12 - var cmap12Groups = []; + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } - // Reminder this loop is not following the specification at 100% - // The specification -> find suites of characters and make a group - // Here we're doing one group for each letter - // Doing as the spec can save 8 times (or more) space - for (i = 0; i < segCount; i += 1) { - var segment = t.segments[i]; + return d; + }; - // CMAP 4 - if (segment.end <= 65535 && segment.start <= 65535) { - endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); - startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); - idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); - idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); - if (segment.glyphId !== undefined) { - glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + /** + * @param {opentype.Table} + * @returns {number} + */ + sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; } - } else { - // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 - segCountToRemove += 1; - } - // CMAP 12 - // Skip Terminator Segment - if (!isPlan0Only && segment.glyphIndex !== undefined) { - cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); - } - } - - // CMAP 4 Subtable - t.segCountX2 = (segCount - segCountToRemove) * 2; - t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; - t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); - t.rangeShift = t.segCountX2 - t.searchRange; - - t.fields = t.fields.concat(endCounts); - t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); - t.fields = t.fields.concat(startCounts); - t.fields = t.fields.concat(idDeltas); - t.fields = t.fields.concat(idRangeOffsets); - t.fields = t.fields.concat(glyphIds); - - t.cmap4Length = 14 + // Subtable header - endCounts.length * 2 + - 2 + // reservedPad - startCounts.length * 2 + - idDeltas.length * 2 + - idRangeOffsets.length * 2 + - glyphIds.length * 2; - - if (!isPlan0Only) { - // CMAP 12 Subtable - var cmap12Length = 16 + // Subtable header - cmap12Groups.length * 4; - - t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; - t.fields = t.fields.concat([ - {name: 'cmap12Format', type: 'USHORT', value: 12}, - {name: 'cmap12Reserved', type: 'USHORT', value: 0}, - {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, - {name: 'cmap12Language', type: 'ULONG', value: 0}, - {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} - ]); + numBytes += sizeOfFunction(value); - t.fields = t.fields.concat(cmap12Groups); - } - - return t; -} - -var cmap = { parse: parseCmapTable, make: makeCmapTable }; - -// Glyph encoding - -var cffStandardStrings = [ - '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', - 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', - 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', - 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', - 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', - 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', - 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', - 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', - 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', - 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', - 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', - 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', - 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', - 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', - 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', - 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', - 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', - 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', - 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', - 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', - 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', - 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', - 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', - 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', - 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', - 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', - 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', - 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', - 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', - 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', - 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', - '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; - -var cffStandardEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', - 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', - 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', - 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', - 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', - 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', - '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', - 'lslash', 'oslash', 'oe', 'germandbls']; - -var cffExpertEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', - 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', - 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', - 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', - 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', - 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', - 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', - 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', - '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', - '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', - 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', - 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', - 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', - 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', - 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', - 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', - 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', - 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; - -var standardNames = [ - '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', - 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', - 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', - 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', - 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', - 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', - 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', - 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', - 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', - 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', - 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', - 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', - 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', - 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', - 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', - 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', - 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', - 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', - 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', - 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', - 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', - 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', - 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; - -/** - * This is the encoding used for fonts created from scratch. - * It loops through all glyphs and finds the appropriate unicode value. - * Since it's linear time, other encodings will be faster. - * @exports opentype.DefaultEncoding - * @class - * @constructor - * @param {opentype.Font} - */ -function DefaultEncoding(font) { - this.font = font; -} - -DefaultEncoding.prototype.charToGlyphIndex = function(c) { - var code = c.codePointAt(0); - var glyphs = this.font.glyphs; - if (glyphs) { - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - if (glyph.unicodes[j] === code) { - return i; - } + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; } } - } - return null; -}; -/** - * @exports opentype.CmapEncoding - * @class - * @constructor - * @param {Object} cmap - a object with the cmap encoded data - */ -function CmapEncoding(cmap) { - this.cmap = cmap; -} + return numBytes; + }; -/** - * @param {string} c - the character - * @return {number} The glyph index. - */ -CmapEncoding.prototype.charToGlyphIndex = function(c) { - return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; -}; + encode.RECORD = encode.TABLE; + sizeOf.RECORD = sizeOf.TABLE; -/** - * @exports opentype.CffEncoding - * @class - * @constructor - * @param {string} encoding - The encoding - * @param {Array} charset - The character set. - */ -function CffEncoding(encoding, charset) { - this.encoding = encoding; - this.charset = charset; -} + // Merge in a list of bytes. + encode.LITERAL = function(v) { + return v; + }; -/** - * @param {string} s - The character - * @return {number} The index. - */ -CffEncoding.prototype.charToGlyphIndex = function(s) { - var code = s.codePointAt(0); - var charName = this.encoding[code]; - return this.charset.indexOf(charName); -}; + sizeOf.LITERAL = function(v) { + return v.length; + }; -/** - * @exports opentype.GlyphNames - * @class - * @constructor - * @param {Object} post - */ -function GlyphNames(post) { - switch (post.version) { - case 1: - this.names = standardNames.slice(); - break; - case 2: - this.names = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - if (post.glyphNameIndex[i] < standardNames.length) { - this.names[i] = standardNames[post.glyphNameIndex[i]]; - } else { - this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; - } - } + // Table metadata - break; - case 2.5: - this.names = new Array(post.numberOfGlyphs); - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + /** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ + function Table(tableName, fields, options) { + // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, + // as this will result in wrong encoding order of the coverage data on serialization to bytes. + // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. + if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; } + } - break; - case 3: - this.names = []; - break; - default: - this.names = []; - break; + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } } -} -/** - * Gets the index of a glyph by name. - * @param {string} name - The glyph name - * @return {number} The index - */ -GlyphNames.prototype.nameToGlyphIndex = function(name) { - return this.names.indexOf(name); -}; - -/** - * @param {number} gid - * @return {string} - */ -GlyphNames.prototype.glyphIndexToName = function(gid) { - return this.names[gid]; -}; + /** + * Encodes the table and returns an array of bytes + * @return {Array} + */ + Table.prototype.encode = function() { + return encode.TABLE(this); + }; -function addGlyphNamesAll(font) { - var glyph; - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); + /** + * Get the size of the table. + * @return {number} + */ + Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); + }; - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - glyph = font.glyphs.get(glyphIndex); - glyph.addUnicode(parseInt(c)); + /** + * @private + */ + function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; } - for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { - glyph = font.glyphs.get(i$1); - if (font.cffEncoding) { - if (font.isCIDFont) { - glyph.name = 'gid' + i$1; - } else { - glyph.name = font.cffEncoding.charset[i$1]; - } - } else if (font.glyphNames.names) { - glyph.name = font.glyphNames.glyphIndexToName(i$1); + /** + * @private + */ + function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; } + return fields; } -} -function addGlyphNamesToUnicodeMap(font) { - font._IndexToUnicodeMap = {}; + /** + * @private + */ + function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; + } - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); + // Common Layout Tables - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - if (font._IndexToUnicodeMap[glyphIndex] === undefined) { - font._IndexToUnicodeMap[glyphIndex] = { - unicodes: [parseInt(c)] - }; + /** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ + function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else if (coverageTable.format === 2) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 2}] + .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { + return [ + {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, + {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; + })) + ); } else { - font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + check.assert(false, 'Coverage format must be 1 or 2.'); } } -} + Coverage.prototype = Object.create(Table.prototype); + Coverage.prototype.constructor = Coverage; -/** - * @alias opentype.addGlyphNames - * @param {opentype.Font} - * @param {Object} - */ -function addGlyphNames(font, opt) { - if (opt.lowMemory) { - addGlyphNamesToUnicodeMap(font); - } else { - addGlyphNamesAll(font); + function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); } -} - -// Drawing utility functions. - -// Draw a line on the given context from point `x1,y1` to point `x2,y2`. -function line(ctx, x1, y1, x2, y2) { - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); -} - -var draw = { line: line }; - -// The Glyph object -// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency - -function getPathDefinition(glyph, path) { - var _path = path || new Path(); - return { - configurable: true, - - get: function() { - if (typeof _path === 'function') { - _path = _path(); - } + ScriptList.prototype = Object.create(Table.prototype); + ScriptList.prototype.constructor = ScriptList; - return _path; - }, + /** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ + function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); + } + FeatureList.prototype = Object.create(Table.prototype); + FeatureList.prototype.constructor = FeatureList; - set: function(p) { - _path = p; - } + /** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ + function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); + } + LookupList.prototype = Object.create(Table.prototype); + LookupList.prototype.constructor = LookupList; + + // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) + // Don't use offsets inside Records (probable bug), only in Tables. + var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, }; -} -/** - * @typedef GlyphOptions - * @type Object - * @property {string} [name] - The glyph name - * @property {number} [unicode] - * @property {Array} [unicodes] - * @property {number} [xMin] - * @property {number} [yMin] - * @property {number} [xMax] - * @property {number} [yMax] - * @property {number} [advanceWidth] - */ - -// A Glyph is an individual mark that often corresponds to a character. -// Some glyphs, such as ligatures, are a combination of many characters. -// Glyphs are the basic building blocks of a font. -// -// The `Glyph` class contains utility methods for drawing the path and its points. -/** - * @exports opentype.Glyph - * @class - * @param {GlyphOptions} - * @constructor - */ -function Glyph(options) { - // By putting all the code on a prototype function (which is only declared once) - // we reduce the memory requirements for larger fonts by some 2% - this.bindConstructorValues(options); -} - -/** - * @param {GlyphOptions} - */ -Glyph.prototype.bindConstructorValues = function(options) { - this.index = options.index || 0; - // These three values cannot be deferred for memory optimization: - this.name = options.name || null; - this.unicode = options.unicode || undefined; - this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + // Parsing utility functions - // But by binding these values only when necessary, we reduce can - // the memory requirements by almost 3% for larger fonts. - if ('xMin' in options) { - this.xMin = options.xMin; + // Retrieve an unsigned byte from the DataView. + function getByte(dataView, offset) { + return dataView.getUint8(offset); } - if ('yMin' in options) { - this.yMin = options.yMin; + // Retrieve an unsigned 16-bit short from the DataView. + // The value is stored in big endian. + function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); } - if ('xMax' in options) { - this.xMax = options.xMax; + // Retrieve a signed 16-bit short from the DataView. + // The value is stored in big endian. + function getShort(dataView, offset) { + return dataView.getInt16(offset, false); } - if ('yMax' in options) { - this.yMax = options.yMax; + // Retrieve an unsigned 32-bit long from the DataView. + // The value is stored in big endian. + function getULong(dataView, offset) { + return dataView.getUint32(offset, false); } - if ('advanceWidth' in options) { - this.advanceWidth = options.advanceWidth; + // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. + // The value is stored in big endian. + function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; } - // The path for a glyph is the most memory intensive, and is bound as a value - // with a getter/setter to ensure we actually do path parsing only once the - // path is actually needed by anything. - Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); -}; + // Retrieve a 4-character tag from the DataView. + // Tags are used to identify tables. + function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } -/** - * @param {number} - */ -Glyph.prototype.addUnicode = function(unicode) { - if (this.unicodes.length === 0) { - this.unicode = unicode; + return tag; } - this.unicodes.push(unicode); -}; - -/** - * Calculate the minimum bounding box for this glyph. - * @return {opentype.BoundingBox} - */ -Glyph.prototype.getBoundingBox = function() { - return this.path.getBoundingBox(); -}; - -/** - * Convert the glyph to a Path we can draw on a drawing context. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - * @param {opentype.Font} if hinting is to be used, the font - * @return {opentype.Path} - */ -Glyph.prototype.getPath = function(x, y, fontSize, options, font) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - var commands; - var hPoints; - if (!options) { options = { }; } - var xScale = options.xScale; - var yScale = options.yScale; - - if (options.hinting && font && font.hinting) { - // in case of hinting, the hinting engine takes care - // of scaling the points (not the path) before hinting. - hPoints = this.path && font.hinting.exec(this, fontSize); - // in case the hinting engine failed hPoints is undefined - // and thus reverts to plain rending - } - - if (hPoints) { - // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency - commands = font.hinting.getCommands(hPoints); - x = Math.round(x); - y = Math.round(y); - // TODO in case of hinting xyScaling is not yet supported - xScale = yScale = 1; - } else { - commands = this.path.commands; - var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; - if (xScale === undefined) { xScale = scale; } - if (yScale === undefined) { yScale = scale; } - } - - var p = new Path(); - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type === 'M') { - p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'L') { - p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Q') { - p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'C') { - p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Z') { - p.closePath(); + // Retrieve an offset from the DataView. + // Offsets are 1 to 4 bytes in length, depending on the offSize argument. + function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); } - } - - return p; -}; -/** - * Split the glyph into contours. - * This function is here for backwards compatibility, and to - * provide raw access to the TrueType glyph outlines. - * @return {Array} - */ -Glyph.prototype.getContours = function() { - if (this.points === undefined) { - return []; + return v; } - var contours = []; - var currentContour = []; - for (var i = 0; i < this.points.length; i += 1) { - var pt = this.points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; + // Retrieve a number of bytes from start offset to the end offset from the DataView. + function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); } - } - - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; -}; -/** - * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. - * @return {Object} - */ -Glyph.prototype.getMetrics = function() { - var commands = this.path.commands; - var xCoords = []; - var yCoords = []; - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type !== 'Z') { - xCoords.push(cmd.x); - yCoords.push(cmd.y); - } + return bytes; + } - if (cmd.type === 'Q' || cmd.type === 'C') { - xCoords.push(cmd.x1); - yCoords.push(cmd.y1); + // Convert the list of bytes to a string. + function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); } - if (cmd.type === 'C') { - xCoords.push(cmd.x2); - yCoords.push(cmd.y2); - } + return s; } - var metrics = { - xMin: Math.min.apply(null, xCoords), - yMin: Math.min.apply(null, yCoords), - xMax: Math.max.apply(null, xCoords), - yMax: Math.max.apply(null, yCoords), - leftSideBearing: this.leftSideBearing + var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 }; - if (!isFinite(metrics.xMin)) { - metrics.xMin = 0; + // A stateful parser that changes the offset whenever a value is retrieved. + // The data is a DataView. + function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; } - if (!isFinite(metrics.xMax)) { - metrics.xMax = this.advanceWidth; - } + Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; + }; - if (!isFinite(metrics.yMin)) { - metrics.yMin = 0; - } + Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; + }; - if (!isFinite(metrics.yMax)) { - metrics.yMax = 0; - } + Parser.prototype.parseCard8 = Parser.prototype.parseByte; - metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); - return metrics; -}; + Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; + }; -/** - * Draw the glyph on the given context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - */ -Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { - this.getPath(x, y, fontSize, options).draw(ctx); -}; + Parser.prototype.parseCard16 = Parser.prototype.parseUShort; + Parser.prototype.parseSID = Parser.prototype.parseUShort; + Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; -/** - * Draw the points of the glyph. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ -Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { - function drawCircles(l, x, y, scale) { - ctx.beginPath(); - for (var j = 0; j < l.length; j += 1) { - ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); - ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); - } + Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; + }; - ctx.closePath(); - ctx.fill(); - } + Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; + }; - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - var scale = 1 / this.path.unitsPerEm * fontSize; + Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; + }; - var blueCircles = []; - var redCircles = []; - var path = this.path; - for (var i = 0; i < path.commands.length; i += 1) { - var cmd = path.commands[i]; - if (cmd.x !== undefined) { - blueCircles.push({x: cmd.x, y: -cmd.y}); - } + Parser.prototype.parseOffset32 = Parser.prototype.parseULong; - if (cmd.x1 !== undefined) { - redCircles.push({x: cmd.x1, y: -cmd.y1}); - } + Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; + }; - if (cmd.x2 !== undefined) { - redCircles.push({x: cmd.x2, y: -cmd.y2}); + Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); } - } - ctx.fillStyle = 'blue'; - drawCircles(blueCircles, x, y, scale); - ctx.fillStyle = 'red'; - drawCircles(redCircles, x, y, scale); -}; - -/** - * Draw lines indicating important font measurements. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ -Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { - var scale; - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - scale = 1 / this.path.unitsPerEm * fontSize; - ctx.lineWidth = 1; - - // Draw the origin - ctx.strokeStyle = 'black'; - draw.line(ctx, x, -10000, x, 10000); - draw.line(ctx, -10000, y, 10000, y); - - // This code is here due to memory optimization: by not using - // defaults in the constructor, we save a notable amount of memory. - var xMin = this.xMin || 0; - var yMin = this.yMin || 0; - var xMax = this.xMax || 0; - var yMax = this.yMax || 0; - var advanceWidth = this.advanceWidth || 0; - - // Draw the glyph box - ctx.strokeStyle = 'blue'; - draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); - draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); - draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); - draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); - - // Draw the advance width - ctx.strokeStyle = 'green'; - draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); -}; + return string; + }; -// The GlyphSet object + Parser.prototype.parseTag = function() { + return this.parseString(4); + }; -// Define a property on the glyph that depends on the path being loaded. -function defineDependentProperty(glyph, externalName, internalName) { - Object.defineProperty(glyph, externalName, { - get: function() { - // Request the path property to make sure the path is loaded. - glyph.path; // jshint ignore:line - return glyph[internalName]; - }, - set: function(newValue) { - glyph[internalName] = newValue; - }, - enumerable: true, - configurable: true - }); -} + // LONGDATETIME is a 64-bit integer. + // JavaScript and unix timestamps traditionally use 32 bits, so we + // only take the last 32 bits. + // + Since until 2038 those bits will be filled by zeros we can ignore them. + Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; + }; -/** - * A GlyphSet represents all glyphs available in the font, but modelled using - * a deferred glyph loader, for retrieving glyphs only once they are absolutely - * necessary, to keep the memory footprint down. - * @exports opentype.GlyphSet - * @class - * @param {opentype.Font} - * @param {Array} - */ -function GlyphSet(font, glyphs) { - this.font = font; - this.glyphs = {}; - if (Array.isArray(glyphs)) { - for (var i = 0; i < glyphs.length; i++) { - var glyph = glyphs[i]; - glyph.path.unitsPerEm = font.unitsPerEm; - this.glyphs[i] = glyph; - } - } + Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); - this.length = (glyphs && glyphs.length) || 0; -} + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; + }; -/** - * @param {number} index - * @return {opentype.Glyph} - */ -GlyphSet.prototype.get = function(index) { - // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. - if (this.glyphs[index] === undefined) { - this.font._push(index); - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); + Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; } - var glyph = this.glyphs[index]; - var unicodeObj = this.font._IndexToUnicodeMap[index]; + this.relativeOffset += typeOffsets[type] * amount; + }; - if (unicodeObj) { - for (var j = 0; j < unicodeObj.unicodes.length; j++) - { glyph.addUnicode(unicodeObj.unicodes[j]); } - } + ///// Parsing lists and records /////////////////////////////// - if (this.font.cffEncoding) { - if (this.font.isCIDFont) { - glyph.name = 'gid' + index; - } else { - glyph.name = this.font.cffEncoding.charset[index]; - } - } else if (this.font.glyphNames.names) { - glyph.name = this.font.glyphNames.glyphIndexToName(index); + // Parse a list of 32 bit unsigned integers. + Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; } - this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; - this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; - } else { - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); - } - } + this.relativeOffset += count * 4; + return offsets; + }; - return this.glyphs[index]; -}; + // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream + // or provided as an argument. + Parser.prototype.parseOffset16List = + Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } -/** - * @param {number} index - * @param {Object} - */ -GlyphSet.prototype.push = function(index, loader) { - this.glyphs[index] = loader; - this.length++; -}; + this.relativeOffset += count * 2; + return offsets; + }; -/** - * @alias opentype.glyphLoader - * @param {opentype.Font} font - * @param {number} index - * @return {opentype.Glyph} - */ -function glyphLoader(font, index) { - return new Glyph({index: index, font: font}); -} + // Parses a list of 16 bit signed integers. + Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } -/** - * Generate a stub glyph that can be filled with all metadata *except* - * the "points" and "path" properties, which must be loaded only once - * the glyph's path is actually requested for text shaping. - * @alias opentype.ttfGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseGlyph - * @param {Object} data - * @param {number} position - * @param {Function} buildPath - * @return {opentype.Glyph} - */ -function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - parseGlyph(glyph, data, position); - var path = buildPath(font.glyphs, glyph); - path.unitsPerEm = font.unitsPerEm; - return path; - }; + this.relativeOffset += count * 2; + return list; + }; - defineDependentProperty(glyph, 'xMin', '_xMin'); - defineDependentProperty(glyph, 'xMax', '_xMax'); - defineDependentProperty(glyph, 'yMin', '_yMin'); - defineDependentProperty(glyph, 'yMax', '_yMax'); + // Parses a list of bytes. + Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); + } - return glyph; + this.relativeOffset += count; + return list; }; -} -/** - * @alias opentype.cffGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseCFFCharstring - * @param {string} charstring - * @return {opentype.Glyph} - */ -function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - var path = parseCFFCharstring(font, glyph, charstring); - path.unitsPerEm = font.unitsPerEm; - return path; - }; - return glyph; + /** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ + Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; }; -} - -var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; -// The `CFF` table contains the glyph outlines in PostScript format. + Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; + }; -// Custom equals function that can also check lists. -function equals(a, b) { - if (a === b) { - return true; - } else if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) { - return false; + /** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ + Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; } + return records; + }; - for (var i = 0; i < a.length; i += 1) { - if (!equals(a[i], b[i])) { - return false; + Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); } + records[i] = rec; } + return records; + }; - return true; - } else { - return false; - } -} - -// Subroutines are encoded using the negative half of the number space. -// See type 2 chapter 4.7 "Subroutine operators". -function calcCFFSubroutineBias(subrs) { - var bias; - if (subrs.length < 1240) { - bias = 107; - } else if (subrs.length < 33900) { - bias = 1131; - } else { - bias = 32768; - } - - return bias; -} - -// Parse a `CFF` INDEX array. -// An index array consists of a list of offsets, then a list of objects at those offsets. -function parseCFFIndex(data, start, conversionFn) { - var offsets = []; - var objects = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } - - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } - - for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { - var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); - if (conversionFn) { - value = conversionFn(value); + // Parse a data structure into an object + // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; } + }; - objects.push(value); - } + /** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ + Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; - return {objects: objects, startOffset: start, endOffset: endOffset}; -} + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } -function parseCFFIndexLowMemory(data, start) { - var offsets = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } + return valueRecord; + }; - return {offsets: offsets, startOffset: start, endOffset: endOffset}; -} -function getCffIndexObject(i, offsets, data, start, conversionFn) { - var count = parse.getCard16(data, start); - var objectOffset = 0; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - } + /** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ + Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; + }; - var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); - if (conversionFn) { - value = conversionFn(value); - } - return value; -} + Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; + }; -// Parse a `CFF` DICT real value. -function parseFloatOperand(parser) { - var s = ''; - var eof = 15; - var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; - while (true) { - var b = parser.parseByte(); - var n1 = b >> 4; - var n2 = b & 15; + Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; + }; - if (n1 === eof) { - break; + /** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ + Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } } + this.relativeOffset = relativeOffset; + return list; + }; - s += lookup[n1]; + ///// Complex tables parsing ////////////////////////////////// - if (n2 === eof) { - break; + // Parse a coverage table in a GSUB, GPOS or GDEF table. + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + // parser.offset must point to the start of the table containing the coverage. + Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges: ranges + }; } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); + }; - s += lookup[n2]; - } + // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); + }; - return parseFloat(s); -} + ///// Static methods /////////////////////////////////// + // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. -// Parse a `CFF` DICT operand. -function parseOperand(parser, b0) { - var b1; - var b2; - var b3; - var b4; - if (b0 === 28) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - return b1 << 8 | b2; - } + Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; + }; - if (b0 === 29) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - b3 = parser.parseByte(); - b4 = parser.parseByte(); - return b1 << 24 | b2 << 16 | b3 << 8 | b4; - } + Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; + }; - if (b0 === 30) { - return parseFloatOperand(parser); - } + Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; + }; - if (b0 >= 32 && b0 <= 246) { - return b0 - 139; - } + Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; + }; - if (b0 >= 247 && b0 <= 250) { - b1 = parser.parseByte(); - return (b0 - 247) * 256 + b1 + 108; - } + Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; + }; - if (b0 >= 251 && b0 <= 254) { - b1 = parser.parseByte(); - return -(b0 - 251) * 256 - b1 - 108; - } + Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; + }; - throw new Error('Invalid b0 ' + b0); -} + Parser.tag = Parser.prototype.parseTag; + Parser.byte = Parser.prototype.parseByte; + Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; + Parser.uShortList = Parser.prototype.parseUShortList; + Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; + Parser.uLongList = Parser.prototype.parseULongList; + Parser.struct = Parser.prototype.parseStruct; + Parser.coverage = Parser.prototype.parseCoverage; + Parser.classDef = Parser.prototype.parseClassDef; + + ///// Script, Feature, Lookup lists /////////////////////////////////////////////// + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + + var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList + }; -// Convert the entries returned by `parseDict` to a proper dictionary. -// If a value is a list of one, it is unpacked. -function entriesToObject(entries) { - var o = {}; - for (var i = 0; i < entries.length; i += 1) { - var key = entries[i][0]; - var values = entries[i][1]; - var value = (void 0); - if (values.length === 1) { - value = values[0]; - } else { - value = values; - } + Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; + }; - if (o.hasOwnProperty(key) && !isNaN(o[key])) { - throw new Error('Object ' + o + ' already has key ' + key); - } + Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; + }; - o[key] = value; - } + Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; + }; - return o; -} + Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; + }; -// Parse a `CFF` DICT object. -// A dictionary contains key-value pairs in a compact tokenized format. -function parseCFFDict(data, start, size) { - start = start !== undefined ? start : 0; - var parser = new parse.Parser(data, start); - var entries = []; - var operands = []; - size = size !== undefined ? size : data.length; + var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, + }; - while (parser.relativeOffset < size) { - var op = parser.parseByte(); + // The `cmap` table stores the mappings from characters to glyphs. - // The first byte for each dict item distinguishes between operator (key) and operand (value). - // Values <= 21 are operators. - if (op <= 21) { - // Two-byte operators have an initial escape byte of 12. - if (op === 12) { - op = 1200 + parser.parseByte(); - } + function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); - entries.push([op, operands]); - operands = []; - } else { - // Since the operands (values) come before the operators (keys), we store all operands in a list - // until we encounter an operator. - operands.push(parseOperand(parser, op)); - } - } + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; - return entriesToObject(entries); -} + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); -// Given a String Index (SID), return the value of the string. -// Strings below index 392 are standard CFF strings and are not encoded in the font. -function getCFFString(strings, index) { - if (index <= 390) { - index = cffStandardStrings[index]; - } else { - index = strings[index - 391]; + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } } - return index; -} + function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); -// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. -// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. -function interpretDict(dict, meta, strings) { - var newDict = {}; - var value; + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; - // Because we also want to include missing values, we start out from the meta list - // and lookup values in the dict. - for (var i = 0; i < meta.length; i += 1) { - var m = meta[i]; + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); - if (Array.isArray(m.type)) { - var values = []; - values.length = m.type.length; - for (var j = 0; j < m.type.length; j++) { - value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; - if (value === undefined) { - value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; - } - if (m.type[j] === 'SID') { - value = getCFFString(strings, value); + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; } - values[j] = value; - } - newDict[m.name] = values; - } else { - value = dict[m.op]; - if (value === undefined) { - value = m.value !== undefined ? m.value : null; - } - - if (m.type === 'SID') { - value = getCFFString(strings, value); - } - newDict[m.name] = value; - } - } - - return newDict; -} - -// Parse the CFF header. -function parseCFFHeader(data, start) { - var header = {}; - header.formatMajor = parse.getCard8(data, start); - header.formatMinor = parse.getCard8(data, start + 1); - header.size = parse.getCard8(data, start + 2); - header.offsetSize = parse.getCard8(data, start + 3); - header.startOffset = start; - header.endOffset = start + 4; - return header; -} - -var TOP_DICT_META = [ - {name: 'version', op: 0, type: 'SID'}, - {name: 'notice', op: 1, type: 'SID'}, - {name: 'copyright', op: 1200, type: 'SID'}, - {name: 'fullName', op: 2, type: 'SID'}, - {name: 'familyName', op: 3, type: 'SID'}, - {name: 'weight', op: 4, type: 'SID'}, - {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, - {name: 'italicAngle', op: 1202, type: 'number', value: 0}, - {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, - {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, - {name: 'paintType', op: 1205, type: 'number', value: 0}, - {name: 'charstringType', op: 1206, type: 'number', value: 2}, - { - name: 'fontMatrix', - op: 1207, - type: ['real', 'real', 'real', 'real', 'real', 'real'], - value: [0.001, 0, 0, 0.001, 0, 0] - }, - {name: 'uniqueId', op: 13, type: 'number'}, - {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, - {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, - {name: 'xuid', op: 14, type: [], value: null}, - {name: 'charset', op: 15, type: 'offset', value: 0}, - {name: 'encoding', op: 16, type: 'offset', value: 0}, - {name: 'charStrings', op: 17, type: 'offset', value: 0}, - {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, - {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, - {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, - {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, - {name: 'cidFontType', op: 1233, type: 'number', value: 0}, - {name: 'cidCount', op: 1234, type: 'number', value: 8720}, - {name: 'uidBase', op: 1235, type: 'number'}, - {name: 'fdArray', op: 1236, type: 'offset'}, - {name: 'fdSelect', op: 1237, type: 'offset'}, - {name: 'fontName', op: 1238, type: 'SID'} -]; - -var PRIVATE_DICT_META = [ - {name: 'subrs', op: 19, type: 'offset', value: 0}, - {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, - {name: 'nominalWidthX', op: 21, type: 'number', value: 0} -]; - -// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. -// The top dictionary contains the essential metadata for the font, together with the private dictionary. -function parseCFFTopDict(data, strings) { - var dict = parseCFFDict(data, 0, data.byteLength); - return interpretDict(dict, TOP_DICT_META, strings); -} - -// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. -function parseCFFPrivateDict(data, start, size, strings) { - var dict = parseCFFDict(data, start, size); - return interpretDict(dict, PRIVATE_DICT_META, strings); -} - -// Returns a list of "Top DICT"s found using an INDEX list. -// Used to read both the usual high-level Top DICTs and also the FDArray -// discovered inside CID-keyed fonts. When a Top DICT has a reference to -// a Private DICT that is read and saved into the Top DICT. -// -// In addition to the expected/optional values as outlined in TOP_DICT_META -// the following values might be saved into the Top DICT. -// -// _subrs [] array of local CFF subroutines from Private DICT -// _subrsBias bias value computed from number of subroutines -// (see calcCFFSubroutineBias() and parseCFFCharstring()) -// _defaultWidthX default widths for CFF characters -// _nominalWidthX bias added to width embedded within glyph description -// -// _privateDict saved copy of parsed Private DICT from Top DICT -function gatherCFFTopDicts(data, start, cffIndex, strings) { - var topDictArray = []; - for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { - var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); - var topDict = parseCFFTopDict(topDictData, strings); - topDict._subrs = []; - topDict._subrsBias = 0; - topDict._defaultWidthX = 0; - topDict._nominalWidthX = 0; - var privateSize = topDict.private[0]; - var privateOffset = topDict.private[1]; - if (privateSize !== 0 && privateOffset !== 0) { - var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); - topDict._defaultWidthX = privateDict.defaultWidthX; - topDict._nominalWidthX = privateDict.nominalWidthX; - if (privateDict.subrs !== 0) { - var subrOffset = privateOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset + start); - topDict._subrs = subrIndex.objects; - topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); - } - topDict._privateDict = privateDict; - } - topDictArray.push(topDict); - } - return topDictArray; -} - -// Parse the CFF charset table, which contains internal names for all the glyphs. -// This function will return a list of glyph names. -// See Adobe TN #5176 chapter 13, "Charsets". -function parseCFFCharset(data, start, nGlyphs, strings) { - var sid; - var count; - var parser = new parse.Parser(data, start); - - // The .notdef glyph is not included, so subtract 1. - nGlyphs -= 1; - var charset = ['.notdef']; - - var format = parser.parseCard8(); - if (format === 0) { - for (var i = 0; i < nGlyphs; i += 1) { - sid = parser.parseSID(); - charset.push(getCFFString(strings, sid)); - } - } else if (format === 1) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard8(); - for (var i$1 = 0; i$1 <= count; i$1 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; + + cmap.glyphIndexMap[c] = glyphIndex; } } - } else if (format === 2) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard16(); - for (var i$2 = 0; i$2 <= count; i$2 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else { - throw new Error('Unknown charset format ' + format); - } - - return charset; -} - -// Parse the CFF encoding data. Only one encoding can be specified per font. -// See Adobe TN #5176 chapter 12, "Encodings". -function parseCFFEncoding(data, start, charset) { - var code; - var enc = {}; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - var nCodes = parser.parseCard8(); - for (var i = 0; i < nCodes; i += 1) { - code = parser.parseCard8(); - enc[code] = i; - } - } else if (format === 1) { - var nRanges = parser.parseCard8(); - code = 1; - for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { - var first = parser.parseCard8(); - var nLeft = parser.parseCard8(); - for (var j = first; j <= first + nLeft; j += 1) { - enc[j] = code; - code += 1; - } - } - } else { - throw new Error('Unknown encoding format ' + format); - } - - return new CffEncoding(enc, charset); -} - -// Take in charstring code and return a Glyph object. -// The encoding is described in the Type 2 Charstring Format -// https://www.microsoft.com/typography/OTSPEC/charstr2.htm -function parseCFFCharstring(font, glyph, code) { - var c1x; - var c1y; - var c2x; - var c2y; - var p = new Path(); - var stack = []; - var nStems = 0; - var haveWidth = false; - var open = false; - var x = 0; - var y = 0; - var subrs; - var subrsBias; - var defaultWidthX; - var nominalWidthX; - if (font.isCIDFont) { - var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; - var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; - subrs = fdDict._subrs; - subrsBias = fdDict._subrsBias; - defaultWidthX = fdDict._defaultWidthX; - nominalWidthX = fdDict._nominalWidthX; - } else { - subrs = font.tables.cff.topDict._subrs; - subrsBias = font.tables.cff.topDict._subrsBias; - defaultWidthX = font.tables.cff.topDict._defaultWidthX; - nominalWidthX = font.tables.cff.topDict._nominalWidthX; - } - var width = defaultWidthX; - - function newContour(x, y) { - if (open) { - p.closePath(); + } + + // Parse the `cmap` table. This table stores the mappings from characters to glyphs. + // There are many available formats, but we only support the Windows format 4 and 12. + // This function returns a `CmapEncoding` object or null if no supported format could be found. + function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } } - p.moveTo(x, y); - open = true; - } + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } - function parseStems() { - var hasWidthArg; + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); - // The number of stem operators on the stack is always even. - // If the value is uneven, that means a width is specified. - hasWidthArg = stack.length % 2 !== 0; - if (hasWidthArg && !haveWidth) { - width = stack.shift() + nominalWidthX; + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); } - nStems += stack.length >> 1; - stack.length = 0; - haveWidth = true; + return cmap; } - function parse(code) { - var b1; - var b2; - var b3; - var b4; - var codeIndex; - var subrCode; - var jpx; - var jpy; - var c3x; - var c3y; - var c4x; - var c4y; - - var i = 0; - while (i < code.length) { - var v = code[i]; - i += 1; - switch (v) { - case 1: // hstem - parseStems(); - break; - case 3: // vstem - parseStems(); - break; - case 4: // vmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - y += stack.pop(); - newContour(x, y); - break; - case 5: // rlineto - while (stack.length > 0) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } + function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); + } - break; - case 6: // hlineto - while (stack.length > 0) { - x += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } + function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); + } - y += stack.shift(); - p.lineTo(x, y); - } + // Make cmap table, format 4 by default, 12 if needed only + function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; - break; - case 7: // vlineto - while (stack.length > 0) { - y += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; + } + } - x += stack.shift(); - p.lineTo(x, y); - } + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, + + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; + + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } + + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); - break; - case 8: // rrcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + var t = new table.Table('cmap', cmapTable); - break; - case 10: // callsubr - codeIndex = stack.pop() + subrsBias; - subrCode = subrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } - break; - case 11: // return - return; - case 12: // flex operators - v = code[i]; - i += 1; - switch (v) { - case 35: // flex - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - y = c4y + stack.shift(); // dy6 - stack.shift(); // flex depth - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 34: // hflex - // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- - c1x = x + stack.shift(); // dx1 - c1y = y; // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = y; // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 36: // hflex1 - // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 37: // flex1 - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - if (Math.abs(c4x - x) > Math.abs(c4y - y)) { - x = c4x + stack.shift(); - } else { - y = c4y + stack.shift(); - } + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - default: - console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); - stack.length = 0; - } - break; - case 14: // endchar - if (stack.length > 0 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + addTerminatorSegment(t); - if (open) { - p.closePath(); - open = false; - } + var segCount = t.segments.length; + var segCountToRemove = 0; - break; - case 18: // hstemhm - parseStems(); - break; - case 19: // hintmask - case 20: // cntrmask - parseStems(); - i += (nStems + 7) >> 3; - break; - case 21: // rmoveto - if (stack.length > 2 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; - y += stack.pop(); - x += stack.pop(); - newContour(x, y); - break; - case 22: // hmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; + } - x += stack.pop(); - newContour(x, y); - break; - case 23: // vstemhm - parseStems(); - break; - case 24: // rcurveline - while (stack.length > 2) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - break; - case 25: // rlinecurve - while (stack.length > 6) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; + } + + var cmap = { parse: parseCmapTable, make: makeCmapTable }; + + // Glyph encoding + + var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + + var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + + var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + + var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - break; - case 26: // vvcurveto - if (stack.length % 2) { - x += stack.shift(); + /** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ + function DefaultEncoding(font) { + this.font = font; + } + + DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; } + } + } + } + return null; + }; - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x; - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + /** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ + function CmapEncoding(cmap) { + this.cmap = cmap; + } - break; - case 27: // hhcurveto - if (stack.length % 2) { - y += stack.shift(); - } + /** + * @param {string} c - the character + * @return {number} The glyph index. + */ + CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; + }; - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y; - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + /** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ + function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; + } - break; - case 28: // shortint - b1 = code[i]; - b2 = code[i + 1]; - stack.push(((b1 << 24) | (b2 << 16)) >> 16); - i += 2; - break; - case 29: // callgsubr - codeIndex = stack.pop() + font.gsubrsBias; - subrCode = font.gsubrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } - - break; - case 30: // vhcurveto - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } + /** + * @param {string} s - The character + * @return {number} The index. + */ + CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); + }; - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); + /** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ + function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; } + } - break; - case 31: // hvcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } - - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } - break; - default: - if (v < 32) { - console.log('Glyph ' + glyph.index + ': unknown operator ' + v); - } else if (v < 247) { - stack.push(v - 139); - } else if (v < 251) { - b1 = code[i]; - i += 1; - stack.push((v - 247) * 256 + b1 + 108); - } else if (v < 255) { - b1 = code[i]; - i += 1; - stack.push(-(v - 251) * 256 - b1 - 108); - } else { - b1 = code[i]; - b2 = code[i + 1]; - b3 = code[i + 2]; - b4 = code[i + 3]; - i += 4; - stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); - } - } + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; } } - parse(code); + /** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ + GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); + }; - glyph.advanceWidth = width; - return p; -} + /** + * @param {number} gid + * @return {string} + */ + GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; + }; -function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { - var fdSelect = []; - var fdIndex; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - // Simple list of nGlyphs elements - for (var iGid = 0; iGid < nGlyphs; iGid++) { - fdIndex = parser.parseCard8(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - fdSelect.push(fdIndex); - } - } else if (format === 3) { - // Ranges - var nRanges = parser.parseCard16(); - var first = parser.parseCard16(); - if (first !== 0) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); } - var next; - for (var iRange = 0; iRange < nRanges; iRange++) { - fdIndex = parser.parseCard8(); - next = parser.parseCard16(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - if (next > nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); - } - for (; first < next; first++) { - fdSelect.push(fdIndex); + + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); } - first = next; - } - if (next !== nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); } - } else { - throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); } - return fdSelect; -} -// Parse the `CFF` table, which contains the glyph outlines in PostScript format. -function parseCFFTable(data, start, font, opt) { - font.tables.cff = {}; - var header = parseCFFHeader(data, start); - var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); - var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); - var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); - var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); - font.gsubrs = globalSubrIndex.objects; - font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; - var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); - if (topDictArray.length !== 1) { - throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); - } - - var topDict = topDictArray[0]; - font.tables.cff.topDict = topDict; - - if (topDict._privateDict) { - font.defaultWidthX = topDict._privateDict.defaultWidthX; - font.nominalWidthX = topDict._privateDict.nominalWidthX; - } + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); - if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { - font.isCIDFont = true; + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + } + } } - if (font.isCIDFont) { - var fdArrayOffset = topDict.fdArray; - var fdSelectOffset = topDict.fdSelect; - if (fdArrayOffset === 0 || fdSelectOffset === 0) { - throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + /** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ + function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); } - fdArrayOffset += start; - var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); - var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); - topDict._fdArray = fdArray; - fdSelectOffset += start; - topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); } - var privateDictOffset = start + topDict.private[1]; - var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); - font.defaultWidthX = privateDict.defaultWidthX; - font.nominalWidthX = privateDict.nominalWidthX; + // Drawing utility functions. - if (privateDict.subrs !== 0) { - var subrOffset = privateDictOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset); - font.subrs = subrIndex.objects; - font.subrsBias = calcCFFSubroutineBias(font.subrs); - } else { - font.subrs = []; - font.subrsBias = 0; + // Draw a line on the given context from point `x1,y1` to point `x2,y2`. + function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); } - // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. - var charStringsIndex; - if (opt.lowMemory) { - charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.offsets.length; - } else { - charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.objects.length; - } + var draw = { line: line }; - var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); - if (topDict.encoding === 0) { - // Standard encoding - font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); - } else if (topDict.encoding === 1) { - // Expert encoding - font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); - } else { - font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); - } + // The Glyph object + // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency - // Prefer the CMAP encoding to the CFF encoding. - font.encoding = font.encoding || font.cffEncoding; + function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, - font.glyphs = new glyphset.GlyphSet(font); - if (opt.lowMemory) { - font._push = function(i) { - var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - }; - } else { - for (var i = 0; i < font.nGlyphs; i += 1) { - var charString = charStringsIndex.objects[i]; - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - } - } -} + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } -// Convert a string to a String ID (SID). -// The list of strings is modified in place. -function encodeString(s, strings) { - var sid; + return _path; + }, - // Is the string in the CFF standard strings? - var i = cffStandardStrings.indexOf(s); - if (i >= 0) { - sid = i; + set: function(p) { + _path = p; + } + }; } + /** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ - // Is the string already in the string index? - i = strings.indexOf(s); - if (i >= 0) { - sid = i + cffStandardStrings.length; - } else { - sid = cffStandardStrings.length + strings.length; - strings.push(s); + // A Glyph is an individual mark that often corresponds to a character. + // Some glyphs, such as ligatures, are a combination of many characters. + // Glyphs are the basic building blocks of a font. + // + // The `Glyph` class contains utility methods for drawing the path and its points. + /** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ + function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); } - return sid; -} - -function makeHeader() { - return new table.Record('Header', [ - {name: 'major', type: 'Card8', value: 1}, - {name: 'minor', type: 'Card8', value: 0}, - {name: 'hdrSize', type: 'Card8', value: 4}, - {name: 'major', type: 'Card8', value: 1} - ]); -} + /** + * @param {GlyphOptions} + */ + Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; -function makeNameIndex(fontNames) { - var t = new table.Record('Name INDEX', [ - {name: 'names', type: 'INDEX', value: []} - ]); - t.names = []; - for (var i = 0; i < fontNames.length; i += 1) { - t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); - } + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; - return t; -} + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; + } -// Given a dictionary's metadata, create a DICT structure. -function makeDict(meta, attrs, strings) { - var m = {}; - for (var i = 0; i < meta.length; i += 1) { - var entry = meta[i]; - var value = attrs[entry.name]; - if (value !== undefined && !equals(value, entry.value)) { - if (entry.type === 'SID') { - value = encodeString(value, strings); - } + if ('yMin' in options) { + this.yMin = options.yMin; + } - m[entry.op] = {name: entry.name, type: entry.type, value: value}; + if ('xMax' in options) { + this.xMax = options.xMax; } - } - return m; -} + if ('yMax' in options) { + this.yMax = options.yMax; + } -// The Top DICT houses the global font attributes. -function makeTopDict(attrs, strings) { - var t = new table.Record('Top DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(TOP_DICT_META, attrs, strings); - return t; -} + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; + } -function makeTopDictIndex(topDict) { - var t = new table.Record('Top DICT INDEX', [ - {name: 'topDicts', type: 'INDEX', value: []} - ]); - t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; - return t; -} + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); + }; -function makeStringIndex(strings) { - var t = new table.Record('String INDEX', [ - {name: 'strings', type: 'INDEX', value: []} - ]); - t.strings = []; - for (var i = 0; i < strings.length; i += 1) { - t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); - } + /** + * @param {number} + */ + Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } - return t; -} + this.unicodes.push(unicode); + }; -function makeGlobalSubrIndex() { - // Currently we don't use subroutines. - return new table.Record('Global Subr INDEX', [ - {name: 'subrs', type: 'INDEX', value: []} - ]); -} + /** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ + Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); + }; -function makeCharsets(glyphNames, strings) { - var t = new table.Record('Charsets', [ - {name: 'format', type: 'Card8', value: 0} - ]); - for (var i = 0; i < glyphNames.length; i += 1) { - var glyphName = glyphNames[i]; - var glyphSID = encodeString(glyphName, strings); - t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); - } - - return t; -} - -function glyphToOps(glyph) { - var ops = []; - var path = glyph.path; - ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); - var x = 0; - var y = 0; - for (var i = 0; i < path.commands.length; i += 1) { - var dx = (void 0); - var dy = (void 0); - var cmd = path.commands[i]; - if (cmd.type === 'Q') { - // CFF only supports bézier curves, so convert the quad to a bézier. - var _13 = 1 / 3; - var _23 = 2 / 3; - - // We're going to create a new command so we don't change the original path. - // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. - cmd = { - type: 'C', - x: cmd.x, - y: cmd.y, - x1: Math.round(_13 * x + _23 * cmd.x1), - y1: Math.round(_13 * y + _23 * cmd.y1), - x2: Math.round(_13 * cmd.x + _23 * cmd.x1), - y2: Math.round(_13 * cmd.y + _23 * cmd.y1) - }; + /** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ + Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } } - if (cmd.type === 'M') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rmoveto', type: 'OP', value: 21}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'L') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rlineto', type: 'OP', value: 5}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'C') { - var dx1 = Math.round(cmd.x1 - x); - var dy1 = Math.round(cmd.y1 - y); - var dx2 = Math.round(cmd.x2 - cmd.x1); - var dy2 = Math.round(cmd.y2 - cmd.y1); - dx = Math.round(cmd.x - cmd.x2); - dy = Math.round(cmd.y - cmd.y2); - ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); - ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); - ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); - ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rrcurveto', type: 'OP', value: 8}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } - - // Contours are closed automatically. - } - - ops.push({name: 'endchar', type: 'OP', value: 14}); - return ops; -} - -function makeCharStringsIndex(glyphs) { - var t = new table.Record('CharStrings INDEX', [ - {name: 'charStrings', type: 'INDEX', value: []} - ]); - - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var ops = glyphToOps(glyph); - t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); - } - - return t; -} + return p; + }; -function makePrivateDict(attrs, strings) { - var t = new table.Record('Private DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); - return t; -} - -function makeCFFTable(glyphs, options) { - var t = new table.Table('CFF ', [ - {name: 'header', type: 'RECORD'}, - {name: 'nameIndex', type: 'RECORD'}, - {name: 'topDictIndex', type: 'RECORD'}, - {name: 'stringIndex', type: 'RECORD'}, - {name: 'globalSubrIndex', type: 'RECORD'}, - {name: 'charsets', type: 'RECORD'}, - {name: 'charStringsIndex', type: 'RECORD'}, - {name: 'privateDict', type: 'RECORD'} - ]); + /** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ + Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } - var fontScale = 1 / options.unitsPerEm; - // We use non-zero values for the offsets so that the DICT encodes them. - // This is important because the size of the Top DICT plays a role in offset calculation, - // and the size shouldn't change after we've written correct offsets. - var attrs = { - version: options.version, - fullName: options.fullName, - familyName: options.familyName, - weight: options.weightName, - fontBBox: options.fontBBox || [0, 0, 0, 0], - fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], - charset: 999, - encoding: 0, - charStrings: 999, - private: [0, 999] - }; - - var privateAttrs = {}; - - var glyphNames = []; - var glyph; - - // Skip first glyph (.notdef) - for (var i = 1; i < glyphs.length; i += 1) { - glyph = glyphs.get(i); - glyphNames.push(glyph.name); - } - - var strings = []; - - t.header = makeHeader(); - t.nameIndex = makeNameIndex([options.postScriptName]); - var topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - t.globalSubrIndex = makeGlobalSubrIndex(); - t.charsets = makeCharsets(glyphNames, strings); - t.charStringsIndex = makeCharStringsIndex(glyphs); - t.privateDict = makePrivateDict(privateAttrs, strings); - - // Needs to come at the end, to encode all custom strings used in the font. - t.stringIndex = makeStringIndex(strings); - - var startOffset = t.header.sizeOf() + - t.nameIndex.sizeOf() + - t.topDictIndex.sizeOf() + - t.stringIndex.sizeOf() + - t.globalSubrIndex.sizeOf(); - attrs.charset = startOffset; - - // We use the CFF standard encoding; proper encoding will be handled in cmap. - attrs.encoding = 0; - attrs.charStrings = attrs.charset + t.charsets.sizeOf(); - attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); - - // Recreate the Top DICT INDEX with the correct offsets. - topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - - return t; -} - -var cff = { parse: parseCFFTable, make: makeCFFTable }; - -// The `head` table contains global information about the font. - -// Parse the header `head` table -function parseHeadTable(data, start) { - var head = {}; - var p = new parse.Parser(data, start); - head.version = p.parseVersion(); - head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; - head.checkSumAdjustment = p.parseULong(); - head.magicNumber = p.parseULong(); - check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); - head.flags = p.parseUShort(); - head.unitsPerEm = p.parseUShort(); - head.created = p.parseLongDateTime(); - head.modified = p.parseLongDateTime(); - head.xMin = p.parseShort(); - head.yMin = p.parseShort(); - head.xMax = p.parseShort(); - head.yMax = p.parseShort(); - head.macStyle = p.parseUShort(); - head.lowestRecPPEM = p.parseUShort(); - head.fontDirectionHint = p.parseShort(); - head.indexToLocFormat = p.parseShort(); - head.glyphDataFormat = p.parseShort(); - return head; -} - -function makeHeadTable(options) { - // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 - var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; - var createdTimestamp = timestamp; - - if (options.createdTimestamp) { - createdTimestamp = options.createdTimestamp + 2082844800; - } - - return new table.Table('head', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, - {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, - {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, - {name: 'flags', type: 'USHORT', value: 0}, - {name: 'unitsPerEm', type: 'USHORT', value: 1000}, - {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, - {name: 'modified', type: 'LONGDATETIME', value: timestamp}, - {name: 'xMin', type: 'SHORT', value: 0}, - {name: 'yMin', type: 'SHORT', value: 0}, - {name: 'xMax', type: 'SHORT', value: 0}, - {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: 0}, - {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, - {name: 'fontDirectionHint', type: 'SHORT', value: 2}, - {name: 'indexToLocFormat', type: 'SHORT', value: 0}, - {name: 'glyphDataFormat', type: 'SHORT', value: 0} - ], options); -} - -var head = { parse: parseHeadTable, make: makeHeadTable }; - -// The `hhea` table contains information for horizontal layout. - -// Parse the horizontal header `hhea` table -function parseHheaTable(data, start) { - var hhea = {}; - var p = new parse.Parser(data, start); - hhea.version = p.parseVersion(); - hhea.ascender = p.parseShort(); - hhea.descender = p.parseShort(); - hhea.lineGap = p.parseShort(); - hhea.advanceWidthMax = p.parseUShort(); - hhea.minLeftSideBearing = p.parseShort(); - hhea.minRightSideBearing = p.parseShort(); - hhea.xMaxExtent = p.parseShort(); - hhea.caretSlopeRise = p.parseShort(); - hhea.caretSlopeRun = p.parseShort(); - hhea.caretOffset = p.parseShort(); - p.relativeOffset += 8; - hhea.metricDataFormat = p.parseShort(); - hhea.numberOfHMetrics = p.parseUShort(); - return hhea; -} - -function makeHheaTable(options) { - return new table.Table('hhea', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'ascender', type: 'FWORD', value: 0}, - {name: 'descender', type: 'FWORD', value: 0}, - {name: 'lineGap', type: 'FWORD', value: 0}, - {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, - {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, - {name: 'minRightSideBearing', type: 'FWORD', value: 0}, - {name: 'xMaxExtent', type: 'FWORD', value: 0}, - {name: 'caretSlopeRise', type: 'SHORT', value: 1}, - {name: 'caretSlopeRun', type: 'SHORT', value: 0}, - {name: 'caretOffset', type: 'SHORT', value: 0}, - {name: 'reserved1', type: 'SHORT', value: 0}, - {name: 'reserved2', type: 'SHORT', value: 0}, - {name: 'reserved3', type: 'SHORT', value: 0}, - {name: 'reserved4', type: 'SHORT', value: 0}, - {name: 'metricDataFormat', type: 'SHORT', value: 0}, - {name: 'numberOfHMetrics', type: 'USHORT', value: 0} - ], options); -} - -var hhea = { parse: parseHheaTable, make: makeHheaTable }; - -// The `hmtx` table contains the horizontal metrics for all glyphs. - -function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); - } - - var glyph = glyphs.get(i); - glyph.advanceWidth = advanceWidth; - glyph.leftSideBearing = leftSideBearing; - } -} - -function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { - font._hmtxTableData = {}; - - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); - } - - font._hmtxTableData[i] = { - advanceWidth: advanceWidth, - leftSideBearing: leftSideBearing - }; - } -} + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; + }; -// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. -// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. -function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { - if (opt.lowMemory) - { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } - else - { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } -} + /** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ + Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } -function makeHmtxTable(glyphs) { - var t = new table.Table('hmtx', []); - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var advanceWidth = glyph.advanceWidth || 0; - var leftSideBearing = glyph.leftSideBearing || 0; - t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); - t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); - } + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } - return t; -} + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } -var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; -// The `ltag` table stores IETF BCP-47 language tags. It allows supporting + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } -function makeLtagTable(tags) { - var result = new table.Table('ltag', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'numTags', type: 'ULONG', value: tags.length} - ]); + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } - var stringPool = ''; - var stringPoolOffset = 12 + tags.length * 4; - for (var i = 0; i < tags.length; ++i) { - var pos = stringPool.indexOf(tags[i]); - if (pos < 0) { - pos = stringPool.length; - stringPool += tags[i]; + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; } - result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); - } + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - return result; -} + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; + }; -function parseLtagTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported ltag table version.'); - // The 'ltag' specification does not define any flags; skip the field. - p.skip('uLong', 1); - var numTags = p.parseULong(); + /** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ + Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); + }; - var tags = []; - for (var i = 0; i < numTags; i++) { - var tag = ''; - var offset = start + p.parseUShort(); - var length = p.parseUShort(); - for (var j = offset; j < offset + length; ++j) { - tag += String.fromCharCode(data.getInt8(j)); - } - - tags.push(tag); - } - - return tags; -} - -var ltag = { make: makeLtagTable, parse: parseLtagTable }; - -// The `maxp` table establishes the memory requirements for the font. - -// Parse the maximum profile `maxp` table. -function parseMaxpTable(data, start) { - var maxp = {}; - var p = new parse.Parser(data, start); - maxp.version = p.parseVersion(); - maxp.numGlyphs = p.parseUShort(); - if (maxp.version === 1.0) { - maxp.maxPoints = p.parseUShort(); - maxp.maxContours = p.parseUShort(); - maxp.maxCompositePoints = p.parseUShort(); - maxp.maxCompositeContours = p.parseUShort(); - maxp.maxZones = p.parseUShort(); - maxp.maxTwilightPoints = p.parseUShort(); - maxp.maxStorage = p.parseUShort(); - maxp.maxFunctionDefs = p.parseUShort(); - maxp.maxInstructionDefs = p.parseUShort(); - maxp.maxStackElements = p.parseUShort(); - maxp.maxSizeOfInstructions = p.parseUShort(); - maxp.maxComponentElements = p.parseUShort(); - maxp.maxComponentDepth = p.parseUShort(); - } - - return maxp; -} - -function makeMaxpTable(numGlyphs) { - return new table.Table('maxp', [ - {name: 'version', type: 'FIXED', value: 0x00005000}, - {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} - ]); -} - -var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; - -// The `name` naming table. - -// NameIDs for the name table. -var nameTableNames = [ - 'copyright', // 0 - 'fontFamily', // 1 - 'fontSubfamily', // 2 - 'uniqueID', // 3 - 'fullName', // 4 - 'version', // 5 - 'postScriptName', // 6 - 'trademark', // 7 - 'manufacturer', // 8 - 'designer', // 9 - 'description', // 10 - 'manufacturerURL', // 11 - 'designerURL', // 12 - 'license', // 13 - 'licenseURL', // 14 - 'reserved', // 15 - 'preferredFamily', // 16 - 'preferredSubfamily', // 17 - 'compatibleFullName', // 18 - 'sampleText', // 19 - 'postScriptFindFontName', // 20 - 'wwsFamily', // 21 - 'wwsSubfamily' // 22 -]; - -var macLanguages = { - 0: 'en', - 1: 'fr', - 2: 'de', - 3: 'it', - 4: 'nl', - 5: 'sv', - 6: 'es', - 7: 'da', - 8: 'pt', - 9: 'no', - 10: 'he', - 11: 'ja', - 12: 'ar', - 13: 'fi', - 14: 'el', - 15: 'is', - 16: 'mt', - 17: 'tr', - 18: 'hr', - 19: 'zh-Hant', - 20: 'ur', - 21: 'hi', - 22: 'th', - 23: 'ko', - 24: 'lt', - 25: 'pl', - 26: 'hu', - 27: 'es', - 28: 'lv', - 29: 'se', - 30: 'fo', - 31: 'fa', - 32: 'ru', - 33: 'zh', - 34: 'nl-BE', - 35: 'ga', - 36: 'sq', - 37: 'ro', - 38: 'cz', - 39: 'sk', - 40: 'si', - 41: 'yi', - 42: 'sr', - 43: 'mk', - 44: 'bg', - 45: 'uk', - 46: 'be', - 47: 'uz', - 48: 'kk', - 49: 'az-Cyrl', - 50: 'az-Arab', - 51: 'hy', - 52: 'ka', - 53: 'mo', - 54: 'ky', - 55: 'tg', - 56: 'tk', - 57: 'mn-CN', - 58: 'mn', - 59: 'ps', - 60: 'ks', - 61: 'ku', - 62: 'sd', - 63: 'bo', - 64: 'ne', - 65: 'sa', - 66: 'mr', - 67: 'bn', - 68: 'as', - 69: 'gu', - 70: 'pa', - 71: 'or', - 72: 'ml', - 73: 'kn', - 74: 'ta', - 75: 'te', - 76: 'si', - 77: 'my', - 78: 'km', - 79: 'lo', - 80: 'vi', - 81: 'id', - 82: 'tl', - 83: 'ms', - 84: 'ms-Arab', - 85: 'am', - 86: 'ti', - 87: 'om', - 88: 'so', - 89: 'sw', - 90: 'rw', - 91: 'rn', - 92: 'ny', - 93: 'mg', - 94: 'eo', - 128: 'cy', - 129: 'eu', - 130: 'ca', - 131: 'la', - 132: 'qu', - 133: 'gn', - 134: 'ay', - 135: 'tt', - 136: 'ug', - 137: 'dz', - 138: 'jv', - 139: 'su', - 140: 'gl', - 141: 'af', - 142: 'br', - 143: 'iu', - 144: 'gd', - 145: 'gv', - 146: 'ga', - 147: 'to', - 148: 'el-polyton', - 149: 'kl', - 150: 'az', - 151: 'nn' -}; + /** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ + Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } -// MacOS language ID → MacOS script ID -// -// Note that the script ID is not sufficient to determine what encoding -// to use in TrueType files. For some languages, MacOS used a modification -// of a mainstream script. For example, an Icelandic name would be stored -// with smRoman in the TrueType naming table, but the actual encoding -// is a special Icelandic version of the normal Macintosh Roman encoding. -// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal -// Syllables but MacOS had run out of available script codes, so this was -// done as a (pretty radical) "modification" of Ethiopic. -// -// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt -var macLanguageToScript = { - 0: 0, // langEnglish → smRoman - 1: 0, // langFrench → smRoman - 2: 0, // langGerman → smRoman - 3: 0, // langItalian → smRoman - 4: 0, // langDutch → smRoman - 5: 0, // langSwedish → smRoman - 6: 0, // langSpanish → smRoman - 7: 0, // langDanish → smRoman - 8: 0, // langPortuguese → smRoman - 9: 0, // langNorwegian → smRoman - 10: 5, // langHebrew → smHebrew - 11: 1, // langJapanese → smJapanese - 12: 4, // langArabic → smArabic - 13: 0, // langFinnish → smRoman - 14: 6, // langGreek → smGreek - 15: 0, // langIcelandic → smRoman (modified) - 16: 0, // langMaltese → smRoman - 17: 0, // langTurkish → smRoman (modified) - 18: 0, // langCroatian → smRoman (modified) - 19: 2, // langTradChinese → smTradChinese - 20: 4, // langUrdu → smArabic - 21: 9, // langHindi → smDevanagari - 22: 21, // langThai → smThai - 23: 3, // langKorean → smKorean - 24: 29, // langLithuanian → smCentralEuroRoman - 25: 29, // langPolish → smCentralEuroRoman - 26: 29, // langHungarian → smCentralEuroRoman - 27: 29, // langEstonian → smCentralEuroRoman - 28: 29, // langLatvian → smCentralEuroRoman - 29: 0, // langSami → smRoman - 30: 0, // langFaroese → smRoman (modified) - 31: 4, // langFarsi → smArabic (modified) - 32: 7, // langRussian → smCyrillic - 33: 25, // langSimpChinese → smSimpChinese - 34: 0, // langFlemish → smRoman - 35: 0, // langIrishGaelic → smRoman (modified) - 36: 0, // langAlbanian → smRoman - 37: 0, // langRomanian → smRoman (modified) - 38: 29, // langCzech → smCentralEuroRoman - 39: 29, // langSlovak → smCentralEuroRoman - 40: 0, // langSlovenian → smRoman (modified) - 41: 5, // langYiddish → smHebrew - 42: 7, // langSerbian → smCyrillic - 43: 7, // langMacedonian → smCyrillic - 44: 7, // langBulgarian → smCyrillic - 45: 7, // langUkrainian → smCyrillic (modified) - 46: 7, // langByelorussian → smCyrillic - 47: 7, // langUzbek → smCyrillic - 48: 7, // langKazakh → smCyrillic - 49: 7, // langAzerbaijani → smCyrillic - 50: 4, // langAzerbaijanAr → smArabic - 51: 24, // langArmenian → smArmenian - 52: 23, // langGeorgian → smGeorgian - 53: 7, // langMoldavian → smCyrillic - 54: 7, // langKirghiz → smCyrillic - 55: 7, // langTajiki → smCyrillic - 56: 7, // langTurkmen → smCyrillic - 57: 27, // langMongolian → smMongolian - 58: 7, // langMongolianCyr → smCyrillic - 59: 4, // langPashto → smArabic - 60: 4, // langKurdish → smArabic - 61: 4, // langKashmiri → smArabic - 62: 4, // langSindhi → smArabic - 63: 26, // langTibetan → smTibetan - 64: 9, // langNepali → smDevanagari - 65: 9, // langSanskrit → smDevanagari - 66: 9, // langMarathi → smDevanagari - 67: 13, // langBengali → smBengali - 68: 13, // langAssamese → smBengali - 69: 11, // langGujarati → smGujarati - 70: 10, // langPunjabi → smGurmukhi - 71: 12, // langOriya → smOriya - 72: 17, // langMalayalam → smMalayalam - 73: 16, // langKannada → smKannada - 74: 14, // langTamil → smTamil - 75: 15, // langTelugu → smTelugu - 76: 18, // langSinhalese → smSinhalese - 77: 19, // langBurmese → smBurmese - 78: 20, // langKhmer → smKhmer - 79: 22, // langLao → smLao - 80: 30, // langVietnamese → smVietnamese - 81: 0, // langIndonesian → smRoman - 82: 0, // langTagalog → smRoman - 83: 0, // langMalayRoman → smRoman - 84: 4, // langMalayArabic → smArabic - 85: 28, // langAmharic → smEthiopic - 86: 28, // langTigrinya → smEthiopic - 87: 28, // langOromo → smEthiopic - 88: 0, // langSomali → smRoman - 89: 0, // langSwahili → smRoman - 90: 0, // langKinyarwanda → smRoman - 91: 0, // langRundi → smRoman - 92: 0, // langNyanja → smRoman - 93: 0, // langMalagasy → smRoman - 94: 0, // langEsperanto → smRoman - 128: 0, // langWelsh → smRoman (modified) - 129: 0, // langBasque → smRoman - 130: 0, // langCatalan → smRoman - 131: 0, // langLatin → smRoman - 132: 0, // langQuechua → smRoman - 133: 0, // langGuarani → smRoman - 134: 0, // langAymara → smRoman - 135: 7, // langTatar → smCyrillic - 136: 4, // langUighur → smArabic - 137: 26, // langDzongkha → smTibetan - 138: 0, // langJavaneseRom → smRoman - 139: 0, // langSundaneseRom → smRoman - 140: 0, // langGalician → smRoman - 141: 0, // langAfrikaans → smRoman - 142: 0, // langBreton → smRoman (modified) - 143: 28, // langInuktitut → smEthiopic (modified) - 144: 0, // langScottishGaelic → smRoman (modified) - 145: 0, // langManxGaelic → smRoman (modified) - 146: 0, // langIrishGaelicScript → smRoman (modified) - 147: 0, // langTongan → smRoman - 148: 6, // langGreekAncient → smRoman - 149: 0, // langGreenlandic → smRoman - 150: 0, // langAzerbaijanRoman → smRoman - 151: 0 // langNynorsk → smRoman -}; + ctx.closePath(); + ctx.fill(); + } -// While Microsoft indicates a region/country for all its language -// IDs, we omit the region code if it's equal to the "most likely -// region subtag" according to Unicode CLDR. For scripts, we omit -// the subtag if it is equal to the Suppress-Script entry in the -// IANA language subtag registry for IETF BCP 47. -// -// For example, Microsoft states that its language code 0x041A is -// Croatian in Croatia. We transform this to the BCP 47 language code 'hr' -// and not 'hr-HR' because Croatia is the default country for Croatian, -// according to Unicode CLDR. As another example, Microsoft states -// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform -// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script -// for the Croatian language, according to IANA. -// -// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html -// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry -var windowsLanguages = { - 0x0436: 'af', - 0x041C: 'sq', - 0x0484: 'gsw', - 0x045E: 'am', - 0x1401: 'ar-DZ', - 0x3C01: 'ar-BH', - 0x0C01: 'ar', - 0x0801: 'ar-IQ', - 0x2C01: 'ar-JO', - 0x3401: 'ar-KW', - 0x3001: 'ar-LB', - 0x1001: 'ar-LY', - 0x1801: 'ary', - 0x2001: 'ar-OM', - 0x4001: 'ar-QA', - 0x0401: 'ar-SA', - 0x2801: 'ar-SY', - 0x1C01: 'aeb', - 0x3801: 'ar-AE', - 0x2401: 'ar-YE', - 0x042B: 'hy', - 0x044D: 'as', - 0x082C: 'az-Cyrl', - 0x042C: 'az', - 0x046D: 'ba', - 0x042D: 'eu', - 0x0423: 'be', - 0x0845: 'bn', - 0x0445: 'bn-IN', - 0x201A: 'bs-Cyrl', - 0x141A: 'bs', - 0x047E: 'br', - 0x0402: 'bg', - 0x0403: 'ca', - 0x0C04: 'zh-HK', - 0x1404: 'zh-MO', - 0x0804: 'zh', - 0x1004: 'zh-SG', - 0x0404: 'zh-TW', - 0x0483: 'co', - 0x041A: 'hr', - 0x101A: 'hr-BA', - 0x0405: 'cs', - 0x0406: 'da', - 0x048C: 'prs', - 0x0465: 'dv', - 0x0813: 'nl-BE', - 0x0413: 'nl', - 0x0C09: 'en-AU', - 0x2809: 'en-BZ', - 0x1009: 'en-CA', - 0x2409: 'en-029', - 0x4009: 'en-IN', - 0x1809: 'en-IE', - 0x2009: 'en-JM', - 0x4409: 'en-MY', - 0x1409: 'en-NZ', - 0x3409: 'en-PH', - 0x4809: 'en-SG', - 0x1C09: 'en-ZA', - 0x2C09: 'en-TT', - 0x0809: 'en-GB', - 0x0409: 'en', - 0x3009: 'en-ZW', - 0x0425: 'et', - 0x0438: 'fo', - 0x0464: 'fil', - 0x040B: 'fi', - 0x080C: 'fr-BE', - 0x0C0C: 'fr-CA', - 0x040C: 'fr', - 0x140C: 'fr-LU', - 0x180C: 'fr-MC', - 0x100C: 'fr-CH', - 0x0462: 'fy', - 0x0456: 'gl', - 0x0437: 'ka', - 0x0C07: 'de-AT', - 0x0407: 'de', - 0x1407: 'de-LI', - 0x1007: 'de-LU', - 0x0807: 'de-CH', - 0x0408: 'el', - 0x046F: 'kl', - 0x0447: 'gu', - 0x0468: 'ha', - 0x040D: 'he', - 0x0439: 'hi', - 0x040E: 'hu', - 0x040F: 'is', - 0x0470: 'ig', - 0x0421: 'id', - 0x045D: 'iu', - 0x085D: 'iu-Latn', - 0x083C: 'ga', - 0x0434: 'xh', - 0x0435: 'zu', - 0x0410: 'it', - 0x0810: 'it-CH', - 0x0411: 'ja', - 0x044B: 'kn', - 0x043F: 'kk', - 0x0453: 'km', - 0x0486: 'quc', - 0x0487: 'rw', - 0x0441: 'sw', - 0x0457: 'kok', - 0x0412: 'ko', - 0x0440: 'ky', - 0x0454: 'lo', - 0x0426: 'lv', - 0x0427: 'lt', - 0x082E: 'dsb', - 0x046E: 'lb', - 0x042F: 'mk', - 0x083E: 'ms-BN', - 0x043E: 'ms', - 0x044C: 'ml', - 0x043A: 'mt', - 0x0481: 'mi', - 0x047A: 'arn', - 0x044E: 'mr', - 0x047C: 'moh', - 0x0450: 'mn', - 0x0850: 'mn-CN', - 0x0461: 'ne', - 0x0414: 'nb', - 0x0814: 'nn', - 0x0482: 'oc', - 0x0448: 'or', - 0x0463: 'ps', - 0x0415: 'pl', - 0x0416: 'pt', - 0x0816: 'pt-PT', - 0x0446: 'pa', - 0x046B: 'qu-BO', - 0x086B: 'qu-EC', - 0x0C6B: 'qu', - 0x0418: 'ro', - 0x0417: 'rm', - 0x0419: 'ru', - 0x243B: 'smn', - 0x103B: 'smj-NO', - 0x143B: 'smj', - 0x0C3B: 'se-FI', - 0x043B: 'se', - 0x083B: 'se-SE', - 0x203B: 'sms', - 0x183B: 'sma-NO', - 0x1C3B: 'sms', - 0x044F: 'sa', - 0x1C1A: 'sr-Cyrl-BA', - 0x0C1A: 'sr', - 0x181A: 'sr-Latn-BA', - 0x081A: 'sr-Latn', - 0x046C: 'nso', - 0x0432: 'tn', - 0x045B: 'si', - 0x041B: 'sk', - 0x0424: 'sl', - 0x2C0A: 'es-AR', - 0x400A: 'es-BO', - 0x340A: 'es-CL', - 0x240A: 'es-CO', - 0x140A: 'es-CR', - 0x1C0A: 'es-DO', - 0x300A: 'es-EC', - 0x440A: 'es-SV', - 0x100A: 'es-GT', - 0x480A: 'es-HN', - 0x080A: 'es-MX', - 0x4C0A: 'es-NI', - 0x180A: 'es-PA', - 0x3C0A: 'es-PY', - 0x280A: 'es-PE', - 0x500A: 'es-PR', - - // Microsoft has defined two different language codes for - // “Spanish with modern sorting” and “Spanish with traditional - // sorting”. This makes sense for collation APIs, and it would be - // possible to express this in BCP 47 language tags via Unicode - // extensions (eg., es-u-co-trad is Spanish with traditional - // sorting). However, for storing names in fonts, the distinction - // does not make sense, so we give “es” in both cases. - 0x0C0A: 'es', - 0x040A: 'es', - - 0x540A: 'es-US', - 0x380A: 'es-UY', - 0x200A: 'es-VE', - 0x081D: 'sv-FI', - 0x041D: 'sv', - 0x045A: 'syr', - 0x0428: 'tg', - 0x085F: 'tzm', - 0x0449: 'ta', - 0x0444: 'tt', - 0x044A: 'te', - 0x041E: 'th', - 0x0451: 'bo', - 0x041F: 'tr', - 0x0442: 'tk', - 0x0480: 'ug', - 0x0422: 'uk', - 0x042E: 'hsb', - 0x0420: 'ur', - 0x0843: 'uz-Cyrl', - 0x0443: 'uz', - 0x042A: 'vi', - 0x0452: 'cy', - 0x0488: 'wo', - 0x0485: 'sah', - 0x0478: 'ii', - 0x046A: 'yo' -}; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; -// Returns a IETF BCP 47 language code, for example 'zh-Hant' -// for 'Chinese in the traditional script'. -function getLanguageCode(platformID, languageID, ltag) { - switch (platformID) { - case 0: // Unicode - if (languageID === 0xFFFF) { - return 'und'; - } else if (ltag) { - return ltag[languageID]; + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); } - break; + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } - case 1: // Macintosh - return macLanguages[languageID]; - - case 3: // Windows - return windowsLanguages[languageID]; - } - - return undefined; -} - -var utf16 = 'utf-16'; - -// MacOS script ID → encoding. This table stores the default case, -// which can be overridden by macLanguageEncodings. -var macScriptEncodings = { - 0: 'macintosh', // smRoman - 1: 'x-mac-japanese', // smJapanese - 2: 'x-mac-chinesetrad', // smTradChinese - 3: 'x-mac-korean', // smKorean - 6: 'x-mac-greek', // smGreek - 7: 'x-mac-cyrillic', // smCyrillic - 9: 'x-mac-devanagai', // smDevanagari - 10: 'x-mac-gurmukhi', // smGurmukhi - 11: 'x-mac-gujarati', // smGujarati - 12: 'x-mac-oriya', // smOriya - 13: 'x-mac-bengali', // smBengali - 14: 'x-mac-tamil', // smTamil - 15: 'x-mac-telugu', // smTelugu - 16: 'x-mac-kannada', // smKannada - 17: 'x-mac-malayalam', // smMalayalam - 18: 'x-mac-sinhalese', // smSinhalese - 19: 'x-mac-burmese', // smBurmese - 20: 'x-mac-khmer', // smKhmer - 21: 'x-mac-thai', // smThai - 22: 'x-mac-lao', // smLao - 23: 'x-mac-georgian', // smGeorgian - 24: 'x-mac-armenian', // smArmenian - 25: 'x-mac-chinesesimp', // smSimpChinese - 26: 'x-mac-tibetan', // smTibetan - 27: 'x-mac-mongolian', // smMongolian - 28: 'x-mac-ethiopic', // smEthiopic - 29: 'x-mac-ce', // smCentralEuroRoman - 30: 'x-mac-vietnamese', // smVietnamese - 31: 'x-mac-extarabic' // smExtArabic -}; + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } -// MacOS language ID → encoding. This table stores the exceptional -// cases, which override macScriptEncodings. For writing MacOS naming -// tables, we need to emit a MacOS script ID. Therefore, we cannot -// merge macScriptEncodings into macLanguageEncodings. -// -// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt -var macLanguageEncodings = { - 15: 'x-mac-icelandic', // langIcelandic - 17: 'x-mac-turkish', // langTurkish - 18: 'x-mac-croatian', // langCroatian - 24: 'x-mac-ce', // langLithuanian - 25: 'x-mac-ce', // langPolish - 26: 'x-mac-ce', // langHungarian - 27: 'x-mac-ce', // langEstonian - 28: 'x-mac-ce', // langLatvian - 30: 'x-mac-icelandic', // langFaroese - 37: 'x-mac-romanian', // langRomanian - 38: 'x-mac-ce', // langCzech - 39: 'x-mac-ce', // langSlovak - 40: 'x-mac-ce', // langSlovenian - 143: 'x-mac-inuit', // langInuktitut - 146: 'x-mac-gaelic' // langIrishGaelicScript -}; + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); + }; -function getEncoding(platformID, encodingID, languageID) { - switch (platformID) { - case 0: // Unicode - return utf16; + /** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ + Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); + }; - case 1: // Apple Macintosh - return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + // The GlyphSet object + + // Define a property on the glyph that depends on the path being loaded. + function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); + } - case 3: // Microsoft Windows - if (encodingID === 1 || encodingID === 10) { - return utf16; + /** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ + function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; } + } - break; + this.length = (glyphs && glyphs.length) || 0; } - return undefined; -} - -// Parse the naming `name` table. -// FIXME: Format 1 additional fields are not supported yet. -// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. -function parseNameTable(data, start, ltag) { - var name = {}; - var p = new parse.Parser(data, start); - var format = p.parseUShort(); - var count = p.parseUShort(); - var stringOffset = p.offset + p.parseUShort(); - for (var i = 0; i < count; i++) { - var platformID = p.parseUShort(); - var encodingID = p.parseUShort(); - var languageID = p.parseUShort(); - var nameID = p.parseUShort(); - var property = nameTableNames[nameID] || nameID; - var byteLength = p.parseUShort(); - var offset = p.parseUShort(); - var language = getLanguageCode(platformID, languageID, ltag); - var encoding = getEncoding(platformID, encodingID, languageID); - if (encoding !== undefined && language !== undefined) { - var text = (void 0); - if (encoding === utf16) { - text = decode.UTF16(data, stringOffset + offset, byteLength); - } else { - text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); + /** + * @param {number} index + * @return {opentype.Glyph} + */ + GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); } - if (text) { - var translations = name[property]; - if (translations === undefined) { - translations = name[property] = {}; + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } + } + + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } - translations[language] = text; + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); } } - } - var langTagCount = 0; - if (format === 1) { - // FIXME: Also handle Microsoft's 'name' table 1. - langTagCount = p.parseUShort(); - } + return this.glyphs[index]; + }; - return name; -} + /** + * @param {number} index + * @param {Object} + */ + GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; + }; -// {23: 'foo'} → {'foo': 23} -// ['bar', 'baz'] → {'bar': 0, 'baz': 1} -function reverseDict(dict) { - var result = {}; - for (var key in dict) { - result[dict[key]] = parseInt(key); + /** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ + function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); } - return result; -} + /** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ + function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; -function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { - return new table.Record('NameRecord', [ - {name: 'platformID', type: 'USHORT', value: platformID}, - {name: 'encodingID', type: 'USHORT', value: encodingID}, - {name: 'languageID', type: 'USHORT', value: languageID}, - {name: 'nameID', type: 'USHORT', value: nameID}, - {name: 'length', type: 'USHORT', value: length}, - {name: 'offset', type: 'USHORT', value: offset} - ]); -} - -// Finds the position of needle in haystack, or -1 if not there. -// Like String.indexOf(), but for arrays. -function findSubArray(needle, haystack) { - var needleLength = needle.length; - var limit = haystack.length - needleLength + 1; - - loop: - for (var pos = 0; pos < limit; pos++) { - for (; pos < limit; pos++) { - for (var k = 0; k < needleLength; k++) { - if (haystack[pos + k] !== needle[k]) { - continue loop; - } - } + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); - return pos; - } + return glyph; + }; } + /** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ + function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; - return -1; -} + return glyph; + }; + } -function addStringToPool(s, pool) { - var offset = findSubArray(s, pool); - if (offset < 0) { - offset = pool.length; - var i = 0; - var len = s.length; - for (; i < len; ++i) { - pool.push(s[i]); - } + var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; - } + // The `CFF` table contains the glyph outlines in PostScript format. + + // Custom equals function that can also check lists. + function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } - return offset; -} + return true; + } else { + return false; + } + } -function makeNameTable(names, ltag) { - var nameID; - var nameIDs = []; + // Subroutines are encoded using the negative half of the number space. + // See type 2 chapter 4.7 "Subroutine operators". + function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; + } + + // Parse a `CFF` INDEX array. + // An index array consists of a list of offsets, then a list of objects at those offsets. + function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } - var namesWithNumericKeys = {}; - var nameTableIds = reverseDict(nameTableNames); - for (var key in names) { - var id = nameTableIds[key]; - if (id === undefined) { - id = key; + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; } - nameID = parseInt(id); + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); + } - if (isNaN(nameID)) { - throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + objects.push(value); } - namesWithNumericKeys[nameID] = names[key]; - nameIDs.push(nameID); + return {objects: objects, startOffset: start, endOffset: endOffset}; } - var macLanguageIds = reverseDict(macLanguages); - var windowsLanguageIds = reverseDict(windowsLanguages); + function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } - var nameRecords = []; - var stringPool = []; + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } - for (var i = 0; i < nameIDs.length; i++) { - nameID = nameIDs[i]; - var translations = namesWithNumericKeys[nameID]; - for (var lang in translations) { - var text = translations[lang]; + return {offsets: offsets, startOffset: start, endOffset: endOffset}; + } + function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } - // For MacOS, we try to emit the name in the form that was introduced - // in the initial version of the TrueType spec (in the late 1980s). - // However, this can fail for various reasons: the requested BCP 47 - // language code might not have an old-style Mac equivalent; - // we might not have a codec for the needed character encoding; - // or the name might contain characters that cannot be expressed - // in the old-style Macintosh encoding. In case of failure, we emit - // the name in a more modern fashion (Unicode encoding with BCP 47 - // language tags) that is recognized by MacOS 10.5, released in 2009. - // If fonts were only read by operating systems, we could simply - // emit all names in the modern form; this would be much easier. - // However, there are many applications and libraries that read - // 'name' tables directly, and these will usually only recognize - // the ancient form (silently skipping the unrecognized names). - var macPlatform = 1; // Macintosh - var macLanguage = macLanguageIds[lang]; - var macScript = macLanguageToScript[macLanguage]; - var macEncoding = getEncoding(macPlatform, macScript, macLanguage); - var macName = encode.MACSTRING(text, macEncoding); - if (macName === undefined) { - macPlatform = 0; // Unicode - macLanguage = ltag.indexOf(lang); - if (macLanguage < 0) { - macLanguage = ltag.length; - ltag.push(lang); - } + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; + } - macScript = 4; // Unicode 2.0 and later - macName = encode.UTF16(text); + // Parse a `CFF` DICT real value. + function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; } - var macNameOffset = addStringToPool(macName, stringPool); - nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, - nameID, macName.length, macNameOffset)); + s += lookup[n1]; - var winLanguage = windowsLanguageIds[lang]; - if (winLanguage !== undefined) { - var winName = encode.UTF16(text); - var winNameOffset = addStringToPool(winName, stringPool); - nameRecords.push(makeNameRecord(3, 1, winLanguage, - nameID, winName.length, winNameOffset)); + if (n2 === eof) { + break; } + + s += lookup[n2]; } + + return parseFloat(s); } - nameRecords.sort(function(a, b) { - return ((a.platformID - b.platformID) || - (a.encodingID - b.encodingID) || - (a.languageID - b.languageID) || - (a.nameID - b.nameID)); - }); + // Parse a `CFF` DICT operand. + function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } - var t = new table.Table('name', [ - {name: 'format', type: 'USHORT', value: 0}, - {name: 'count', type: 'USHORT', value: nameRecords.length}, - {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} - ]); + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } - for (var r = 0; r < nameRecords.length; r++) { - t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); - } - - t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); - return t; -} - -var _name = { parse: parseNameTable, make: makeNameTable }; - -// The `OS/2` table contains metrics required in OpenType fonts. - -var unicodeRanges = [ - {begin: 0x0000, end: 0x007F}, // Basic Latin - {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement - {begin: 0x0100, end: 0x017F}, // Latin Extended-A - {begin: 0x0180, end: 0x024F}, // Latin Extended-B - {begin: 0x0250, end: 0x02AF}, // IPA Extensions - {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters - {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks - {begin: 0x0370, end: 0x03FF}, // Greek and Coptic - {begin: 0x2C80, end: 0x2CFF}, // Coptic - {begin: 0x0400, end: 0x04FF}, // Cyrillic - {begin: 0x0530, end: 0x058F}, // Armenian - {begin: 0x0590, end: 0x05FF}, // Hebrew - {begin: 0xA500, end: 0xA63F}, // Vai - {begin: 0x0600, end: 0x06FF}, // Arabic - {begin: 0x07C0, end: 0x07FF}, // NKo - {begin: 0x0900, end: 0x097F}, // Devanagari - {begin: 0x0980, end: 0x09FF}, // Bengali - {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi - {begin: 0x0A80, end: 0x0AFF}, // Gujarati - {begin: 0x0B00, end: 0x0B7F}, // Oriya - {begin: 0x0B80, end: 0x0BFF}, // Tamil - {begin: 0x0C00, end: 0x0C7F}, // Telugu - {begin: 0x0C80, end: 0x0CFF}, // Kannada - {begin: 0x0D00, end: 0x0D7F}, // Malayalam - {begin: 0x0E00, end: 0x0E7F}, // Thai - {begin: 0x0E80, end: 0x0EFF}, // Lao - {begin: 0x10A0, end: 0x10FF}, // Georgian - {begin: 0x1B00, end: 0x1B7F}, // Balinese - {begin: 0x1100, end: 0x11FF}, // Hangul Jamo - {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional - {begin: 0x1F00, end: 0x1FFF}, // Greek Extended - {begin: 0x2000, end: 0x206F}, // General Punctuation - {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts - {begin: 0x20A0, end: 0x20CF}, // Currency Symbol - {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols - {begin: 0x2100, end: 0x214F}, // Letterlike Symbols - {begin: 0x2150, end: 0x218F}, // Number Forms - {begin: 0x2190, end: 0x21FF}, // Arrows - {begin: 0x2200, end: 0x22FF}, // Mathematical Operators - {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical - {begin: 0x2400, end: 0x243F}, // Control Pictures - {begin: 0x2440, end: 0x245F}, // Optical Character Recognition - {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics - {begin: 0x2500, end: 0x257F}, // Box Drawing - {begin: 0x2580, end: 0x259F}, // Block Elements - {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes - {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols - {begin: 0x2700, end: 0x27BF}, // Dingbats - {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation - {begin: 0x3040, end: 0x309F}, // Hiragana - {begin: 0x30A0, end: 0x30FF}, // Katakana - {begin: 0x3100, end: 0x312F}, // Bopomofo - {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo - {begin: 0xA840, end: 0xA87F}, // Phags-pa - {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months - {begin: 0x3300, end: 0x33FF}, // CJK Compatibility - {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables - {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * - {begin: 0x10900, end: 0x1091F}, // Phoenicia - {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs - {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) - {begin: 0x31C0, end: 0x31EF}, // CJK Strokes - {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms - {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A - {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks - {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms - {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants - {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B - {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms - {begin: 0xFFF0, end: 0xFFFF}, // Specials - {begin: 0x0F00, end: 0x0FFF}, // Tibetan - {begin: 0x0700, end: 0x074F}, // Syriac - {begin: 0x0780, end: 0x07BF}, // Thaana - {begin: 0x0D80, end: 0x0DFF}, // Sinhala - {begin: 0x1000, end: 0x109F}, // Myanmar - {begin: 0x1200, end: 0x137F}, // Ethiopic - {begin: 0x13A0, end: 0x13FF}, // Cherokee - {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics - {begin: 0x1680, end: 0x169F}, // Ogham - {begin: 0x16A0, end: 0x16FF}, // Runic - {begin: 0x1780, end: 0x17FF}, // Khmer - {begin: 0x1800, end: 0x18AF}, // Mongolian - {begin: 0x2800, end: 0x28FF}, // Braille Patterns - {begin: 0xA000, end: 0xA48F}, // Yi Syllables - {begin: 0x1700, end: 0x171F}, // Tagalog - {begin: 0x10300, end: 0x1032F}, // Old Italic - {begin: 0x10330, end: 0x1034F}, // Gothic - {begin: 0x10400, end: 0x1044F}, // Deseret - {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols - {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols - {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) - {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors - {begin: 0xE0000, end: 0xE007F}, // Tags - {begin: 0x1900, end: 0x194F}, // Limbu - {begin: 0x1950, end: 0x197F}, // Tai Le - {begin: 0x1980, end: 0x19DF}, // New Tai Lue - {begin: 0x1A00, end: 0x1A1F}, // Buginese - {begin: 0x2C00, end: 0x2C5F}, // Glagolitic - {begin: 0x2D30, end: 0x2D7F}, // Tifinagh - {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols - {begin: 0xA800, end: 0xA82F}, // Syloti Nagri - {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary - {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers - {begin: 0x10380, end: 0x1039F}, // Ugaritic - {begin: 0x103A0, end: 0x103DF}, // Old Persian - {begin: 0x10450, end: 0x1047F}, // Shavian - {begin: 0x10480, end: 0x104AF}, // Osmanya - {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary - {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi - {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols - {begin: 0x12000, end: 0x123FF}, // Cuneiform - {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals - {begin: 0x1B80, end: 0x1BBF}, // Sundanese - {begin: 0x1C00, end: 0x1C4F}, // Lepcha - {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki - {begin: 0xA880, end: 0xA8DF}, // Saurashtra - {begin: 0xA900, end: 0xA92F}, // Kayah Li - {begin: 0xA930, end: 0xA95F}, // Rejang - {begin: 0xAA00, end: 0xAA5F}, // Cham - {begin: 0x10190, end: 0x101CF}, // Ancient Symbols - {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc - {begin: 0x102A0, end: 0x102DF}, // Carian - {begin: 0x1F030, end: 0x1F09F} // Domino Tiles -]; - -function getUnicodeRange(unicode) { - for (var i = 0; i < unicodeRanges.length; i += 1) { - var range = unicodeRanges[i]; - if (unicode >= range.begin && unicode < range.end) { - return i; - } - } - - return -1; -} - -// Parse the OS/2 and Windows metrics `OS/2` table -function parseOS2Table(data, start) { - var os2 = {}; - var p = new parse.Parser(data, start); - os2.version = p.parseUShort(); - os2.xAvgCharWidth = p.parseShort(); - os2.usWeightClass = p.parseUShort(); - os2.usWidthClass = p.parseUShort(); - os2.fsType = p.parseUShort(); - os2.ySubscriptXSize = p.parseShort(); - os2.ySubscriptYSize = p.parseShort(); - os2.ySubscriptXOffset = p.parseShort(); - os2.ySubscriptYOffset = p.parseShort(); - os2.ySuperscriptXSize = p.parseShort(); - os2.ySuperscriptYSize = p.parseShort(); - os2.ySuperscriptXOffset = p.parseShort(); - os2.ySuperscriptYOffset = p.parseShort(); - os2.yStrikeoutSize = p.parseShort(); - os2.yStrikeoutPosition = p.parseShort(); - os2.sFamilyClass = p.parseShort(); - os2.panose = []; - for (var i = 0; i < 10; i++) { - os2.panose[i] = p.parseByte(); - } - - os2.ulUnicodeRange1 = p.parseULong(); - os2.ulUnicodeRange2 = p.parseULong(); - os2.ulUnicodeRange3 = p.parseULong(); - os2.ulUnicodeRange4 = p.parseULong(); - os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); - os2.fsSelection = p.parseUShort(); - os2.usFirstCharIndex = p.parseUShort(); - os2.usLastCharIndex = p.parseUShort(); - os2.sTypoAscender = p.parseShort(); - os2.sTypoDescender = p.parseShort(); - os2.sTypoLineGap = p.parseShort(); - os2.usWinAscent = p.parseUShort(); - os2.usWinDescent = p.parseUShort(); - if (os2.version >= 1) { - os2.ulCodePageRange1 = p.parseULong(); - os2.ulCodePageRange2 = p.parseULong(); - } - - if (os2.version >= 2) { - os2.sxHeight = p.parseShort(); - os2.sCapHeight = p.parseShort(); - os2.usDefaultChar = p.parseUShort(); - os2.usBreakChar = p.parseUShort(); - os2.usMaxContent = p.parseUShort(); - } - - return os2; -} - -function makeOS2Table(options) { - return new table.Table('OS/2', [ - {name: 'version', type: 'USHORT', value: 0x0003}, - {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, - {name: 'usWeightClass', type: 'USHORT', value: 0}, - {name: 'usWidthClass', type: 'USHORT', value: 0}, - {name: 'fsType', type: 'USHORT', value: 0}, - {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, - {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, - {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, - {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, - {name: 'sFamilyClass', type: 'SHORT', value: 0}, - {name: 'bFamilyType', type: 'BYTE', value: 0}, - {name: 'bSerifStyle', type: 'BYTE', value: 0}, - {name: 'bWeight', type: 'BYTE', value: 0}, - {name: 'bProportion', type: 'BYTE', value: 0}, - {name: 'bContrast', type: 'BYTE', value: 0}, - {name: 'bStrokeVariation', type: 'BYTE', value: 0}, - {name: 'bArmStyle', type: 'BYTE', value: 0}, - {name: 'bLetterform', type: 'BYTE', value: 0}, - {name: 'bMidline', type: 'BYTE', value: 0}, - {name: 'bXHeight', type: 'BYTE', value: 0}, - {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, - {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, - {name: 'fsSelection', type: 'USHORT', value: 0}, - {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, - {name: 'usLastCharIndex', type: 'USHORT', value: 0}, - {name: 'sTypoAscender', type: 'SHORT', value: 0}, - {name: 'sTypoDescender', type: 'SHORT', value: 0}, - {name: 'sTypoLineGap', type: 'SHORT', value: 0}, - {name: 'usWinAscent', type: 'USHORT', value: 0}, - {name: 'usWinDescent', type: 'USHORT', value: 0}, - {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, - {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, - {name: 'sxHeight', type: 'SHORT', value: 0}, - {name: 'sCapHeight', type: 'SHORT', value: 0}, - {name: 'usDefaultChar', type: 'USHORT', value: 0}, - {name: 'usBreakChar', type: 'USHORT', value: 0}, - {name: 'usMaxContext', type: 'USHORT', value: 0} - ], options); -} - -var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; - -// The `post` table stores additional PostScript information, such as glyph names. - -// Parse the PostScript `post` table -function parsePostTable(data, start) { - var post = {}; - var p = new parse.Parser(data, start); - post.version = p.parseVersion(); - post.italicAngle = p.parseFixed(); - post.underlinePosition = p.parseShort(); - post.underlineThickness = p.parseShort(); - post.isFixedPitch = p.parseULong(); - post.minMemType42 = p.parseULong(); - post.maxMemType42 = p.parseULong(); - post.minMemType1 = p.parseULong(); - post.maxMemType1 = p.parseULong(); - switch (post.version) { - case 1: - post.names = standardNames.slice(); - break; - case 2: - post.numberOfGlyphs = p.parseUShort(); - post.glyphNameIndex = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - post.glyphNameIndex[i] = p.parseUShort(); - } + if (b0 === 30) { + return parseFloatOperand(parser); + } - post.names = []; - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - if (post.glyphNameIndex[i$1] >= standardNames.length) { - var nameLength = p.parseChar(); - post.names.push(p.parseString(nameLength)); - } + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); + } + + // Convert the entries returned by `parseDict` to a proper dictionary. + // If a value is a list of one, it is unpacked. + function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; } - break; - case 2.5: - post.numberOfGlyphs = p.parseUShort(); - post.offset = new Array(post.numberOfGlyphs); - for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { - post.offset[i$2] = p.parseChar(); + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); } - break; + o[key] = value; + } + + return o; } - return post; -} - -function makePostTable() { - return new table.Table('post', [ - {name: 'version', type: 'FIXED', value: 0x00030000}, - {name: 'italicAngle', type: 'FIXED', value: 0}, - {name: 'underlinePosition', type: 'FWORD', value: 0}, - {name: 'underlineThickness', type: 'FWORD', value: 0}, - {name: 'isFixedPitch', type: 'ULONG', value: 0}, - {name: 'minMemType42', type: 'ULONG', value: 0}, - {name: 'maxMemType42', type: 'ULONG', value: 0}, - {name: 'minMemType1', type: 'ULONG', value: 0}, - {name: 'maxMemType1', type: 'ULONG', value: 0} - ]); -} -var post = { parse: parsePostTable, make: makePostTable }; + // Parse a `CFF` DICT object. + // A dictionary contains key-value pairs in a compact tokenized format. + function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; -// The `GSUB` table contains ligatures, among other things. + while (parser.relativeOffset < size) { + var op = parser.parseByte(); -var subtableParsers = new Array(9); // subtableParsers[0] is unused + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS -subtableParsers[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - deltaGlyphId: this.parseUShort() - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - substitute: this.parseOffset16List() - }; + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); } - check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); -}; -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS -subtableParsers[2] = function parseLookup2() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - sequences: this.parseListOfLists() - }; -}; + // Given a String Index (SID), return the value of the string. + // Strings below index 392 are standard CFF strings and are not encoded in the font. + function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS -subtableParsers[3] = function parseLookup3() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - alternateSets: this.parseListOfLists() - }; -}; + return index; + } -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS -subtableParsers[4] = function parseLookup4() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ligatureSets: this.parseListOfLists(function() { - return { - ligGlyph: this.parseUShort(), - components: this.parseUShortList(this.parseUShort() - 1) - }; - }) - }; -}; + // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. + // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. + function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; -var lookupRecordDesc = { - sequenceIndex: Parser.uShort, - lookupListIndex: Parser.uShort -}; + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF -subtableParsers[5] = function parseLookup5() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } - if (substFormat === 1) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ruleSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - input: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - classDef: this.parsePointer(Parser.classDef), - classSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - classes: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - substFormat: substFormat, - coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; + } + + // Parse the CFF header. + function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; + } + + var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} + ]; + + var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} + ]; + + // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. + // The top dictionary contains the essential metadata for the font, together with the private dictionary. + function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); } - check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); -}; -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC -subtableParsers[6] = function parseLookup6() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - chainRuleSets: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - backtrackClassDef: this.parsePointer(Parser.classDef), - inputClassDef: this.parsePointer(Parser.classDef), - lookaheadClassDef: this.parsePointer(Parser.classDef), - chainClassSet: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - return { - substFormat: 3, - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; + // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. + function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); } - check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); -}; -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES -subtableParsers[7] = function parseLookup7() { - // Extension Substitution subtable - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); - var extensionLookupType = this.parseUShort(); - var extensionParser = new Parser(this.data, this.offset + this.parseULong()); - return { - substFormat: 1, - lookupType: extensionLookupType, - extension: subtableParsers[extensionLookupType].call(extensionParser) - }; -}; + // Returns a list of "Top DICT"s found using an INDEX list. + // Used to read both the usual high-level Top DICTs and also the FDArray + // discovered inside CID-keyed fonts. When a Top DICT has a reference to + // a Private DICT that is read and saved into the Top DICT. + // + // In addition to the expected/optional values as outlined in TOP_DICT_META + // the following values might be saved into the Top DICT. + // + // _subrs [] array of local CFF subroutines from Private DICT + // _subrsBias bias value computed from number of subroutines + // (see calcCFFSubroutineBias() and parseCFFCharstring()) + // _defaultWidthX default widths for CFF characters + // _nominalWidthX bias added to width embedded within glyph description + // + // _privateDict saved copy of parsed Private DICT from Top DICT + function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + topDict._defaultWidthX = 0; + topDict._nominalWidthX = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; + } -// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS -subtableParsers[8] = function parseLookup8() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - substitutes: this.parseUShortList() - }; -}; + // Parse the CFF charset table, which contains internal names for all the glyphs. + // This function will return a list of glyph names. + // See Adobe TN #5176 chapter 13, "Charsets". + function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); -// https://www.microsoft.com/typography/OTSPEC/gsub.htm -function parseGsubTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers), - variations: p.parseFeatureVariationsList() - }; + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; } -} + // Parse the CFF encoding data. Only one encoding can be specified per font. + // See Adobe TN #5176 chapter 12, "Encodings". + function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); + } + + // Take in charstring code and return a Glyph object. + // The encoding is described in the Type 2 Charstring Format + // https://www.microsoft.com/typography/OTSPEC/charstr2.htm + function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; -// GSUB Writing ////////////////////////////////////////////// -var subtableMakers = new Array(9); + function newContour(x, y) { + if (open) { + p.closePath(); + } -subtableMakers[1] = function makeLookup1(subtable) { - if (subtable.substFormat === 1) { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, - {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} - ]); - } else { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 2}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.ushortList('substitute', subtable.substitute))); - } -}; + p.moveTo(x, y); + open = true; + } -subtableMakers[2] = function makeLookup2(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { - return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); - }))); -}; + function parseStems() { + var hasWidthArg; -subtableMakers[3] = function makeLookup3(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { - return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); - }))); -}; + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } -subtableMakers[4] = function makeLookup4(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { - return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { - return new table.Table('ligatureTable', - [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] - .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) - ); - })); - }))); -}; + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } -subtableMakers[6] = function makeLookup6(subtable) { - if (subtable.substFormat === 1) { - var returnTable = new table.Table('chainContextTable', [ - {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { - return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { - var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) - .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) - .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) - .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); - - chainRule.lookupRecords.forEach(function (record, i) { - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); - }); - return new table.Table('chainRuleTable', tableData); - })); - }))); - return returnTable; - } else if (subtable.substFormat === 2) { - check.assert(false, 'lookup type 6 format 2 is not yet supported.'); - } else if (subtable.substFormat === 3) { - var tableData = [ - {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; - - tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); - subtable.backtrackCoverage.forEach(function (coverage, i) { - tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); - }); - tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); - subtable.inputCoverage.forEach(function (coverage, i) { - tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); - }); - tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); - subtable.lookaheadCoverage.forEach(function (coverage, i) { - tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); - }); + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } - tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); - subtable.lookupRecords.forEach(function (record, i) { - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); - }); + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } - var returnTable$1 = new table.Table('chainContextTable', tableData); + y += stack.shift(); + p.lineTo(x, y); + } - return returnTable$1; - } + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } - check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); -}; + x += stack.shift(); + p.lineTo(x, y); + } -function makeGsubTable(gsub) { - return new table.Table('GSUB', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} - ]); -} - -var gsub = { parse: parseGsubTable, make: makeGsubTable }; - -// The `GPOS` table contains kerning pairs, among other things. - -// Parse the metadata `meta` table. -// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html -function parseMetaTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported META table version.'); - p.parseULong(); // flags - currently unused and set to 0 - p.parseULong(); // tableOffset - var numDataMaps = p.parseULong(); - - var tags = {}; - for (var i = 0; i < numDataMaps; i++) { - var tag = p.parseTag(); - var dataOffset = p.parseULong(); - var dataLength = p.parseULong(); - var text = decode.UTF8(data, start + dataOffset, dataLength); - - tags[tag] = text; - } - return tags; -} - -function makeMetaTable(tags) { - var numTags = Object.keys(tags).length; - var stringPool = ''; - var stringPoolOffset = 16 + numTags * 12; - - var result = new table.Table('meta', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'offset', type: 'ULONG', value: stringPoolOffset}, - {name: 'numTags', type: 'ULONG', value: numTags} - ]); + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - for (var tag in tags) { - var pos = stringPool.length; - stringPool += tags[tag]; + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } - result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); - result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); - } + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + if (open) { + p.closePath(); + open = false; + } - return result; -} + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } -var meta = { parse: parseMetaTable, make: makeMetaTable }; + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } -// The `COLR` table adds support for multi-colored glyphs + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } -function parseColrTable(data, start) { - var p = new Parser(data, start); - var version = p.parseUShort(); - check.argument(version === 0x0000, 'Only COLRv0 supported.'); - var numBaseGlyphRecords = p.parseUShort(); - var baseGlyphRecordsOffset = p.parseOffset32(); - var layerRecordsOffset = p.parseOffset32(); - var numLayerRecords = p.parseUShort(); - p.relativeOffset = baseGlyphRecordsOffset; - var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, { - glyphID: Parser.uShort, - firstLayerIndex: Parser.uShort, - numLayers: Parser.uShort, - }); - p.relativeOffset = layerRecordsOffset; - var layerRecords = p.parseRecordList(numLayerRecords, { - glyphID: Parser.uShort, - paletteIndex: Parser.uShort - }); + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } - return { - version: version, - baseGlyphRecords: baseGlyphRecords, - layerRecords: layerRecords, - }; -} + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } -function makeColrTable(ref) { - var version = ref.version; if ( version === void 0 ) version = 0x0000; - var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = []; - var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = []; + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } - check.argument(version === 0x0000, 'Only COLRv0 supported.'); - var baseGlyphRecordsOffset = 14; - var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6); - return new table.Table('COLR', [ - { name: 'version', type: 'USHORT', value: version }, - { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length }, - { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset }, - { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset }, - { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [ - { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID }, - { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex }, - { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(), - layerRecords.map(function (layer, i) { return [ - { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID }, - { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() )); -} - -var colr = { parse: parseColrTable, make: makeColrTable }; - -// The `CPAL` define a contiguous list of colors (colorRecords) - -// Parse the header `head` table -function parseCpalTable(data, start) { - var p = new Parser(data, start); - var version = p.parseShort(); - var numPaletteEntries = p.parseShort(); - var numPalettes = p.parseShort(); - var numColorRecords = p.parseShort(); - var colorRecordsArrayOffset = p.parseOffset32(); - var colorRecordIndices = p.parseUShortList(numPalettes); - p.relativeOffset = colorRecordsArrayOffset; - var colorRecords = p.parseULongList(numColorRecords); - return { - version: version, - numPaletteEntries: numPaletteEntries, - colorRecords: colorRecords, - colorRecordIndices: colorRecordIndices, - }; -} - -function makeCpalTable(ref) { - var version = ref.version; if ( version === void 0 ) version = 0; - var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0; - var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = []; - var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0]; - - check.argument(version === 0, 'Only CPALv0 are supported.'); - check.argument(colorRecords.length, 'No colorRecords given.'); - check.argument(colorRecordIndices.length, 'No colorRecordIndices given.'); - check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices'); - return new table.Table('CPAL', [ - { name: 'version', type: 'USHORT', value: version }, - { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length }, - { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length }, - { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length }, - { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }), - colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) )); -} - -var cpal = { parse: parseCpalTable, make: makeCpalTable }; - -// The `sfnt` wrapper provides organization for the tables in the font. - -function log2(v) { - return Math.log(v) / Math.log(2) | 0; -} - -function computeCheckSum(bytes) { - while (bytes.length % 4 !== 0) { - bytes.push(0); - } - - var sum = 0; - for (var i = 0; i < bytes.length; i += 4) { - sum += (bytes[i] << 24) + - (bytes[i + 1] << 16) + - (bytes[i + 2] << 8) + - (bytes[i + 3]); - } - - sum %= Math.pow(2, 32); - return sum; -} - -function makeTableRecord(tag, checkSum, offset, length) { - return new table.Record('Table Record', [ - {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, - {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, - {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, - {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} - ]); -} - -function makeSfntTable(tables) { - var sfnt = new table.Table('sfnt', [ - {name: 'version', type: 'TAG', value: 'OTTO'}, - {name: 'numTables', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); - sfnt.tables = tables; - sfnt.numTables = tables.length; - var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); - sfnt.searchRange = 16 * highestPowerOf2; - sfnt.entrySelector = log2(highestPowerOf2); - sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; - - var recordFields = []; - var tableFields = []; - - var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - - for (var i = 0; i < tables.length; i += 1) { - var t = tables[i]; - check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); - var tableLength = t.sizeOf(); - var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); - recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); - tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); - offset += tableLength; - check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - } + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - // Table records need to be sorted alphabetically. - recordFields.sort(function(r1, r2) { - if (r1.value.tag > r2.value.tag) { - return 1; - } else { - return -1; - } - }); + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } - sfnt.fields = sfnt.fields.concat(recordFields); - sfnt.fields = sfnt.fields.concat(tableFields); - return sfnt; -} + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } -// Get the metrics for a character. If the string has more than one character -// this function returns metrics for the first available character. -// You can provide optional fallback metrics if no characters are available. -function metricsForChar(font, chars, notFoundMetrics) { - for (var i = 0; i < chars.length; i += 1) { - var glyphIndex = font.charToGlyphIndex(chars[i]); - if (glyphIndex > 0) { - var glyph = font.glyphs.get(glyphIndex); - return glyph.getMetrics(); + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } } - } - return notFoundMetrics; -} + parse(code); -function average(vs) { - var sum = 0; - for (var i = 0; i < vs.length; i += 1) { - sum += vs[i]; + glyph.advanceWidth = width; + return p; + } + + function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + } + return fdSelect; } - return sum / vs.length; -} + // Parse the `CFF` table, which contains the glyph outlines in PostScript format. + function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); -// Convert the font object to a SFNT data structure. -// This structure contains all the necessary tables and metadata to create a binary OTF file. -function fontToSfntTable(font) { - var xMins = []; - var yMins = []; - var xMaxs = []; - var yMaxs = []; - var advanceWidths = []; - var leftSideBearings = []; - var rightSideBearings = []; - var firstCharIndex; - var lastCharIndex = 0; - var ulUnicodeRange1 = 0; - var ulUnicodeRange2 = 0; - var ulUnicodeRange3 = 0; - var ulUnicodeRange4 = 0; + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); + } - for (var i = 0; i < font.glyphs.length; i += 1) { - var glyph = font.glyphs.get(i); - var unicode = glyph.unicode | 0; + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; - if (isNaN(glyph.advanceWidth)) { - throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; } - if (firstCharIndex > unicode || firstCharIndex === undefined) { - // ignore .notdef char - if (unicode > 0) { - firstCharIndex = unicode; - } + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; } - if (lastCharIndex < unicode) { - lastCharIndex = unicode; + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } + + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; } - var position = os2.getUnicodeRange(unicode); - if (position < 32) { - ulUnicodeRange1 |= 1 << position; - } else if (position < 64) { - ulUnicodeRange2 |= 1 << position - 32; - } else if (position < 96) { - ulUnicodeRange3 |= 1 << position - 64; - } else if (position < 123) { - ulUnicodeRange4 |= 1 << position - 96; + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; } else { - throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); - } - // Skip non-important characters. - if (glyph.name === '.notdef') { continue; } - var metrics = glyph.getMetrics(); - xMins.push(metrics.xMin); - yMins.push(metrics.yMin); - xMaxs.push(metrics.xMax); - yMaxs.push(metrics.yMax); - leftSideBearings.push(metrics.leftSideBearing); - rightSideBearings.push(metrics.rightSideBearing); - advanceWidths.push(glyph.advanceWidth); - } - - var globals = { - xMin: Math.min.apply(null, xMins), - yMin: Math.min.apply(null, yMins), - xMax: Math.max.apply(null, xMaxs), - yMax: Math.max.apply(null, yMaxs), - advanceWidthMax: Math.max.apply(null, advanceWidths), - advanceWidthAvg: average(advanceWidths), - minLeftSideBearing: Math.min.apply(null, leftSideBearings), - maxLeftSideBearing: Math.max.apply(null, leftSideBearings), - minRightSideBearing: Math.min.apply(null, rightSideBearings) - }; - globals.ascender = font.ascender; - globals.descender = font.descender; - - var headTable = head.make({ - flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) - unitsPerEm: font.unitsPerEm, - xMin: globals.xMin, - yMin: globals.yMin, - xMax: globals.xMax, - yMax: globals.yMax, - lowestRecPPEM: 3, - createdTimestamp: font.createdTimestamp - }); + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } - var hheaTable = hhea.make({ - ascender: globals.ascender, - descender: globals.descender, - advanceWidthMax: globals.advanceWidthMax, - minLeftSideBearing: globals.minLeftSideBearing, - minRightSideBearing: globals.minRightSideBearing, - xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length - }); - - var maxpTable = maxp.make(font.glyphs.length); - - var os2Table = os2.make(Object.assign({ - xAvgCharWidth: Math.round(globals.advanceWidthAvg), - usFirstCharIndex: firstCharIndex, - usLastCharIndex: lastCharIndex, - ulUnicodeRange1: ulUnicodeRange1, - ulUnicodeRange2: ulUnicodeRange2, - ulUnicodeRange3: ulUnicodeRange3, - ulUnicodeRange4: ulUnicodeRange4, - // See http://typophile.com/node/13081 for more info on vertical metrics. - // We get metrics for typical characters (such as "x" for xHeight). - // We provide some fallback characters if characters are unavailable: their - // ordering was chosen experimentally. - sTypoAscender: globals.ascender, - sTypoDescender: globals.descender, - sTypoLineGap: 0, - usWinAscent: globals.yMax, - usWinDescent: Math.abs(globals.yMin), - ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now - sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, - sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, - usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. - usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. - }, font.tables.os2)); - - var hmtxTable = hmtx.make(font.glyphs); - var cmapTable = cmap.make(font.glyphs); - - var englishFamilyName = font.getEnglishName('fontFamily'); - var englishStyleName = font.getEnglishName('fontSubfamily'); - var englishFullName = englishFamilyName + ' ' + englishStyleName; - var postScriptName = font.getEnglishName('postScriptName'); - if (!postScriptName) { - postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; - } - - var names = {}; - for (var n in font.names) { - names[n] = font.names[n]; - } - - if (!names.uniqueID) { - names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; - } - - if (!names.postScriptName) { - names.postScriptName = {en: postScriptName}; - } - - if (!names.preferredFamily) { - names.preferredFamily = font.names.fontFamily; - } - - if (!names.preferredSubfamily) { - names.preferredSubfamily = font.names.fontSubfamily; - } - - var languageTags = []; - var nameTable = _name.make(names, languageTags); - var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); - - var postTable = post.make(); - var cffTable = cff.make(font.glyphs, { - version: font.getEnglishName('version'), - fullName: englishFullName, - familyName: englishFamilyName, - weightName: englishStyleName, - postScriptName: postScriptName, - unitsPerEm: font.unitsPerEm, - fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] - }); + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } - var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; - // The order does not matter because makeSfntTable() will sort them. - var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; - if (ltagTable) { - tables.push(ltagTable); - } - // Optional tables - if (font.tables.gsub) { - tables.push(gsub.make(font.tables.gsub)); - } - if (font.tables.cpal) { - tables.push(cpal.make(font.tables.cpal)); - } - if (font.tables.colr) { - tables.push(colr.make(font.tables.colr)); - } - if (metaTable) { - tables.push(metaTable); + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } + } } - var sfntTable = makeSfntTable(tables); + // Convert a string to a String ID (SID). + // The list of strings is modified in place. + function encodeString(s, strings) { + var sid; - // Compute the font's checkSum and store it in head.checkSumAdjustment. - var bytes = sfntTable.encode(); - var checkSum = computeCheckSum(bytes); - var tableFields = sfntTable.fields; - var checkSumAdjusted = false; - for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { - if (tableFields[i$1].name === 'head table') { - tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; - checkSumAdjusted = true; - break; + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); } + + return sid; } - if (!checkSumAdjusted) { - throw new Error('Could not find head table with checkSum to adjust.'); - } - - return sfntTable; -} - -var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; - -// The Layout object is the prototype of Substitution objects, and provides - -function searchTag(arr, tag) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid].tag; - if (val === tag) { - return imid; - } else if (val < tag) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; -} - -function binSearch(arr, value) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid]; - if (val === value) { - return imid; - } else if (val < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; -} - -// binary search in a list of ranges (coverage, class definition) -function searchRange(ranges, value) { - // jshint bitwise: false - var range; - var imin = 0; - var imax = ranges.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - range = ranges[imid]; - var start = range.start; - if (start === value) { - return range; - } else if (start < value) { - imin = imid + 1; - } else { imax = imid - 1; } + function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); } - if (imin > 0) { - range = ranges[imin - 1]; - if (value > range.end) { return 0; } - return range; + + function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; } -} -/** - * @exports opentype.Layout - * @class - */ -function Layout(font, tableName) { - this.font = font; - this.tableName = tableName; -} + // Given a dictionary's metadata, create a DICT structure. + function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } -Layout.prototype = { + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } - /** - * Binary search an object by "tag" property - * @instance - * @function searchTag - * @memberof opentype.Layout - * @param {Array} arr - * @param {string} tag - * @return {number} - */ - searchTag: searchTag, + return m; + } - /** - * Binary search in a list of numbers - * @instance - * @function binSearch - * @memberof opentype.Layout - * @param {Array} arr - * @param {number} value - * @return {number} - */ - binSearch: binSearch, + // The Top DICT houses the global font attributes. + function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; + } - /** - * Get or create the Layout table (GSUB, GPOS etc). - * @param {boolean} create - Whether to create a new one. - * @return {Object} The GSUB or GPOS table. - */ - getTable: function(create) { - var layout = this.font.tables[this.tableName]; - if (!layout && create) { - layout = this.font.tables[this.tableName] = this.createDefaultTable(); + function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; + } + + function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); } - return layout; - }, - /** - * Returns all scripts in the substitution table. - * @instance - * @return {Array} - */ - getScriptNames: function() { - var layout = this.getTable(); - if (!layout) { return []; } - return layout.scripts.map(function(script) { - return script.tag; - }); - }, + return t; + } - /** - * Returns the best bet for a script name. - * Returns 'DFLT' if it exists. - * If not, returns 'latn' if it exists. - * If neither exist, returns undefined. - */ - getDefaultScriptName: function() { - var layout = this.getTable(); - if (!layout) { return; } - var hasLatn = false; - for (var i = 0; i < layout.scripts.length; i++) { - var name = layout.scripts[i].tag; - if (name === 'DFLT') { return name; } - if (name === 'latn') { hasLatn = true; } - } - if (hasLatn) { return 'latn'; } - }, + function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); + } - /** - * Returns all LangSysRecords in the given script. - * @instance - * @param {string} [script='DFLT'] - * @param {boolean} create - forces the creation of this script table if it doesn't exist. - * @return {Object} An object with tag and script properties. - */ - getScriptTable: function(script, create) { - var layout = this.getTable(create); - if (layout) { - script = script || 'DFLT'; - var scripts = layout.scripts; - var pos = searchTag(layout.scripts, script); - if (pos >= 0) { - return scripts[pos].script; - } else if (create) { - var scr = { - tag: script, - script: { - defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, - langSysRecords: [] - } + function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; + } + + function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: Math.round(_13 * x + _23 * cmd.x1), + y1: Math.round(_13 * y + _23 * cmd.y1), + x2: Math.round(_13 * cmd.x + _23 * cmd.x1), + y2: Math.round(_13 * cmd.y + _23 * cmd.y1) }; - scripts.splice(-1 - pos, 0, scr); - return scr.script; } - } - }, - /** - * Returns a language system table - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. - * @return {Object} - */ - getLangSysTable: function(script, language, create) { - var scriptTable = this.getScriptTable(script, create); - if (scriptTable) { - if (!language || language === 'dflt' || language === 'DFLT') { - return scriptTable.defaultLangSys; - } - var pos = searchTag(scriptTable.langSysRecords, language); - if (pos >= 0) { - return scriptTable.langSysRecords[pos].langSys; - } else if (create) { - var langSysRecord = { - tag: language, - langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} - }; - scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); - return langSysRecord.langSys; + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); } - } - }, - /** - * Get a specific feature table. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm - * @param {boolean} create - forces the creation of the feature table if it doesn't exist. - * @return {Object} - */ - getFeatureTable: function(script, language, feature, create) { - var langSysTable = this.getLangSysTable(script, language, create); - if (langSysTable) { - var featureRecord; - var featIndexes = langSysTable.featureIndexes; - var allFeatures = this.font.tables[this.tableName].features; - // The FeatureIndex array of indices is in arbitrary order, - // even if allFeatures is sorted alphabetically by feature tag. - for (var i = 0; i < featIndexes.length; i++) { - featureRecord = allFeatures[featIndexes[i]]; - if (featureRecord.tag === feature) { - return featureRecord.feature; - } - } - if (create) { - var index = allFeatures.length; - // Automatic ordering of features would require to shift feature indexes in the script list. - check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); - featureRecord = { - tag: feature, - feature: { params: 0, lookupListIndexes: [] } - }; - allFeatures.push(featureRecord); - featIndexes.push(index); - return featureRecord.feature; - } + // Contours are closed automatically. } - }, - /** - * Get the lookup tables of a given type for a script/language/feature. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - 4-letter feature code - * @param {number} lookupType - 1 to 9 - * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. - * @return {Object[]} - */ - getLookupTables: function(script, language, feature, lookupType, create) { - var featureTable = this.getFeatureTable(script, language, feature, create); - var tables = []; - if (featureTable) { - var lookupTable; - var lookupListIndexes = featureTable.lookupListIndexes; - var allLookups = this.font.tables[this.tableName].lookups; - // lookupListIndexes are in no particular order, so use naive search. - for (var i = 0; i < lookupListIndexes.length; i++) { - lookupTable = allLookups[lookupListIndexes[i]]; - if (lookupTable.lookupType === lookupType) { - tables.push(lookupTable); - } - } - if (tables.length === 0 && create) { - lookupTable = { - lookupType: lookupType, - lookupFlag: 0, - subtables: [], - markFilteringSet: undefined - }; - var index = allLookups.length; - allLookups.push(lookupTable); - lookupListIndexes.push(index); - return [lookupTable]; - } - } - return tables; - }, + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; + } - /** - * Find a glyph in a class definition table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table - * @param {object} classDefTable - an OpenType Layout class definition table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getGlyphClass: function(classDefTable, glyphIndex) { - switch (classDefTable.format) { - case 1: - if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { - return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; - } - return 0; - case 2: - var range = searchRange(classDefTable.ranges, glyphIndex); - return range ? range.classId : 0; - } - }, + function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); - /** - * Find a glyph in a coverage table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table - * @param {object} coverageTable - an OpenType Layout coverage table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getCoverageIndex: function(coverageTable, glyphIndex) { - switch (coverageTable.format) { - case 1: - var index = binSearch(coverageTable.glyphs, glyphIndex); - return index >= 0 ? index : -1; - case 2: - var range = searchRange(coverageTable.ranges, glyphIndex); - return range ? range.index + glyphIndex - range.start : -1; + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); } - }, - /** - * Returns the list of glyph indexes of a coverage table. - * Format 1: the list is stored raw - * Format 2: compact list as range records. - * @instance - * @param {Object} coverageTable - * @return {Array} - */ - expandCoverage: function(coverageTable) { - if (coverageTable.format === 1) { - return coverageTable.glyphs; - } else { - var glyphs = []; - var ranges = coverageTable.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - var start = range.start; - var end = range.end; - for (var j = start; j <= end; j++) { - glyphs.push(j); - } - } - return glyphs; - } + return t; } -}; + function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; + } + + function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); -// The Position object provides utility methods to manipulate + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; -/** - * @exports opentype.Position - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ -function Position(font) { - Layout.call(this, font, 'gpos'); -} + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; + } + + var cff = { parse: parseCFFTable, make: makeCFFTable }; + + // The `head` table contains global information about the font. + + // Parse the header `head` table + function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; + } + + function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); + } + + var head = { parse: parseHeadTable, make: makeHeadTable }; + + // The `hhea` table contains information for horizontal layout. + + // Parse the horizontal header `hhea` table + function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; + } + + function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); + } + + var hhea = { parse: parseHheaTable, make: makeHheaTable }; + + // The `hmtx` table contains the horizontal metrics for all glyphs. + + function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } -Position.prototype = Layout.prototype; + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } + } -/** - * Init some data for faster and easier access later. - */ -Position.prototype.init = function() { - var script = this.getDefaultScriptName(); - this.defaultKerningTables = this.getKerningTables(script); -}; + function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; -/** - * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. - * - * @param {integer} leftIndex - left glyph index - * @param {integer} rightIndex - right glyph index - * @returns {integer} - */ -Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { - for (var i = 0; i < kerningLookups.length; i++) { - var subtables = kerningLookups[i].subtables; - for (var j = 0; j < subtables.length; j++) { - var subtable = subtables[j]; - var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); - if (covIndex < 0) { continue; } - switch (subtable.posFormat) { - case 1: - // Search Pair Adjustment Positioning Format 1 - var pairSet = subtable.pairSets[covIndex]; - for (var k = 0; k < pairSet.length; k++) { - var pair = pairSet[k]; - if (pair.secondGlyph === rightIndex) { - return pair.value1 && pair.value1.xAdvance || 0; - } - } - break; // left glyph found, not right glyph - try next subtable - case 2: - // Search Pair Adjustment Positioning Format 2 - var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); - var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); - var pair$1 = subtable.classRecords[class1][class2]; - return pair$1.value1 && pair$1.value1.xAdvance || 0; + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing + }; } } - return 0; -}; -/** - * List all kerning lookup tables. - * - * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value - * @param {string} [language='dflt'] - * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) - */ -Position.prototype.getKerningTables = function(script, language) { - if (this.font.tables.gpos) { - return this.getLookupTables(script, language, 'kern', 2); + // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. + // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. + function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } } -}; - -// The Substitution object provides utility methods to manipulate -/** - * @exports opentype.Substitution - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ -function Substitution(font) { - Layout.call(this, font, 'gsub'); -} + function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } -// Check if 2 arrays of primitives are equal. -function arraysEqual(ar1, ar2) { - var n = ar1.length; - if (n !== ar2.length) { return false; } - for (var i = 0; i < n; i++) { - if (ar1[i] !== ar2[i]) { return false; } + return t; } - return true; -} -// Find the first subtable of a lookup table in a particular format. -function getSubstFormat(lookupTable, format, defaultSubtable) { - var subtables = lookupTable.subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - if (subtable.substFormat === format) { - return subtable; - } - } - if (defaultSubtable) { - subtables.push(defaultSubtable); - return defaultSubtable; - } - return undefined; -} + var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; -Substitution.prototype = Layout.prototype; + // The `ltag` table stores IETF BCP-47 language tags. It allows supporting -/** - * Create a default GSUB table. - * @return {Object} gsub - The GSUB table. - */ -Substitution.prototype.createDefaultTable = function() { - // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. - return { - version: 1, - scripts: [{ - tag: 'DFLT', - script: { - defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, - langSysRecords: [] - } - }], - features: [], - lookups: [] - }; -}; + function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); -/** - * List all single substitutions (lookup type 1) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) - * @return {Array} substitutions - The list of substitutions. - */ -Substitution.prototype.getSingle = function(feature, script, language) { - var substitutions = []; - var lookupTables = this.getLookupTables(script, language, feature, 1); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var j = (void 0); - if (subtable.substFormat === 1) { - var delta = subtable.deltaGlyphId; - for (j = 0; j < glyphs.length; j++) { - var glyph = glyphs[j]; - substitutions.push({ sub: glyph, by: glyph + delta }); - } - } else { - var substitute = subtable.substitute; - for (j = 0; j < glyphs.length; j++) { - substitutions.push({ sub: glyphs[j], by: substitute[j] }); - } + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; } + + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; } - return substitutions; -}; -/** - * List all multiple substitutions (lookup type 2) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('ccmp', 'stch') - * @return {Array} substitutions - The list of substitutions. - */ -Substitution.prototype.getMultiple = function(feature, script, language) { - var substitutions = []; - var lookupTables = this.getLookupTables(script, language, feature, 2); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var j = (void 0); + function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); - for (j = 0; j < glyphs.length; j++) { - var glyph = glyphs[j]; - var replacements = subtable.sequences[j]; - substitutions.push({ sub: glyph, by: replacements }); + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); } - } - } - return substitutions; -}; -/** - * List all alternates (lookup type 3) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt'...) - * @return {Array} alternates - The list of alternates - */ -Substitution.prototype.getAlternates = function(feature, script, language) { - var alternates = []; - var lookupTables = this.getLookupTables(script, language, feature, 3); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var alternateSets = subtable.alternateSets; - for (var j = 0; j < glyphs.length; j++) { - alternates.push({ sub: glyphs[j], by: alternateSets[j] }); - } + tags.push(tag); } + + return tags; } - return alternates; -}; -/** - * List all ligatures (lookup type 4) for a given script, language, and feature. - * The result is an array of ligature objects like { sub: [ids], by: id } - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} ligatures - The list of ligatures. - */ -Substitution.prototype.getLigatures = function(feature, script, language) { - var ligatures = []; - var lookupTables = this.getLookupTables(script, language, feature, 4); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var ligatureSets = subtable.ligatureSets; - for (var j = 0; j < glyphs.length; j++) { - var startGlyph = glyphs[j]; - var ligSet = ligatureSets[j]; - for (var k = 0; k < ligSet.length; k++) { - var lig = ligSet[k]; - ligatures.push({ - sub: [startGlyph].concat(lig.components), - by: lig.ligGlyph - }); - } - } + var ltag = { make: makeLtagTable, parse: parseLtagTable }; + + // The `maxp` table establishes the memory requirements for the font. + + // Parse the maximum profile `maxp` table. + function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); } + + return maxp; } - return ligatures; -}; -/** - * Add or modify a single substitution (lookup type 1) - * Format 2, more flexible, is always used. - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ -Substitution.prototype.addSingle = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; - var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 - substFormat: 2, - coverage: {format: 1, glyphs: []}, - substitute: [] - }); - check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.substitute.splice(pos, 0, 0); - } - subtable.substitute[pos] = substitution.by; -}; + function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); + } -/** - * Add or modify a multiple substitution (lookup type 2) - * @param {string} feature - 4-letter feature name ('ccmp', 'stch') - * @param {Object} substitution - { sub: id, by: [id] } for format 2. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ -Substitution.prototype.addMultiple = function(feature, substitution, script, language) { - check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); - var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; - var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: {format: 1, glyphs: []}, - sequences: [] - }); - check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.sequences.splice(pos, 0, 0); - } - subtable.sequences[pos] = substitution.by; -}; + var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + + // The `name` naming table. + + // NameIDs for the name table. + var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 + ]; -/** - * Add or modify an alternate substitution (lookup type 3) - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, by: [ids] } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ -Substitution.prototype.addAlternate = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; - var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: {format: 1, glyphs: []}, - alternateSets: [] - }); - check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.alternateSets.splice(pos, 0, 0); - } - subtable.alternateSets[pos] = substitution.by; -}; + var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' + }; -/** - * Add a ligature (lookup type 4) - * Ligatures with more components must be stored ahead of those with fewer components in order to be found - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} ligature - { sub: [ids], by: id } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ -Substitution.prototype.addLigature = function(feature, ligature, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; - var subtable = lookupTable.subtables[0]; - if (!subtable) { - subtable = { // lookup type 4 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: { format: 1, glyphs: [] }, - ligatureSets: [] - }; - lookupTable.subtables[0] = subtable; - } - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = ligature.sub[0]; - var ligComponents = ligature.sub.slice(1); - var ligatureTable = { - ligGlyph: ligature.by, - components: ligComponents - }; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos >= 0) { - // ligatureSet already exists - var ligatureSet = subtable.ligatureSets[pos]; - for (var i = 0; i < ligatureSet.length; i++) { - // If ligature already exists, return. - if (arraysEqual(ligatureSet[i].components, ligComponents)) { - return; - } - } - // ligature does not exist: add it. - ligatureSet.push(ligatureTable); - } else { - // Create a new ligatureSet and add coverage for the first glyph. - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.ligatureSets.splice(pos, 0, [ligatureTable]); - } -}; + // MacOS language ID → MacOS script ID + // + // Note that the script ID is not sufficient to determine what encoding + // to use in TrueType files. For some languages, MacOS used a modification + // of a mainstream script. For example, an Icelandic name would be stored + // with smRoman in the TrueType naming table, but the actual encoding + // is a special Icelandic version of the normal Macintosh Roman encoding. + // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal + // Syllables but MacOS had run out of available script codes, so this was + // done as a (pretty radical) "modification" of Ethiopic. + // + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt + var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman + }; -/** - * List all feature data for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} substitutions - The list of substitutions. - */ -Substitution.prototype.getFeature = function(feature, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.getSingle(feature, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - return this.getSingle(feature, script, language) - .concat(this.getAlternates(feature, script, language)); - case 'dlig': - case 'liga': - case 'rlig': - return this.getLigatures(feature, script, language); - case 'ccmp': - return this.getMultiple(feature, script, language) - .concat(this.getLigatures(feature, script, language)); - case 'stch': - return this.getMultiple(feature, script, language); - } - return undefined; -}; + // While Microsoft indicates a region/country for all its language + // IDs, we omit the region code if it's equal to the "most likely + // region subtag" according to Unicode CLDR. For scripts, we omit + // the subtag if it is equal to the Suppress-Script entry in the + // IANA language subtag registry for IETF BCP 47. + // + // For example, Microsoft states that its language code 0x041A is + // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' + // and not 'hr-HR' because Croatia is the default country for Croatian, + // according to Unicode CLDR. As another example, Microsoft states + // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform + // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script + // for the Croatian language, according to IANA. + // + // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html + // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' + }; -/** - * Add a substitution to a feature for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ -Substitution.prototype.add = function(feature, sub, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.addSingle(feature, sub, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - if (typeof sub.by === 'number') { - return this.addSingle(feature, sub, script, language); - } - return this.addAlternate(feature, sub, script, language); - case 'dlig': - case 'liga': - case 'rlig': - return this.addLigature(feature, sub, script, language); - case 'ccmp': - if (sub.by instanceof Array) { - return this.addMultiple(feature, sub, script, language); - } - return this.addLigature(feature, sub, script, language); - } - return undefined; -}; + // Returns a IETF BCP 47 language code, for example 'zh-Hant' + // for 'Chinese in the traditional script'. + function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; + } -function isBrowser() { - return typeof window !== 'undefined'; -} + break; -function nodeBufferToArrayBuffer(buffer) { - var ab = new ArrayBuffer(buffer.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - view[i] = buffer[i]; - } + case 1: // Macintosh + return macLanguages[languageID]; - return ab; -} + case 3: // Windows + return windowsLanguages[languageID]; + } -function arrayBufferToNodeBuffer(ab) { - var buffer = new Buffer(ab.byteLength); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - buffer[i] = view[i]; + return undefined; } - return buffer; -} + var utf16 = 'utf-16'; + + // MacOS script ID → encoding. This table stores the default case, + // which can be overridden by macLanguageEncodings. + var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic + }; -function checkArgument(expression, message) { - if (!expression) { - throw message; - } -} + // MacOS language ID → encoding. This table stores the exceptional + // cases, which override macScriptEncodings. For writing MacOS naming + // tables, we need to emit a MacOS script ID. Therefore, we cannot + // merge macScriptEncodings into macLanguageEncodings. + // + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt + var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript + }; -// The `glyf` table describes the glyphs in TrueType outline format. + function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; -// Parse the coordinate data for a glyph. -function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { - var v; - if ((flag & shortVectorBitMask) > 0) { - // The coordinate is 1 byte long. - v = p.parseByte(); - // The `same` bit is re-used for short values to signify the sign of the value. - if ((flag & sameBitMask) === 0) { - v = -v; - } + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; - v = previousValue + v; - } else { - // The coordinate is 2 bytes long. - // If the `same` bit is set, the coordinate is the same as the previous coordinate. - if ((flag & sameBitMask) > 0) { - v = previousValue; - } else { - // Parse the coordinate as a signed 16-bit delta value. - v = previousValue + p.parseShort(); - } - } - - return v; -} - -// Parse a TrueType glyph. -function parseGlyph(glyph, data, start) { - var p = new parse.Parser(data, start); - glyph.numberOfContours = p.parseShort(); - glyph._xMin = p.parseShort(); - glyph._yMin = p.parseShort(); - glyph._xMax = p.parseShort(); - glyph._yMax = p.parseShort(); - var flags; - var flag; - - if (glyph.numberOfContours > 0) { - // This glyph is not a composite. - var endPointIndices = glyph.endPointIndices = []; - for (var i = 0; i < glyph.numberOfContours; i += 1) { - endPointIndices.push(p.parseUShort()); - } - - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { - glyph.instructions.push(p.parseByte()); - } - - var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; - flags = []; - for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { - flag = p.parseByte(); - flags.push(flag); - // If bit 3 is set, we repeat this flag n times, where n is the next byte. - if ((flag & 8) > 0) { - var repeatCount = p.parseByte(); - for (var j = 0; j < repeatCount; j += 1) { - flags.push(flag); - i$2 += 1; + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; } - } + + break; } - check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + return undefined; + } - if (endPointIndices.length > 0) { - var points = []; - var point; - // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. - if (numberOfCoordinates > 0) { - for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { - flag = flags[i$3]; - point = {}; - point.onCurve = !!(flag & 1); - point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; - points.push(point); + // Parse the naming `name` table. + // FIXME: Format 1 additional fields are not supported yet. + // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. + function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); } - var px = 0; - for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { - flag = flags[i$4]; - point = points[i$4]; - point.x = parseGlyphCoordinate(p, flag, px, 2, 16); - px = point.x; - } + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } - var py = 0; - for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { - flag = flags[i$5]; - point = points[i$5]; - point.y = parseGlyphCoordinate(p, flag, py, 4, 32); - py = point.y; + translations[language] = text; } } + } - glyph.points = points; - } else { - glyph.points = []; + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); } - } else if (glyph.numberOfContours === 0) { - glyph.points = []; - } else { - glyph.isComposite = true; - glyph.points = []; - glyph.components = []; - var moreComponents = true; - while (moreComponents) { - flags = p.parseUShort(); - var component = { - glyphIndex: p.parseUShort(), - xScale: 1, - scale01: 0, - scale10: 0, - yScale: 1, - dx: 0, - dy: 0 - }; - if ((flags & 1) > 0) { - // The arguments are words - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseShort(); - component.dy = p.parseShort(); - } else { - // values are matched points - component.matchedPoints = [p.parseUShort(), p.parseUShort()]; - } - } else { - // The arguments are bytes - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseChar(); - component.dy = p.parseChar(); - } else { - // values are matched points - component.matchedPoints = [p.parseByte(), p.parseByte()]; - } - } + return name; + } - if ((flags & 8) > 0) { - // We have a scale - component.xScale = component.yScale = p.parseF2Dot14(); - } else if ((flags & 64) > 0) { - // We have an X / Y scale - component.xScale = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } else if ((flags & 128) > 0) { - // We have a 2x2 transformation - component.xScale = p.parseF2Dot14(); - component.scale01 = p.parseF2Dot14(); - component.scale10 = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } - - glyph.components.push(component); - moreComponents = !!(flags & 32); - } - if (flags & 0x100) { - // We have instructions - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { - glyph.instructions.push(p.parseByte()); - } + // {23: 'foo'} → {'foo': 23} + // ['bar', 'baz'] → {'bar': 0, 'baz': 1} + function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); } + + return result; } -} -// Transform an array of points and return a new array. -function transformPoints(points, transform) { - var newPoints = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - var newPt = { - x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, - y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, - onCurve: pt.onCurve, - lastPointOfContour: pt.lastPointOfContour - }; - newPoints.push(newPt); + function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); } - return newPoints; -} + // Finds the position of needle in haystack, or -1 if not there. + // Like String.indexOf(), but for arrays. + function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; + } + } -function getContours(points) { - var contours = []; - var currentContour = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; + return pos; + } } + + return -1; } - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; -} + function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } -// Convert the TrueType glyph outline to a Path. -function getPath(points) { - var p = new Path(); - if (!points) { - return p; + } + + return offset; } - var contours = getContours(points); + function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; - for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { - var contour = contours[contourIndex]; + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } - var prev = null; - var curr = contour[contour.length - 1]; - var next = contour[0]; + nameID = parseInt(id); - if (curr.onCurve) { - p.moveTo(curr.x, curr.y); - } else { - if (next.onCurve) { - p.moveTo(next.x, next.y); - } else { - // If both first and last points are off-curve, start at their middle. - var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; - p.moveTo(start.x, start.y); + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); } - } - for (var i = 0; i < contour.length; ++i) { - prev = curr; - curr = next; - next = contour[(i + 1) % contour.length]; - - if (curr.onCurve) { - // This is a straight line. - p.lineTo(curr.x, curr.y); - } else { - var prev2 = prev; - var next2 = next; + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); + } + + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); + + var nameRecords = []; + var stringPool = []; + + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; + + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); + } - if (!prev.onCurve) { - prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); } - if (!next.onCurve) { - next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; - } + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); - p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } } } - p.closePath(); - } - return p; -} + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); -function buildPath(glyphs, glyph) { - if (glyph.isComposite) { - for (var j = 0; j < glyph.components.length; j += 1) { - var component = glyph.components[j]; - var componentGlyph = glyphs.get(component.glyphIndex); - // Force the ttfGlyphLoader to parse the glyph. - componentGlyph.getPath(); - if (componentGlyph.points) { - var transformedPoints = (void 0); - if (component.matchedPoints === undefined) { - // component positioned by offset - transformedPoints = transformPoints(componentGlyph.points, component); - } else { - // component positioned by matched points - if ((component.matchedPoints[0] > glyph.points.length - 1) || - (component.matchedPoints[1] > componentGlyph.points.length - 1)) { - throw Error('Matched points out of range in ' + glyph.name); - } - var firstPt = glyph.points[component.matchedPoints[0]]; - var secondPt = componentGlyph.points[component.matchedPoints[1]]; - var transform = { - xScale: component.xScale, scale01: component.scale01, - scale10: component.scale10, yScale: component.yScale, - dx: 0, dy: 0 - }; - secondPt = transformPoints([secondPt], transform)[0]; - transform.dx = firstPt.x - secondPt.x; - transform.dy = firstPt.y - secondPt.y; - transformedPoints = transformPoints(componentGlyph.points, transform); - } - glyph.points = glyph.points.concat(transformedPoints); + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; + } + + var _name = { parse: parseNameTable, make: makeNameTable }; + + // The `OS/2` table contains metrics required in OpenType fonts. + + var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles + ]; + + function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; } } - } - return getPath(glyph.points); -} + return -1; + } + + // Parse the OS/2 and Windows metrics `OS/2` table + function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; + } + + function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); + } + + var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + + // The `post` table stores additional PostScript information, such as glyph names. + + // Parse the PostScript `post` table + function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } -function parseGlyfTableAll(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } - // The last element of the loca table is invalid. - for (var i = 0; i < loca.length - 1; i += 1) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } + + break; } + return post; } - return glyphs; -} + function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); + } -function parseGlyfTableOnLowMemory(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); + var post = { parse: parsePostTable, make: makePostTable }; - font._push = function(i) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - }; - - return glyphs; -} - -// Parse all the glyphs according to the offsets from the `loca` table. -function parseGlyfTable(data, start, loca, font, opt) { - if (opt.lowMemory) - { return parseGlyfTableOnLowMemory(data, start, loca, font); } - else - { return parseGlyfTableAll(data, start, loca, font); } -} - -var glyf = { getPath: getPath, parse: parseGlyfTable}; - -/* A TrueType font hinting interpreter. -* -* (c) 2017 Axel Kittenberger -* -* This interpreter has been implemented according to this documentation: -* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html -* -* According to the documentation F24DOT6 values are used for pixels. -* That means calculation is 1/64 pixel accurate and uses integer operations. -* However, Javascript has floating point operations by default and only -* those are available. One could make a case to simulate the 1/64 accuracy -* exactly by truncating after every division operation -* (for example with << 0) to get pixel exactly results as other TrueType -* implementations. It may make sense since some fonts are pixel optimized -* by hand using DELTAP instructions. The current implementation doesn't -* and rather uses full floating point precision. -* -* xScale, yScale and rotation is currently ignored. -* -* A few non-trivial instructions are missing as I didn't encounter yet -* a font that used them to test a possible implementation. -* -* Some fonts seem to use undocumented features regarding the twilight zone. -* Only some of them are implemented as they were encountered. -* -* The exports.DEBUG statements are removed on the minified distribution file. -*/ - -var instructionTable; -var exec; -var execGlyph; -var execComponent; - -/* -* Creates a hinting object. -* -* There ought to be exactly one -* for each truetype font that is used for hinting. -*/ -function Hinting(font) { - // the font this hinting object is for - this.font = font; - - this.getCommands = function (hPoints) { - return glyf.getPath(hPoints).commands; - }; - - // cached states - this._fpgmState = - this._prepState = - undefined; - - // errorState - // 0 ... all okay - // 1 ... had an error in a glyf, - // continue working but stop spamming - // the console - // 2 ... error at prep, stop hinting at this ppem - // 3 ... error at fpeg, stop hinting for this font at all - this._errorState = 0; -} - -/* -* Not rounding. -*/ -function roundOff(v) { - return v; -} - -/* -* Rounding to grid. -*/ -function roundToGrid(v) { - //Rounding in TT is supposed to "symmetrical around zero" - return Math.sign(v) * Math.round(Math.abs(v)); -} - -/* -* Rounding to double grid. -*/ -function roundToDoubleGrid(v) { - return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; -} - -/* -* Rounding to half grid. -*/ -function roundToHalfGrid(v) { - return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); -} - -/* -* Rounding to up to grid. -*/ -function roundUpToGrid(v) { - return Math.sign(v) * Math.ceil(Math.abs(v)); -} - -/* -* Rounding to down to grid. -*/ -function roundDownToGrid(v) { - return Math.sign(v) * Math.floor(Math.abs(v)); -} - -/* -* Super rounding. -*/ -var roundSuper = function (v) { - var period = this.srPeriod; - var phase = this.srPhase; - var threshold = this.srThreshold; - var sign = 1; - - if (v < 0) { - v = -v; - sign = -1; - } - - v += threshold - phase; - - v = Math.trunc(v / period) * period; - - v += phase; - - // according to http://xgridfit.sourceforge.net/round.html - if (v < 0) { return phase * sign; } - - return v * sign; -}; + // The `GSUB` table contains ligatures, among other things. -/* -* Unit vector of x-axis. -*/ -var xUnitVector = { - x: 1, + var subtableParsers = new Array(9); // subtableParsers[0] is unused - y: 0, + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS + subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); + }; - axis: 'x', + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS + subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; + }; - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); - }, + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS + subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; + }; - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS + subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; + }; - if (!pv || pv === this) { - do1 = p.xo - rp1.xo; - do2 = p.xo - rp2.xo; - dm1 = rp1.x - rp1.xo; - dm2 = rp2.x - rp2.xo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; + var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort + }; - if (dt === 0) { - p.x = p.xo + (dm1 + dm2) / 2; - return; - } + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF + subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); - p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; - return; + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); + }; - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC + subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); + }; - xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES + subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; + }; - // Slope of line normal to this - normalSlope: Number.NEGATIVE_INFINITY, + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS + subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; + }; - // Sets the point 'p' relative to point 'rp' - // by the distance 'd'. - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.x = (org ? rp.xo : rp.x) + d; - return; + // https://www.microsoft.com/typography/OTSPEC/gsub.htm + function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; } - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - p.x = rpdx + (p.y - rpdy) / pv.normalSlope; - }, + } - // Slope of vector line. - slope: 0, + // GSUB Writing ////////////////////////////////////////////// + var subtableMakers = new Array(9); - // Touches the point p. - touch: function (p) { - p.xTouched = true; - }, + subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); + } + }; - // Tests if a point p is touched. - touched: function (p) { - return p.xTouched; - }, + subtableMakers[2] = function makeLookup2(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { + return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); + }))); + }; - // Untouches the point p. - untouch: function (p) { - p.xTouched = false; - } -}; + subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); + }; -/* -* Unit vector of y-axis. -*/ -var yUnitVector = { - x: 0, + subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); + }; - y: 1, + subtableMakers[6] = function makeLookup6(subtable) { + if (subtable.substFormat === 1) { + var returnTable = new table.Table('chainContextTable', [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { + return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { + var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) + .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) + .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) + .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); + + chainRule.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + return new table.Table('chainRuleTable', tableData); + })); + }))); + return returnTable; + } else if (subtable.substFormat === 2) { + check.assert(false, 'lookup type 6 format 2 is not yet supported.'); + } else if (subtable.substFormat === 3) { + var tableData = [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; + + tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); + subtable.backtrackCoverage.forEach(function (coverage, i) { + tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); + subtable.inputCoverage.forEach(function (coverage, i) { + tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); + subtable.lookaheadCoverage.forEach(function (coverage, i) { + tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + + tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); + subtable.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + + var returnTable$1 = new table.Table('chainContextTable', tableData); + + return returnTable$1; + } + + check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); + }; - axis: 'y', + function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); + } - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); - }, + var gsub = { parse: parseGsubTable, make: makeGsubTable }; - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; + // The `GPOS` table contains kerning pairs, among other things. - if (!pv || pv === this) { - do1 = p.yo - rp1.yo; - do2 = p.yo - rp2.yo; - dm1 = rp1.y - rp1.yo; - dm2 = rp2.y - rp2.yo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; + // Parse the metadata `meta` table. + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html + function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); - if (dt === 0) { - p.y = p.yo + (dm1 + dm2) / 2; - return; - } + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); - p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; - return; + tags[tag] = text; } + return tags; + } - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } + function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; - yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); - // Slope of line normal to this. - normalSlope: 0, + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; - // Sets the point 'p' relative to point 'rp' - // by the distance 'd' - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.y = (org ? rp.yo : rp.y) + d; - return; + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); } - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - p.y = rpdy + pv.normalSlope * (p.x - rpdx); - }, + return result; + } - // Slope of vector line. - slope: Number.POSITIVE_INFINITY, + var meta = { parse: parseMetaTable, make: makeMetaTable }; - // Touches the point p. - touch: function (p) { - p.yTouched = true; - }, + // The `COLR` table adds support for multi-colored glyphs - // Tests if a point p is touched. - touched: function (p) { - return p.yTouched; - }, + function parseColrTable(data, start) { + var p = new Parser(data, start); + var version = p.parseUShort(); + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var numBaseGlyphRecords = p.parseUShort(); + var baseGlyphRecordsOffset = p.parseOffset32(); + var layerRecordsOffset = p.parseOffset32(); + var numLayerRecords = p.parseUShort(); + p.relativeOffset = baseGlyphRecordsOffset; + var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, { + glyphID: Parser.uShort, + firstLayerIndex: Parser.uShort, + numLayers: Parser.uShort, + }); + p.relativeOffset = layerRecordsOffset; + var layerRecords = p.parseRecordList(numLayerRecords, { + glyphID: Parser.uShort, + paletteIndex: Parser.uShort + }); - // Untouches the point p. - untouch: function (p) { - p.yTouched = false; + return { + version: version, + baseGlyphRecords: baseGlyphRecords, + layerRecords: layerRecords, + }; } -}; -Object.freeze(xUnitVector); -Object.freeze(yUnitVector); - -/* -* Creates a unit vector that is not x- or y-axis. -*/ -function UnitVector(x, y) { - this.x = x; - this.y = y; - this.axis = undefined; - this.slope = y / x; - this.normalSlope = -x / y; - Object.freeze(this); -} - -/* -* Gets the projected distance between two points. -* o1/o2 ... if true, respective original position is used. -*/ -UnitVector.prototype.distance = function(p1, p2, o1, o2) { - return ( - this.x * xUnitVector.distance(p1, p2, o1, o2) + - this.y * yUnitVector.distance(p1, p2, o1, o2) - ); -}; + function makeColrTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0x0000; + var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = []; + var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = []; -/* -* Moves point p so the moved position has the same relative -* position to the moved positions of rp1 and rp2 than the -* original positions had. -* -* See APPENDIX on INTERPOLATE at the bottom of this file. -*/ -UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { - var dm1; - var dm2; - var do1; - var do2; - var doa1; - var doa2; - var dt; - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); -}; + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var baseGlyphRecordsOffset = 14; + var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6); + return new table.Table('COLR', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length }, + { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset }, + { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset }, + { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [ + { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID }, + { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex }, + { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(), + layerRecords.map(function (layer, i) { return [ + { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID }, + { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() )); + } -/* -* Sets the point 'p' relative to point 'rp' -* by the distance 'd' -* -* See APPENDIX on SETRELATIVE at the bottom of this file. -* -* p ... point to set -* rp ... reference point -* d ... distance on projection vector -* pv ... projection vector (undefined = this) -* org ... if true, uses the original position of rp as reference. -*/ -UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { - pv = pv || this; - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - var pvns = pv.normalSlope; - var fvs = this.slope; - - var px = p.x; - var py = p.y; - - p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); - p.y = fvs * (p.x - px) + py; -}; + var colr = { parse: parseColrTable, make: makeColrTable }; -/* -* Touches the point p. -*/ -UnitVector.prototype.touch = function(p) { - p.xTouched = true; - p.yTouched = true; -}; + // The `CPAL` define a contiguous list of colors (colorRecords) -/* -* Returns a unit vector with x/y coordinates. -*/ -function getUnitVector(x, y) { - var d = Math.sqrt(x * x + y * y); - - x /= d; - y /= d; - - if (x === 1 && y === 0) { return xUnitVector; } - else if (x === 0 && y === 1) { return yUnitVector; } - else { return new UnitVector(x, y); } -} - -/* -* Creates a point in the hinting engine. -*/ -function HPoint( - x, - y, - lastPointOfContour, - onCurve -) { - this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value - this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value - - this.lastPointOfContour = lastPointOfContour; - this.onCurve = onCurve; - this.prevPointOnContour = undefined; - this.nextPointOnContour = undefined; - this.xTouched = false; - this.yTouched = false; - - Object.preventExtensions(this); -} - -/* -* Returns the next touched point on the contour. -* -* v ... unit vector to test touch axis. -*/ -HPoint.prototype.nextTouched = function(v) { - var p = this.nextPointOnContour; - - while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } - - return p; -}; + // Parse the header `head` table + function parseCpalTable(data, start) { + var p = new Parser(data, start); + var version = p.parseShort(); + var numPaletteEntries = p.parseShort(); + var numPalettes = p.parseShort(); + var numColorRecords = p.parseShort(); + var colorRecordsArrayOffset = p.parseOffset32(); + var colorRecordIndices = p.parseUShortList(numPalettes); + p.relativeOffset = colorRecordsArrayOffset; + var colorRecords = p.parseULongList(numColorRecords); + return { + version: version, + numPaletteEntries: numPaletteEntries, + colorRecords: colorRecords, + colorRecordIndices: colorRecordIndices, + }; + } -/* -* Returns the previous touched point on the contour -* -* v ... unit vector to test touch axis. -*/ -HPoint.prototype.prevTouched = function(v) { - var p = this.prevPointOnContour; + function makeCpalTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0; + var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0; + var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = []; + var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0]; - while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } + check.argument(version === 0, 'Only CPALv0 are supported.'); + check.argument(colorRecords.length, 'No colorRecords given.'); + check.argument(colorRecordIndices.length, 'No colorRecordIndices given.'); + check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices'); + return new table.Table('CPAL', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length }, + { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length }, + { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length }, + { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }), + colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) )); + } - return p; -}; + var cpal = { parse: parseCpalTable, make: makeCpalTable }; -/* -* The zero point. -*/ -var HPZero = Object.freeze(new HPoint(0, 0)); - -/* -* The default state of the interpreter. -* -* Note: Freezing the defaultState and then deriving from it -* makes the V8 Javascript engine going awkward, -* so this is avoided, albeit the defaultState shouldn't -* ever change. -*/ -var defaultState = { - cvCutIn: 17 / 16, // control value cut in - deltaBase: 9, - deltaShift: 0.125, - loop: 1, // loops some instructions - minDis: 1, // minimum distance - autoFlip: true -}; + // The `sfnt` wrapper provides organization for the tables in the font. -/* -* The current state of the interpreter. -* -* env ... 'fpgm' or 'prep' or 'glyf' -* prog ... the program -*/ -function State(env, prog) { - this.env = env; - this.stack = []; - this.prog = prog; - - switch (env) { - case 'glyf' : - this.zp0 = this.zp1 = this.zp2 = 1; - this.rp0 = this.rp1 = this.rp2 = 0; - /* fall through */ - case 'prep' : - this.fv = this.pv = this.dpv = xUnitVector; - this.round = roundToGrid; - } -} - -/* -* Executes a glyph program. -* -* This does the hinting for each glyph. -* -* Returns an array of moved points. -* -* glyph: the glyph to hint -* ppem: the size the glyph is rendered for -*/ -Hinting.prototype.exec = function(glyph, ppem) { - if (typeof ppem !== 'number') { - throw new Error('Point size is not a number!'); - } - - // Received a fatal error, don't do any hinting anymore. - if (this._errorState > 2) { return; } - - var font = this.font; - var prepState = this._prepState; - - if (!prepState || prepState.ppem !== ppem) { - var fpgmState = this._fpgmState; - - if (!fpgmState) { - // Executes the fpgm state. - // This is used by fonts to define functions. - State.prototype = defaultState; - - fpgmState = - this._fpgmState = - new State('fpgm', font.tables.fpgm); - - fpgmState.funcs = [ ]; - fpgmState.font = font; + function log2(v) { + return Math.log(v) / Math.log(2) | 0; + } - if (exports.DEBUG) { - console.log('---EXEC FPGM---'); - fpgmState.step = -1; - } + function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } - try { - exec(fpgmState); - } catch (e) { - console.log('Hinting error in FPGM:' + e); - this._errorState = 3; - return; - } + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); } - // Executes the prep program for this ppem setting. - // This is used by fonts to set cvt values - // depending on to be rendered font size. + sum %= Math.pow(2, 32); + return sum; + } - State.prototype = fpgmState; - prepState = - this._prepState = - new State('prep', font.tables.prep); + function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); + } - prepState.ppem = ppem; + function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; - // Creates a copy of the cvt table - // and scales it to the current ppem setting. - var oCvt = font.tables.cvt; - if (oCvt) { - var cvt = prepState.cvt = new Array(oCvt.length); - var scale = ppem / font.unitsPerEm; - for (var c = 0; c < oCvt.length; c++) { - cvt[c] = oCvt[c] * scale; - } - } else { - prepState.cvt = []; - } + var recordFields = []; + var tableFields = []; - if (exports.DEBUG) { - console.log('---EXEC PREP---'); - prepState.step = -1; + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); } - try { - exec(prepState); - } catch (e) { - if (this._errorState < 2) { - console.log('Hinting error in PREP:' + e); + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); } - this._errorState = 2; } - } - if (this._errorState > 1) { return; } + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); - try { - return execGlyph(glyph, prepState); - } catch (e) { - if (this._errorState < 1) { - console.log('Hinting error:' + e); - console.log('Note: further hinting errors are silenced'); - } - this._errorState = 1; - return undefined; + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; } -}; - -/* -* Executes the hinting program for a glyph. -*/ -execGlyph = function(glyph, prepState) { - // original point positions - var xScale = prepState.ppem / prepState.font.unitsPerEm; - var yScale = xScale; - var components = glyph.components; - var contours; - var gZone; - var state; - - State.prototype = prepState; - if (!components) { - state = new State('glyf', glyph.instructions); - if (exports.DEBUG) { - console.log('---EXEC GLYPH---'); - state.step = -1; - } - execComponent(glyph, state, xScale, yScale); - gZone = state.gZone; - } else { - var font = prepState.font; - gZone = []; - contours = []; - for (var i = 0; i < components.length; i++) { - var c = components[i]; - var cg = font.glyphs.get(c.glyphIndex); - state = new State('glyf', cg.instructions); - - if (exports.DEBUG) { - console.log('---EXEC COMP ' + i + '---'); - state.step = -1; + // Get the metrics for a character. If the string has more than one character + // this function returns metrics for the first available character. + // You can provide optional fallback metrics if no characters are available. + function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); } + } - execComponent(cg, state, xScale, yScale); - // appends the computed points to the result array - // post processes the component points - var dx = Math.round(c.dx * xScale); - var dy = Math.round(c.dy * yScale); - var gz = state.gZone; - var cc = state.contours; - for (var pi = 0; pi < gz.length; pi++) { - var p = gz[pi]; - p.xTouched = p.yTouched = false; - p.xo = p.x = p.x + dx; - p.yo = p.y = p.y + dy; - } + return notFoundMetrics; + } - var gLen = gZone.length; - gZone.push.apply(gZone, gz); - for (var j = 0; j < cc.length; j++) { - contours.push(cc[j] + gLen); - } + function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; } - if (glyph.instructions && !state.inhibitGridFit) { - // the composite has instructions on its own - state = new State('glyf', glyph.instructions); - - state.gZone = state.z0 = state.z1 = state.z2 = gZone; + return sum / vs.length; + } - state.contours = contours; + // Convert the font object to a SFNT data structure. + // This structure contains all the necessary tables and metadata to create a binary OTF file. + function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; - // note: HPZero cannot be used here, since - // the point might be modified - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; - if (exports.DEBUG) { - console.log('---EXEC COMPOSITE---'); - state.step = -1; + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); } - exec(state); + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; + } + } - gZone.length -= 2; - } - } + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } - return gZone; -}; + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); -/* -* Executes the hinting program for a component of a multi-component glyph -* or of the glyph itself for a non-component glyph. -*/ -execComponent = function(glyph, state, xScale, yScale) -{ - var points = glyph.points || []; - var pLen = points.length; - var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; - var contours = state.contours = []; - - // Scales the original points and - // makes copies for the hinted points. - var cp; // current point - for (var i = 0; i < pLen; i++) { - cp = points[i]; - - gZone[i] = new HPoint( - cp.x * xScale, - cp.y * yScale, - cp.lastPointOfContour, - cp.onCurve - ); - } + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); - // Chain links the contours. - var sp; // start point - var np; // next point + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); - for (var i$1 = 0; i$1 < pLen; i$1++) { - cp = gZone[i$1]; + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; - if (!sp) { - sp = cp; - contours.push(i$1); + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); } - - if (cp.lastPointOfContour) { - cp.nextPointOnContour = sp; - sp.prevPointOnContour = cp; - sp = undefined; - } else { - np = gZone[i$1 + 1]; - cp.nextPointOnContour = np; - np.prevPointOnContour = cp; + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); } - } - - if (state.inhibitGridFit) { return; } - - if (exports.DEBUG) { - console.log('PROCESSING GLYPH', state.stack); - for (var i$2 = 0; i$2 < pLen; i$2++) { - console.log(i$2, gZone[i$2].x, gZone[i$2].y); + if (font.tables.cpal) { + tables.push(cpal.make(font.tables.cpal)); + } + if (font.tables.colr) { + tables.push(colr.make(font.tables.colr)); + } + if (metaTable) { + tables.push(metaTable); } - } - - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); - exec(state); + var sfntTable = makeSfntTable(tables); - // Removes the extra points. - gZone.length -= 2; + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } - if (exports.DEBUG) { - console.log('FINISHED GLYPH', state.stack); - for (var i$3 = 0; i$3 < pLen; i$3++) { - console.log(i$3, gZone[i$3].x, gZone[i$3].y); + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; + } + + var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + + // The Layout object is the prototype of Substitution objects, and provides + + function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; + } + + function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; + } + + // binary search in a list of ranges (coverage, class definition) + function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; } } -}; -/* -* Executes the program loaded in state. -*/ -exec = function(state) { - var prog = state.prog; + /** + * @exports opentype.Layout + * @class + */ + function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; + } - if (!prog) { return; } + Layout.prototype = { - var pLen = prog.length; - var ins; + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, - for (state.ip = 0; state.ip < pLen; state.ip++) { - if (exports.DEBUG) { state.step++; } - ins = instructionTable[prog[state.ip]]; + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, - if (!ins) { - throw new Error( - 'unknown instruction: 0x' + - Number(prog[state.ip]).toString(16) - ); - } + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, - ins(state); + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, - // very extensive debugging for each step - /* - if (exports.DEBUG) { - var da; - if (state.gZone) { - da = []; - for (let i = 0; i < state.gZone.length; i++) - { - da.push(i + ' ' + - state.gZone[i].x * 64 + ' ' + - state.gZone[i].y * 64 + ' ' + - (state.gZone[i].xTouched ? 'x' : '') + - (state.gZone[i].yTouched ? 'y' : '') - ); + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; } - console.log('GZ', da); } + }, - if (state.tZone) { - da = []; - for (let i = 0; i < state.tZone.length; i++) { - da.push(i + ' ' + - state.tZone[i].x * 64 + ' ' + - state.tZone[i].y * 64 + ' ' + - (state.tZone[i].xTouched ? 'x' : '') + - (state.tZone[i].yTouched ? 'y' : '') - ); + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; } - console.log('TZ', da); } + }, - if (state.stack.length > 10) { - console.log( - state.stack.length, - '...', state.stack.slice(state.stack.length - 10) - ); - } else { - console.log(state.stack.length, state.stack); + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } } - } - */ - } -}; + }, -/* -* Initializes the twilight zone. -* -* This is only done if a SZPx instruction -* refers to the twilight zone. -*/ -function initTZone(state) -{ - var tZone = state.tZone = new Array(state.gZone.length); - - // no idea if this is actually correct... - for (var i = 0; i < tZone.length; i++) - { - tZone[i] = new HPoint(0, 0); - } -} + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, -/* -* Skips the instruction pointer ahead over an IF/ELSE block. -* handleElse .. if true breaks on matching ELSE -*/ -function skip(state, handleElse) -{ - var prog = state.prog; - var ip = state.ip; - var nesting = 1; - var ins; + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, - do { - ins = prog[++ip]; - if (ins === 0x58) // IF - { nesting++; } - else if (ins === 0x59) // EIF - { nesting--; } - else if (ins === 0x40) // NPUSHB - { ip += prog[ip + 1] + 1; } - else if (ins === 0x41) // NPUSHW - { ip += 2 * prog[ip + 1] + 1; } - else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB - { ip += ins - 0xB0 + 1; } - else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW - { ip += (ins - 0xB8 + 1) * 2; } - else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE - { break; } - } while (nesting > 0); - - state.ip = ip; -} - -/*----------------------------------------------------------* -* And then a lot of instructions... * -*----------------------------------------------------------*/ - -// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis -// 0x00-0x01 -function SVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } - - state.fv = state.pv = state.dpv = v; -} - -// SPVTCA[a] Set Projection Vector to Coordinate Axis -// 0x02-0x03 -function SPVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } - - state.pv = state.dpv = v; -} - -// SFVTCA[a] Set Freedom Vector to Coordinate Axis -// 0x04-0x05 -function SFVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } - - state.fv = v; -} - -// SPVTL[a] Set Projection Vector To Line -// 0x06-0x07 -function SPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.pv = state.dpv = getUnitVector(dx, dy); -} - -// SFVTL[a] Set Freedom Vector To Line -// 0x08-0x09 -function SFVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.fv = getUnitVector(dx, dy); -} - -// SPVFS[] Set Projection Vector From Stack -// 0x0A -function SPVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - - state.pv = state.dpv = getUnitVector(x, y); -} - -// SFVFS[] Set Freedom Vector From Stack -// 0x0B -function SFVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - - state.fv = getUnitVector(x, y); -} - -// GPV[] Get Projection Vector -// 0x0C -function GPV(state) { - var stack = state.stack; - var pv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } - - stack.push(pv.x * 0x4000); - stack.push(pv.y * 0x4000); -} - -// GFV[] Get Freedom Vector -// 0x0C -function GFV(state) { - var stack = state.stack; - var fv = state.fv; - - if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } - - stack.push(fv.x * 0x4000); - stack.push(fv.y * 0x4000); -} - -// SFVTPV[] Set Freedom Vector To Projection Vector -// 0x0E -function SFVTPV(state) { - state.fv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } -} - -// ISECT[] moves point p to the InterSECTion of two lines -// 0x0F -function ISECT(state) -{ - var stack = state.stack; - var pa0i = stack.pop(); - var pa1i = stack.pop(); - var pb0i = stack.pop(); - var pb1i = stack.pop(); - var pi = stack.pop(); - var z0 = state.z0; - var z1 = state.z1; - var pa0 = z0[pa0i]; - var pa1 = z0[pa1i]; - var pb0 = z1[pb0i]; - var pb1 = z1[pb1i]; - var p = state.z2[pi]; - - if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } - - // math from - // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line - - var x1 = pa0.x; - var y1 = pa0.y; - var x2 = pa1.x; - var y2 = pa1.y; - var x3 = pb0.x; - var y3 = pb0.y; - var x4 = pb1.x; - var y4 = pb1.y; - - var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - var f1 = x1 * y2 - y1 * x2; - var f2 = x3 * y4 - y3 * x4; - - p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; - p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; -} - -// SRP0[] Set Reference Point 0 -// 0x10 -function SRP0(state) { - state.rp0 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } -} - -// SRP1[] Set Reference Point 1 -// 0x11 -function SRP1(state) { - state.rp1 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } -} - -// SRP1[] Set Reference Point 2 -// 0x12 -function SRP2(state) { - state.rp2 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } -} - -// SZP0[] Set Zone Pointer 0 -// 0x13 -function SZP0(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } - - state.zp0 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.tZone; - break; - case 1 : - state.z0 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } -} + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, -// SZP1[] Set Zone Pointer 1 -// 0x14 -function SZP1(state) { - var n = state.stack.pop(); + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } - if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + }; - state.zp1 = n; + // The Position object provides utility methods to manipulate - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z1 = state.tZone; - break; - case 1 : - state.z1 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); + /** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ + function Position(font) { + Layout.call(this, font, 'gpos'); } -} -// SZP2[] Set Zone Pointer 2 -// 0x15 -function SZP2(state) { - var n = state.stack.pop(); + Position.prototype = Layout.prototype; - if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } - - state.zp2 = n; + /** + * Init some data for faster and easier access later. + */ + Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); + }; - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z2 = state.tZone; - break; - case 1 : - state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } -} + /** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ + Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; + }; -// SZPS[] Set Zone PointerS -// 0x16 -function SZPS(state) { - var n = state.stack.pop(); + /** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ + Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } + }; - if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + // The Substitution object provides utility methods to manipulate - state.zp0 = state.zp1 = state.zp2 = n; + /** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ + function Substitution(font) { + Layout.call(this, font, 'gsub'); + } - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.z1 = state.z2 = state.tZone; - break; - case 1 : - state.z0 = state.z1 = state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); + // Check if 2 arrays of primitives are equal. + function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; } -} -// SLOOP[] Set LOOP variable -// 0x17 -function SLOOP(state) { - state.loop = state.stack.pop(); + // Find the first subtable of a lookup table in a particular format. + function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; + } - if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } -} + Substitution.prototype = Layout.prototype; -// RTG[] Round To Grid -// 0x18 -function RTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } + /** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ + Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; + }; - state.round = roundToGrid; -} + /** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; + }; -// RTHG[] Round To Half Grid -// 0x19 -function RTHG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } + /** + * List all multiple substitutions (lookup type 2) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('ccmp', 'stch') + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getMultiple = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 2); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); - state.round = roundToHalfGrid; -} + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + var replacements = subtable.sequences[j]; + substitutions.push({ sub: glyph, by: replacements }); + } + } + } + return substitutions; + }; -// SMD[] Set Minimum Distance -// 0x1A -function SMD(state) { - var d = state.stack.pop(); + /** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ + Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; + }; - if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } + /** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ + Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; + }; - state.minDis = d / 0x40; -} + /** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; + }; -// ELSE[] ELSE clause -// 0x1B -function ELSE(state) { - // This instruction has been reached by executing a then branch - // so it just skips ahead until matching EIF. - // - // In case the IF was negative the IF[] instruction already - // skipped forward over the ELSE[] + /** + * Add or modify a multiple substitution (lookup type 2) + * @param {string} feature - 4-letter feature name ('ccmp', 'stch') + * @param {Object} substitution - { sub: id, by: [id] } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addMultiple = function(feature, substitution, script, language) { + check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); + var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + sequences: [] + }); + check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.sequences.splice(pos, 0, 0); + } + subtable.sequences[pos] = substitution.by; + }; - if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } + /** + * Add or modify an alternate substitution (lookup type 3) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; + }; - skip(state, false); -} + /** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } + }; -// JMPR[] JuMP Relative -// 0x1C -function JMPR(state) { - var o = state.stack.pop(); + /** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': + return this.getLigatures(feature, script, language); + case 'ccmp': + return this.getMultiple(feature, script, language) + .concat(this.getLigatures(feature, script, language)); + case 'stch': + return this.getMultiple(feature, script, language); + } + return undefined; + }; - if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } - - // A jump by 1 would do nothing. - state.ip += o - 1; -} - -// SCVTCI[] Set Control Value Table Cut-In -// 0x1D -function SCVTCI(state) { - var n = state.stack.pop(); + /** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + case 'ccmp': + if (sub.by instanceof Array) { + return this.addMultiple(feature, sub, script, language); + } + return this.addLigature(feature, sub, script, language); + } + return undefined; + }; - if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } + function isBrowser() { + return typeof window !== 'undefined'; + } - state.cvCutIn = n / 0x40; -} + function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } -// DUP[] DUPlicate top stack element -// 0x20 -function DUP(state) { - var stack = state.stack; + return ab; + } - if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } + function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } - stack.push(stack[stack.length - 1]); -} + return buffer; + } -// POP[] POP top stack element -// 0x21 -function POP(state) { - if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + function checkArgument(expression, message) { + if (!expression) { + throw message; + } + } - state.stack.pop(); -} + // The `glyf` table describes the glyphs in TrueType outline format. -// CLEAR[] CLEAR the stack -// 0x22 -function CLEAR(state) { - if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } + // Parse the coordinate data for a glyph. + function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } - state.stack.length = 0; -} + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } -// SWAP[] SWAP the top two elements on the stack -// 0x23 -function SWAP(state) { - var stack = state.stack; + return v; + } - var a = stack.pop(); - var b = stack.pop(); + // Parse a TrueType glyph. + function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } - if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } - stack.push(a); - stack.push(b); -} + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } -// DEPTH[] DEPTH of the stack -// 0x24 -function DEPTH(state) { - var stack = state.stack; + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } - if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } - stack.push(stack.length); -} + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } -// LOOPCALL[] LOOPCALL function -// 0x2A -function LOOPCALL(state) { - var stack = state.stack; - var fn = stack.pop(); - var c = stack.pop(); + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } - if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } - // saves callers program - var cip = state.ip; - var cprog = state.prog; + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } - state.prog = state.funcs[fn]; + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } + } - // executes the function - for (var i = 0; i < c; i++) { - exec(state); + // Transform an array of points and return a new array. + function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } - if (exports.DEBUG) { console.log( - ++state.step, - i + 1 < c ? 'next loopcall' : 'done loopcall', - i - ); } + return newPoints; } - // restores the callers program - state.ip = cip; - state.prog = cprog; -} + function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } -// CALL[] CALL function -// 0x2B -function CALL(state) { - var fn = state.stack.pop(); + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; + } - if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } + // Convert the TrueType glyph outline to a Path. + function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } - // saves callers program - var cip = state.ip; - var cprog = state.prog; + var contours = getContours(points); - state.prog = state.funcs[fn]; + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; - // executes the function - exec(state); + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; - // restores the callers program - state.ip = cip; - state.prog = cprog; + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); + } + } - if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } -} + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; -// CINDEX[] Copy the INDEXed element to the top of the stack -// 0x25 -function CINDEX(state) { - var stack = state.stack; - var k = stack.pop(); + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; - if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } - // In case of k == 1, it copies the last element after popping - // thus stack.length - k. - stack.push(stack[stack.length - k]); -} + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } -// MINDEX[] Move the INDEXed element to the top of the stack -// 0x26 -function MINDEX(state) { - var stack = state.stack; - var k = stack.pop(); + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } - if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } + p.closePath(); + } + return p; + } - stack.push(stack.splice(stack.length - k, 1)[0]); -} + function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } -// FDEF[] Function DEFinition -// 0x2C -function FDEF(state) { - if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; + return getPath(glyph.points); + } - var fn = stack.pop(); - var ipBegin = ip; + function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); - if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } - while (prog[++ip] !== 0x2D){ } + return glyphs; + } - state.ip = ip; - state.funcs[fn] = prog.slice(ipBegin + 1, ip); -} + function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); -// MDAP[a] Move Direct Absolute Point -// 0x2E-0x2F -function MDAP(round, state) { - var pi = state.stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; - if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } + return glyphs; + } + + // Parse all the glyphs according to the offsets from the `loca` table. + function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } + } + + var glyf = { getPath: getPath, parse: parseGlyfTable}; + + /* A TrueType font hinting interpreter. + * + * (c) 2017 Axel Kittenberger + * + * This interpreter has been implemented according to this documentation: + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html + * + * According to the documentation F24DOT6 values are used for pixels. + * That means calculation is 1/64 pixel accurate and uses integer operations. + * However, Javascript has floating point operations by default and only + * those are available. One could make a case to simulate the 1/64 accuracy + * exactly by truncating after every division operation + * (for example with << 0) to get pixel exactly results as other TrueType + * implementations. It may make sense since some fonts are pixel optimized + * by hand using DELTAP instructions. The current implementation doesn't + * and rather uses full floating point precision. + * + * xScale, yScale and rotation is currently ignored. + * + * A few non-trivial instructions are missing as I didn't encounter yet + * a font that used them to test a possible implementation. + * + * Some fonts seem to use undocumented features regarding the twilight zone. + * Only some of them are implemented as they were encountered. + * + * The exports.DEBUG statements are removed on the minified distribution file. + */ + + var instructionTable; + var exec; + var execGlyph; + var execComponent; + + /* + * Creates a hinting object. + * + * There ought to be exactly one + * for each truetype font that is used for hinting. + */ + function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; - var d = pv.distance(p, HPZero); + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; + } + + /* + * Not rounding. + */ + function roundOff(v) { + return v; + } - if (round) { d = state.round(d); } + /* + * Rounding to grid. + */ + function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); + } - fv.setRelative(p, HPZero, d, pv); - fv.touch(p); + /* + * Rounding to double grid. + */ + function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; + } - state.rp0 = state.rp1 = pi; -} + /* + * Rounding to half grid. + */ + function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); + } -// IUP[a] Interpolate Untouched Points through the outline -// 0x30 -function IUP(v, state) { - var z2 = state.z2; - var pLen = z2.length - 2; - var cp; - var pp; - var np; + /* + * Rounding to up to grid. + */ + function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); + } - if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + /* + * Rounding to down to grid. + */ + function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); + } - for (var i = 0; i < pLen; i++) { - cp = z2[i]; // current point + /* + * Super rounding. + */ + var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; - // if this point has been touched go on - if (v.touched(cp)) { continue; } + if (v < 0) { + v = -v; + sign = -1; + } - pp = cp.prevTouched(v); + v += threshold - phase; - // no point on the contour has been touched? - if (pp === cp) { continue; } + v = Math.trunc(v / period) * period; - np = cp.nextTouched(v); + v += phase; - if (pp === np) { - // only one point on the contour has been touched - // so simply moves the point like that + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } - v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); - } + return v * sign; + }; - v.interpolate(cp, pp, np, v); - } -} + /* + * Unit vector of x-axis. + */ + var xUnitVector = { + x: 1, -// SHP[] SHift Point using reference point -// 0x32-0x33 -function SHP(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var loop = state.loop; - var z2 = state.z2; + y: 0, - while (loop--) - { - var pi = stack.pop(); - var p = z2[pi]; + axis: 'x', - var d = pv.distance(rp, rp, false, true); - fv.setRelative(p, p, d, pv); - fv.touch(p); + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? - 'loop ' + (state.loop - loop) + ': ' : - '' - ) + - 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi - ); - } - } + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } - state.loop = 1; -} + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } -// SHC[] SHift Contour using reference point -// 0x36-0x37 -function SHC(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var ci = stack.pop(); - var sp = state.z2[state.contours[ci]]; - var p = sp; + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; - if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } - var d = pv.distance(rp, rp, false, true); + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, - do { - if (p !== rp) { fv.setRelative(p, p, d, pv); } - p = p.nextPointOnContour; - } while (p !== sp); -} - -// SHZ[] SHift Zone using reference point -// 0x36-0x37 -function SHZ(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - - var e = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } - - var z; - switch (e) { - case 0 : z = state.tZone; break; - case 1 : z = state.gZone; break; - default : throw new Error('Invalid zone'); - } - - var p; - var d = pv.distance(rp, rp, false, true); - var pLen = z.length - 2; - for (var i = 0; i < pLen; i++) - { - p = z[i]; - fv.setRelative(p, p, d, pv); - //if (p !== rp) fv.setRelative(p, p, d, pv); - } -} - -// SHPIX[] SHift point by a PIXel amount -// 0x38 -function SHPIX(state) { - var stack = state.stack; - var loop = state.loop; - var fv = state.fv; - var d = stack.pop() / 0x40; - var z2 = state.z2; - - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'SHPIX[]', pi, d - ); - } + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; - fv.setRelative(p, p, d); - fv.touch(p); - } + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, - state.loop = 1; -} + // Slope of vector line. + slope: 0, -// IP[] Interpolate Point -// 0x39 -function IP(state) { - var stack = state.stack; - var rp1i = state.rp1; - var rp2i = state.rp2; - var loop = state.loop; - var rp1 = state.z0[rp1i]; - var rp2 = state.z1[rp2i]; - var fv = state.fv; - var pv = state.dpv; - var z2 = state.z2; + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'IP[]', pi, rp1i, '<->', rp2i - ); + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; } + }; - fv.interpolate(p, rp1, rp2, pv); - - fv.touch(p); - } + /* + * Unit vector of y-axis. + */ + var yUnitVector = { + x: 0, - state.loop = 1; -} - -// MSIRP[a] Move Stack Indirect Relative Point -// 0x3A-0x3B -function MSIRP(a, state) { - var stack = state.stack; - var d = stack.pop() / 64; - var pi = stack.pop(); - var p = state.z1[pi]; - var rp0 = state.z0[state.rp0]; - var fv = state.fv; - var pv = state.pv; - - fv.setRelative(p, rp0, d, pv); - fv.touch(p); - - if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } - - state.rp1 = state.rp0; - state.rp2 = pi; - if (a) { state.rp0 = pi; } -} - -// ALIGNRP[] Align to reference point. -// 0x3C -function ALIGNRP(state) { - var stack = state.stack; - var rp0i = state.rp0; - var rp0 = state.z0[rp0i]; - var loop = state.loop; - var fv = state.fv; - var pv = state.pv; - var z1 = state.z1; - - while (loop--) { - var pi = stack.pop(); - var p = z1[pi]; + y: 1, - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'ALIGNRP[]', pi - ); - } + axis: 'y', - fv.setRelative(p, rp0, 0, pv); - fv.touch(p); - } + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, - state.loop = 1; -} - -// RTG[] Round To Double Grid -// 0x3D -function RTDG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } - - state.round = roundToDoubleGrid; -} - -// MIAP[a] Move Indirect Absolute Point -// 0x3E-0x3F -function MIAP(round, state) { - var stack = state.stack; - var n = stack.pop(); - var pi = stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; - var cv = state.cvt[n]; - - if (exports.DEBUG) { - console.log( - state.step, - 'MIAP[' + round + ']', - n, '(', cv, ')', pi - ); - } + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } - var d = pv.distance(p, HPZero); + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } - if (round) { - if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; - d = state.round(d); - } + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } - fv.setRelative(p, HPZero, d, pv); + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, - if (state.zp0 === 0) { - p.xo = p.x; - p.yo = p.y; - } + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; + } - fv.touch(p); + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; - state.rp0 = state.rp1 = pi; -} + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, -// NPUSB[] PUSH N Bytes -// 0x40 -function NPUSHB(state) { - var prog = state.prog; - var ip = state.ip; - var stack = state.stack; + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, - var n = prog[++ip]; + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, - if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; + } + }; - state.ip = ip; -} + Object.freeze(xUnitVector); + Object.freeze(yUnitVector); + + /* + * Creates a unit vector that is not x- or y-axis. + */ + function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); + } + + /* + * Gets the projected distance between two points. + * o1/o2 ... if true, respective original position is used. + */ + UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); + }; -// NPUSHW[] PUSH N Words -// 0x41 -function NPUSHW(state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - var n = prog[++ip]; + /* + * Moves point p so the moved position has the same relative + * position to the moved positions of rp1 and rp2 than the + * original positions had. + * + * See APPENDIX on INTERPOLATE at the bottom of this file. + */ + UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; - if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } - state.ip = ip; -} + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }; -// WS[] Write Store -// 0x42 -function WS(state) { - var stack = state.stack; - var store = state.store; + /* + * Sets the point 'p' relative to point 'rp' + * by the distance 'd' + * + * See APPENDIX on SETRELATIVE at the bottom of this file. + * + * p ... point to set + * rp ... reference point + * d ... distance on projection vector + * pv ... projection vector (undefined = this) + * org ... if true, uses the original position of rp as reference. + */ + UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; - if (!store) { store = state.store = []; } + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; - var v = stack.pop(); - var l = stack.pop(); + var pvns = pv.normalSlope; + var fvs = this.slope; - if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + var px = p.x; + var py = p.y; - store[l] = v; -} + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; + }; -// RS[] Read Store -// 0x43 -function RS(state) { - var stack = state.stack; - var store = state.store; + /* + * Touches the point p. + */ + UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; + }; - var l = stack.pop(); + /* + * Returns a unit vector with x/y coordinates. + */ + function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); - if (exports.DEBUG) { console.log(state.step, 'RS', l); } + x /= d; + y /= d; - var v = (store && store[l]) || 0; + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } + } - stack.push(v); -} + /* + * Creates a point in the hinting engine. + */ + function HPoint( + x, + y, + lastPointOfContour, + onCurve + ) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value -// WCVTP[] Write Control Value Table in Pixel units -// 0x44 -function WCVTP(state) { - var stack = state.stack; + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; - var v = stack.pop(); - var l = stack.pop(); + Object.preventExtensions(this); + } - if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + /* + * Returns the next touched point on the contour. + * + * v ... unit vector to test touch axis. + */ + HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; - state.cvt[l] = v / 0x40; -} + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } -// RCVT[] Read Control Value Table entry -// 0x45 -function RCVT(state) { - var stack = state.stack; - var cvte = stack.pop(); + return p; + }; - if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } + /* + * Returns the previous touched point on the contour + * + * v ... unit vector to test touch axis. + */ + HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; - stack.push(state.cvt[cvte] * 0x40); -} + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } -// GC[] Get Coordinate projected onto the projection vector -// 0x46-0x47 -function GC(a, state) { - var stack = state.stack; - var pi = stack.pop(); - var p = state.z2[pi]; + return p; + }; - if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } + /* + * The zero point. + */ + var HPZero = Object.freeze(new HPoint(0, 0)); + + /* + * The default state of the interpreter. + * + * Note: Freezing the defaultState and then deriving from it + * makes the V8 Javascript engine going awkward, + * so this is avoided, albeit the defaultState shouldn't + * ever change. + */ + var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true + }; - stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); -} + /* + * The current state of the interpreter. + * + * env ... 'fpgm' or 'prep' or 'glyf' + * prog ... the program + */ + function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } + } -// MD[a] Measure Distance -// 0x49-0x4A -function MD(a, state) { - var stack = state.stack; - var pi2 = stack.pop(); - var pi1 = stack.pop(); - var p2 = state.z1[pi2]; - var p1 = state.z0[pi1]; - var d = state.dpv.distance(p1, p2, a, a); + /* + * Executes a glyph program. + * + * This does the hinting for each glyph. + * + * Returns an array of moved points. + * + * glyph: the glyph to hint + * ppem: the size the glyph is rendered for + */ + Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } - if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } - state.stack.push(Math.round(d * 64)); -} + var font = this.font; + var prepState = this._prepState; -// MPPEM[] Measure Pixels Per EM -// 0x4B -function MPPEM(state) { - if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } - state.stack.push(state.ppem); -} + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; -// FLIPON[] set the auto FLIP Boolean to ON -// 0x4D -function FLIPON(state) { - if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } - state.autoFlip = true; -} + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; -// LT[] Less Than -// 0x50 -function LT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); - if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } + fpgmState.funcs = [ ]; + fpgmState.font = font; - stack.push(e1 < e2 ? 1 : 0); -} + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } -// LTEQ[] Less Than or EQual -// 0x53 -function LTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; + } + } - if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. + + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); + + prepState.ppem = ppem; + + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; + } + } else { + prepState.cvt = []; + } - stack.push(e1 <= e2 ? 1 : 0); -} + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } -// GTEQ[] Greater Than -// 0x52 -function GT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } - if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + if (this._errorState > 1) { return; } - stack.push(e1 > e2 ? 1 : 0); -} - -// GTEQ[] Greater Than or EQual -// 0x53 -function GTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); + } + this._errorState = 1; + return undefined; + } + }; - if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } + /* + * Executes the hinting program for a glyph. + */ + execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); + + state = new State('glyf', cg.instructions); + + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } - stack.push(e1 >= e2 ? 1 : 0); -} - -// EQ[] EQual -// 0x54 -function EQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } - if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } - - stack.push(e2 === e1 ? 1 : 0); -} - -// NEQ[] Not EQual -// 0x55 -function NEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } - - stack.push(e2 !== e1 ? 1 : 0); -} - -// ODD[] ODD -// 0x56 -function ODD(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } - - stack.push(Math.trunc(n) % 2 ? 1 : 0); -} - -// EVEN[] EVEN -// 0x57 -function EVEN(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } - - stack.push(Math.trunc(n) % 2 ? 0 : 1); -} - -// IF[] IF test -// 0x58 -function IF(state) { - var test = state.stack.pop(); + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } - if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } - - // if test is true it just continues - // if not the ip is skipped until matching ELSE or EIF - if (!test) { - skip(state, true); + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } - } -} + state.gZone = state.z0 = state.z1 = state.z2 = gZone; -// EIF[] End IF -// 0x59 -function EIF(state) { - // this can be reached normally when - // executing an else branch. - // -> just ignore it + state.contours = contours; - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } -} + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); -// AND[] logical AND -// 0x5A -function AND(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } - if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } + exec(state); - stack.push(e2 && e1 ? 1 : 0); -} + gZone.length -= 2; + } + } -// OR[] logical OR -// 0x5B -function OR(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + return gZone; + }; - if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } + /* + * Executes the hinting program for a component of a multi-component glyph + * or of the glyph itself for a non-component glyph. + */ + execComponent = function(glyph, state, xScale, yScale) + { + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + + // Chain links the contours. + var sp; // start point + var np; // next point - stack.push(e2 || e1 ? 1 : 0); -} + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; -// NOT[] logical NOT -// 0x5C -function NOT(state) { - var stack = state.stack; - var e = stack.pop(); + if (!sp) { + sp = cp; + contours.push(i$1); + } - if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } - stack.push(e ? 0 : 1); -} + if (state.inhibitGridFit) { return; } -// DELTAP1[] DELTA exception P1 -// DELTAP2[] DELTA exception P2 -// DELTAP3[] DELTA exception P3 -// 0x5D, 0x71, 0x72 -function DELTAP123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var fv = state.fv; - var pv = state.pv; - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - var z0 = state.z0; + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } - if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); - for (var i = 0; i < n; i++) { - var pi = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } + exec(state); - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + // Removes the extra points. + gZone.length -= 2; - var p = z0[pi]; - fv.setRelative(p, p, mag * ds, pv); - } -} + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } + }; + + /* + * Executes the program loaded in state. + */ + exec = function(state) { + var prog = state.prog; -// SDB[] Set Delta Base in the graphics state -// 0x5E -function SDB(state) { - var stack = state.stack; - var n = stack.pop(); + if (!prog) { return; } - if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + var pLen = prog.length; + var ins; - state.deltaBase = n; -} + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; -// SDS[] Set Delta Shift in the graphics state -// 0x5F -function SDS(state) { - var stack = state.stack; - var n = stack.pop(); + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); + } - if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } + ins(state); + + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } - state.deltaShift = Math.pow(0.5, n); -} + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } -// ADD[] ADD -// 0x60 -function ADD(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } + } + */ + } + }; - if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } + /* + * Initializes the twilight zone. + * + * This is only done if a SZPx instruction + * refers to the twilight zone. + */ + function initTZone(state) + { + var tZone = state.tZone = new Array(state.gZone.length); - stack.push(n1 + n2); -} + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } + } -// SUB[] SUB -// 0x61 -function SUB(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); + /* + * Skips the instruction pointer ahead over an IF/ELSE block. + * handleElse .. if true breaks on matching ELSE + */ + function skip(state, handleElse) + { + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; - if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); - stack.push(n1 - n2); -} + state.ip = ip; + } -// DIV[] DIV -// 0x62 -function DIV(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); + /*----------------------------------------------------------* + * And then a lot of instructions... * + *----------------------------------------------------------*/ - if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } + // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis + // 0x00-0x01 + function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } - stack.push(n1 * 64 / n2); -} + state.fv = state.pv = state.dpv = v; + } -// MUL[] MUL -// 0x63 -function MUL(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); + // SPVTCA[a] Set Projection Vector to Coordinate Axis + // 0x02-0x03 + function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } - if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } + state.pv = state.dpv = v; + } - stack.push(n1 * n2 / 64); -} + // SFVTCA[a] Set Freedom Vector to Coordinate Axis + // 0x04-0x05 + function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } -// ABS[] ABSolute value -// 0x64 -function ABS(state) { - var stack = state.stack; - var n = stack.pop(); + state.fv = v; + } - if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } + // SPVTL[a] Set Projection Vector To Line + // 0x06-0x07 + function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; - stack.push(Math.abs(n)); -} + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } -// NEG[] NEGate -// 0x65 -function NEG(state) { - var stack = state.stack; - var n = stack.pop(); + var dx; + var dy; - if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } - stack.push(-n); -} + state.pv = state.dpv = getUnitVector(dx, dy); + } -// FLOOR[] FLOOR -// 0x66 -function FLOOR(state) { - var stack = state.stack; - var n = stack.pop(); + // SFVTL[a] Set Freedom Vector To Line + // 0x08-0x09 + function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; - if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } - stack.push(Math.floor(n / 0x40) * 0x40); -} + var dx; + var dy; -// CEILING[] CEILING -// 0x67 -function CEILING(state) { - var stack = state.stack; - var n = stack.pop(); + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } - if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + state.fv = getUnitVector(dx, dy); + } - stack.push(Math.ceil(n / 0x40) * 0x40); -} + // SPVFS[] Set Projection Vector From Stack + // 0x0A + function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); -// ROUND[ab] ROUND value -// 0x68-0x6B -function ROUND(dt, state) { - var stack = state.stack; - var n = stack.pop(); + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + state.pv = state.dpv = getUnitVector(x, y); + } - stack.push(state.round(n / 0x40) * 0x40); -} + // SFVFS[] Set Freedom Vector From Stack + // 0x0B + function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); -// WCVTF[] Write Control Value Table in Funits -// 0x70 -function WCVTF(state) { - var stack = state.stack; - var v = stack.pop(); - var l = stack.pop(); + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + state.fv = getUnitVector(x, y); + } - state.cvt[l] = v * state.ppem / state.font.unitsPerEm; -} + // GPV[] Get Projection Vector + // 0x0C + function GPV(state) { + var stack = state.stack; + var pv = state.pv; -// DELTAC1[] DELTA exception C1 -// DELTAC2[] DELTA exception C2 -// DELTAC3[] DELTA exception C3 -// 0x73, 0x74, 0x75 -function DELTAC123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } - if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); + } - for (var i = 0; i < n; i++) { - var c = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } + // GFV[] Get Freedom Vector + // 0x0C + function GFV(state) { + var stack = state.stack; + var fv = state.fv; - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } - var delta = mag * ds; + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); + } - if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + // SFVTPV[] Set Freedom Vector To Projection Vector + // 0x0E + function SFVTPV(state) { + state.fv = state.pv; - state.cvt[c] += delta; + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } } -} -// SROUND[] Super ROUND -// 0x76 -function SROUND(state) { - var n = state.stack.pop(); + // ISECT[] moves point p to the InterSECTion of two lines + // 0x0F + function ISECT(state) + { + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; - if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } - state.round = roundSuper; + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line - var period; + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; - switch (n & 0xC0) { - case 0x00: - period = 0.5; - break; - case 0x40: - period = 1; - break; - case 0x80: - period = 2; - break; - default: - throw new Error('invalid SROUND value'); + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; } - state.srPeriod = period; + // SRP0[] Set Reference Point 0 + // 0x10 + function SRP0(state) { + state.rp0 = state.stack.pop(); - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: throw new Error('invalid SROUND value'); + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } } - n &= 0x0F; + // SRP1[] Set Reference Point 1 + // 0x11 + function SRP1(state) { + state.rp1 = state.stack.pop(); - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } -} + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } + } -// S45ROUND[] Super ROUND 45 degrees -// 0x77 -function S45ROUND(state) { - var n = state.stack.pop(); + // SRP1[] Set Reference Point 2 + // 0x12 + function SRP2(state) { + state.rp2 = state.stack.pop(); - if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } + } - state.round = roundSuper; + // SZP0[] Set Zone Pointer 0 + // 0x13 + function SZP0(state) { + var n = state.stack.pop(); - var period; + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } - switch (n & 0xC0) { - case 0x00: - period = Math.sqrt(2) / 2; - break; - case 0x40: - period = Math.sqrt(2); - break; - case 0x80: - period = 2 * Math.sqrt(2); - break; - default: - throw new Error('invalid S45ROUND value'); + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } } - state.srPeriod = period; + // SZP1[] Set Zone Pointer 1 + // 0x14 + function SZP1(state) { + var n = state.stack.pop(); - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: - throw new Error('invalid S45ROUND value'); + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + + state.zp1 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } } - n &= 0x0F; + // SZP2[] Set Zone Pointer 2 + // 0x15 + function SZP2(state) { + var n = state.stack.pop(); - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } -} + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } + + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } -// ROFF[] Round Off -// 0x7A -function ROFF(state) { - if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } + // SZPS[] Set Zone PointerS + // 0x16 + function SZPS(state) { + var n = state.stack.pop(); - state.round = roundOff; -} + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } -// RUTG[] Round Up To Grid -// 0x7C -function RUTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } + state.zp0 = state.zp1 = state.zp2 = n; - state.round = roundUpToGrid; -} + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } -// RDTG[] Round Down To Grid -// 0x7D -function RDTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } + // SLOOP[] Set LOOP variable + // 0x17 + function SLOOP(state) { + state.loop = state.stack.pop(); - state.round = roundDownToGrid; -} + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } + } -// SCANCTRL[] SCAN conversion ConTRoL -// 0x85 -function SCANCTRL(state) { - var n = state.stack.pop(); + // RTG[] Round To Grid + // 0x18 + function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } - // ignored by opentype.js + state.round = roundToGrid; + } - if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } -} + // RTHG[] Round To Half Grid + // 0x19 + function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } -// SDPVTL[a] Set Dual Projection Vector To Line -// 0x86-0x87 -function SDPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; + state.round = roundToHalfGrid; + } - if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + // SMD[] Set Minimum Distance + // 0x1A + function SMD(state) { + var d = state.stack.pop(); - var dx; - var dy; + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; + state.minDis = d / 0x40; } - state.dpv = getUnitVector(dx, dy); -} + // ELSE[] ELSE clause + // 0x1B + function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] + + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } -// GETINFO[] GET INFOrmation -// 0x88 -function GETINFO(state) { - var stack = state.stack; - var sel = stack.pop(); - var r = 0; + skip(state, false); + } - if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } + // JMPR[] JuMP Relative + // 0x1C + function JMPR(state) { + var o = state.stack.pop(); - // v35 as in no subpixel hinting - if (sel & 0x01) { r = 35; } + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } - // TODO rotation and stretch currently not supported - // and thus those GETINFO are always 0. + // A jump by 1 would do nothing. + state.ip += o - 1; + } - // opentype.js is always gray scaling - if (sel & 0x20) { r |= 0x1000; } + // SCVTCI[] Set Control Value Table Cut-In + // 0x1D + function SCVTCI(state) { + var n = state.stack.pop(); - stack.push(r); -} + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } -// ROLL[] ROLL the top three stack elements -// 0x8A -function ROLL(state) { - var stack = state.stack; - var a = stack.pop(); - var b = stack.pop(); - var c = stack.pop(); + state.cvCutIn = n / 0x40; + } - if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } + // DUP[] DUPlicate top stack element + // 0x20 + function DUP(state) { + var stack = state.stack; - stack.push(b); - stack.push(a); - stack.push(c); -} - -// MAX[] MAXimum of top two stack elements -// 0x8B -function MAX(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } - - stack.push(Math.max(e1, e2)); -} - -// MIN[] MINimum of top two stack elements -// 0x8C -function MIN(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } - if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } + stack.push(stack[stack.length - 1]); + } - stack.push(Math.min(e1, e2)); -} + // POP[] POP top stack element + // 0x21 + function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } -// SCANTYPE[] SCANTYPE -// 0x8D -function SCANTYPE(state) { - var n = state.stack.pop(); - // ignored by opentype.js - if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } -} - -// INSTCTRL[] INSTCTRL -// 0x8D -function INSTCTRL(state) { - var s = state.stack.pop(); - var v = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } - - switch (s) { - case 1 : state.inhibitGridFit = !!v; return; - case 2 : state.ignoreCvt = !!v; return; - default: throw new Error('invalid INSTCTRL[] selector'); - } -} - -// PUSHB[abc] PUSH Bytes -// 0xB0-0xB7 -function PUSHB(n, state) { - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; - - if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - - state.ip = ip; -} - -// PUSHW[abc] PUSH Words -// 0xB8-0xBF -function PUSHW(n, state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } - - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } - - state.ip = ip; -} - -// MDRP[abcde] Move Direct Relative Point -// 0xD0-0xEF -// (if indirect is 0) -// -// and -// -// MIRP[abcde] Move Indirect Relative Point -// 0xE0-0xFF -// (if indirect is 1) - -function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { - var stack = state.stack; - var cvte = indirect && stack.pop(); - var pi = stack.pop(); - var rp0i = state.rp0; - var rp = state.z0[rp0i]; - var p = state.z1[pi]; - - var md = state.minDis; - var fv = state.fv; - var pv = state.dpv; - var od; // original distance - var d; // moving distance - var sign; // sign of distance - var cv; - - d = od = pv.distance(p, rp, true, true); - sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 - - // TODO consider autoFlip - d = Math.abs(d); - - if (indirect) { - cv = state.cvt[cvte]; - - if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } - } - - if (keepD && d < md) { d = md; } - - if (ro) { d = state.round(d); } - - fv.setRelative(p, rp, sign * d, pv); - fv.touch(p); - - if (exports.DEBUG) { - console.log( - state.step, - (indirect ? 'MIRP[' : 'MDRP[') + - (setRp0 ? 'M' : 'm') + - (keepD ? '>' : '_') + - (ro ? 'R' : '_') + - (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + - ']', - indirect ? - cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : - '', - pi, - '(d =', od, '->', sign * d, ')' - ); + state.stack.pop(); } - state.rp1 = state.rp0; - state.rp2 = pi; - if (setRp0) { state.rp0 = pi; } -} - -/* -* The instruction table. -*/ -instructionTable = [ - /* 0x00 */ SVTCA.bind(undefined, yUnitVector), - /* 0x01 */ SVTCA.bind(undefined, xUnitVector), - /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), - /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), - /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), - /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), - /* 0x06 */ SPVTL.bind(undefined, 0), - /* 0x07 */ SPVTL.bind(undefined, 1), - /* 0x08 */ SFVTL.bind(undefined, 0), - /* 0x09 */ SFVTL.bind(undefined, 1), - /* 0x0A */ SPVFS, - /* 0x0B */ SFVFS, - /* 0x0C */ GPV, - /* 0x0D */ GFV, - /* 0x0E */ SFVTPV, - /* 0x0F */ ISECT, - /* 0x10 */ SRP0, - /* 0x11 */ SRP1, - /* 0x12 */ SRP2, - /* 0x13 */ SZP0, - /* 0x14 */ SZP1, - /* 0x15 */ SZP2, - /* 0x16 */ SZPS, - /* 0x17 */ SLOOP, - /* 0x18 */ RTG, - /* 0x19 */ RTHG, - /* 0x1A */ SMD, - /* 0x1B */ ELSE, - /* 0x1C */ JMPR, - /* 0x1D */ SCVTCI, - /* 0x1E */ undefined, // TODO SSWCI - /* 0x1F */ undefined, // TODO SSW - /* 0x20 */ DUP, - /* 0x21 */ POP, - /* 0x22 */ CLEAR, - /* 0x23 */ SWAP, - /* 0x24 */ DEPTH, - /* 0x25 */ CINDEX, - /* 0x26 */ MINDEX, - /* 0x27 */ undefined, // TODO ALIGNPTS - /* 0x28 */ undefined, - /* 0x29 */ undefined, // TODO UTP - /* 0x2A */ LOOPCALL, - /* 0x2B */ CALL, - /* 0x2C */ FDEF, - /* 0x2D */ undefined, // ENDF (eaten by FDEF) - /* 0x2E */ MDAP.bind(undefined, 0), - /* 0x2F */ MDAP.bind(undefined, 1), - /* 0x30 */ IUP.bind(undefined, yUnitVector), - /* 0x31 */ IUP.bind(undefined, xUnitVector), - /* 0x32 */ SHP.bind(undefined, 0), - /* 0x33 */ SHP.bind(undefined, 1), - /* 0x34 */ SHC.bind(undefined, 0), - /* 0x35 */ SHC.bind(undefined, 1), - /* 0x36 */ SHZ.bind(undefined, 0), - /* 0x37 */ SHZ.bind(undefined, 1), - /* 0x38 */ SHPIX, - /* 0x39 */ IP, - /* 0x3A */ MSIRP.bind(undefined, 0), - /* 0x3B */ MSIRP.bind(undefined, 1), - /* 0x3C */ ALIGNRP, - /* 0x3D */ RTDG, - /* 0x3E */ MIAP.bind(undefined, 0), - /* 0x3F */ MIAP.bind(undefined, 1), - /* 0x40 */ NPUSHB, - /* 0x41 */ NPUSHW, - /* 0x42 */ WS, - /* 0x43 */ RS, - /* 0x44 */ WCVTP, - /* 0x45 */ RCVT, - /* 0x46 */ GC.bind(undefined, 0), - /* 0x47 */ GC.bind(undefined, 1), - /* 0x48 */ undefined, // TODO SCFS - /* 0x49 */ MD.bind(undefined, 0), - /* 0x4A */ MD.bind(undefined, 1), - /* 0x4B */ MPPEM, - /* 0x4C */ undefined, // TODO MPS - /* 0x4D */ FLIPON, - /* 0x4E */ undefined, // TODO FLIPOFF - /* 0x4F */ undefined, // TODO DEBUG - /* 0x50 */ LT, - /* 0x51 */ LTEQ, - /* 0x52 */ GT, - /* 0x53 */ GTEQ, - /* 0x54 */ EQ, - /* 0x55 */ NEQ, - /* 0x56 */ ODD, - /* 0x57 */ EVEN, - /* 0x58 */ IF, - /* 0x59 */ EIF, - /* 0x5A */ AND, - /* 0x5B */ OR, - /* 0x5C */ NOT, - /* 0x5D */ DELTAP123.bind(undefined, 1), - /* 0x5E */ SDB, - /* 0x5F */ SDS, - /* 0x60 */ ADD, - /* 0x61 */ SUB, - /* 0x62 */ DIV, - /* 0x63 */ MUL, - /* 0x64 */ ABS, - /* 0x65 */ NEG, - /* 0x66 */ FLOOR, - /* 0x67 */ CEILING, - /* 0x68 */ ROUND.bind(undefined, 0), - /* 0x69 */ ROUND.bind(undefined, 1), - /* 0x6A */ ROUND.bind(undefined, 2), - /* 0x6B */ ROUND.bind(undefined, 3), - /* 0x6C */ undefined, // TODO NROUND[ab] - /* 0x6D */ undefined, // TODO NROUND[ab] - /* 0x6E */ undefined, // TODO NROUND[ab] - /* 0x6F */ undefined, // TODO NROUND[ab] - /* 0x70 */ WCVTF, - /* 0x71 */ DELTAP123.bind(undefined, 2), - /* 0x72 */ DELTAP123.bind(undefined, 3), - /* 0x73 */ DELTAC123.bind(undefined, 1), - /* 0x74 */ DELTAC123.bind(undefined, 2), - /* 0x75 */ DELTAC123.bind(undefined, 3), - /* 0x76 */ SROUND, - /* 0x77 */ S45ROUND, - /* 0x78 */ undefined, // TODO JROT[] - /* 0x79 */ undefined, // TODO JROF[] - /* 0x7A */ ROFF, - /* 0x7B */ undefined, - /* 0x7C */ RUTG, - /* 0x7D */ RDTG, - /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though - /* 0x7F */ POP, // actually AA, supposed to do only a pop though - /* 0x80 */ undefined, // TODO FLIPPT - /* 0x81 */ undefined, // TODO FLIPRGON - /* 0x82 */ undefined, // TODO FLIPRGOFF - /* 0x83 */ undefined, - /* 0x84 */ undefined, - /* 0x85 */ SCANCTRL, - /* 0x86 */ SDPVTL.bind(undefined, 0), - /* 0x87 */ SDPVTL.bind(undefined, 1), - /* 0x88 */ GETINFO, - /* 0x89 */ undefined, // TODO IDEF - /* 0x8A */ ROLL, - /* 0x8B */ MAX, - /* 0x8C */ MIN, - /* 0x8D */ SCANTYPE, - /* 0x8E */ INSTCTRL, - /* 0x8F */ undefined, - /* 0x90 */ undefined, - /* 0x91 */ undefined, - /* 0x92 */ undefined, - /* 0x93 */ undefined, - /* 0x94 */ undefined, - /* 0x95 */ undefined, - /* 0x96 */ undefined, - /* 0x97 */ undefined, - /* 0x98 */ undefined, - /* 0x99 */ undefined, - /* 0x9A */ undefined, - /* 0x9B */ undefined, - /* 0x9C */ undefined, - /* 0x9D */ undefined, - /* 0x9E */ undefined, - /* 0x9F */ undefined, - /* 0xA0 */ undefined, - /* 0xA1 */ undefined, - /* 0xA2 */ undefined, - /* 0xA3 */ undefined, - /* 0xA4 */ undefined, - /* 0xA5 */ undefined, - /* 0xA6 */ undefined, - /* 0xA7 */ undefined, - /* 0xA8 */ undefined, - /* 0xA9 */ undefined, - /* 0xAA */ undefined, - /* 0xAB */ undefined, - /* 0xAC */ undefined, - /* 0xAD */ undefined, - /* 0xAE */ undefined, - /* 0xAF */ undefined, - /* 0xB0 */ PUSHB.bind(undefined, 1), - /* 0xB1 */ PUSHB.bind(undefined, 2), - /* 0xB2 */ PUSHB.bind(undefined, 3), - /* 0xB3 */ PUSHB.bind(undefined, 4), - /* 0xB4 */ PUSHB.bind(undefined, 5), - /* 0xB5 */ PUSHB.bind(undefined, 6), - /* 0xB6 */ PUSHB.bind(undefined, 7), - /* 0xB7 */ PUSHB.bind(undefined, 8), - /* 0xB8 */ PUSHW.bind(undefined, 1), - /* 0xB9 */ PUSHW.bind(undefined, 2), - /* 0xBA */ PUSHW.bind(undefined, 3), - /* 0xBB */ PUSHW.bind(undefined, 4), - /* 0xBC */ PUSHW.bind(undefined, 5), - /* 0xBD */ PUSHW.bind(undefined, 6), - /* 0xBE */ PUSHW.bind(undefined, 7), - /* 0xBF */ PUSHW.bind(undefined, 8), - /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), - /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), - /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), - /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), - /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), - /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), - /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), - /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), - /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), - /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), - /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), - /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), - /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), - /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), - /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), - /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), - /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), - /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), - /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), - /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), - /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), - /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), - /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), - /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), - /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), - /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), - /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), - /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), - /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), - /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), - /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), - /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), - /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), - /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), - /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), - /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), - /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), - /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), - /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), - /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), - /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), - /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), - /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), - /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), - /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), - /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), - /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), - /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), - /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), - /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), - /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), - /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), - /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), - /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), - /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), - /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), - /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), - /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), - /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), - /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), - /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), - /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), - /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), - /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) -]; - -/***************************** - Mathematical Considerations -****************************** - -fv ... refers to freedom vector -pv ... refers to projection vector -rp ... refers to reference point -p ... refers to to point being operated on -d ... refers to distance - -SETRELATIVE: -============ - -case freedom vector == x-axis: ------------------------------- - - (pv) - .-' - rpd .-' - .-* - d .-'90°' - .-' ' - .-' ' - *-' ' b - rp ' - ' - ' - p *----------*-------------- (fv) - pm + // CLEAR[] CLEAR the stack + // 0x22 + function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y + state.stack.length = 0; + } - equation of line b + // SWAP[] SWAP the top two elements on the stack + // 0x23 + function SWAP(state) { + var stack = state.stack; - y - rpdy = pvns * (x- rpdx) + var a = stack.pop(); + var b = stack.pop(); - y = p.y + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } - x = rpdx + ( p.y - rpdy ) / pvns + stack.push(a); + stack.push(b); + } + // DEPTH[] DEPTH of the stack + // 0x24 + function DEPTH(state) { + var stack = state.stack; -case freedom vector == y-axis: ------------------------------- + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } - * pm - |\ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ b - | \ - | \ - | \ .-' (pv) - | 90° \.-' - | .-'* rpd - | .-' - * *-' d - p rp + stack.push(stack.length); + } - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y + // LOOPCALL[] LOOPCALL function + // 0x2A + function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); - equation of line b: - pvns ... normal slope to pv + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } - y - rpdy = pvns * (x - rpdx) + // saves callers program + var cip = state.ip; + var cprog = state.prog; - x = p.x + state.prog = state.funcs[fn]; - y = rpdy + pvns * (p.x - rpdx) + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } + } + // restores the callers program + state.ip = cip; + state.prog = cprog; + } -generic case: -------------- + // CALL[] CALL function + // 0x2B + function CALL(state) { + var fn = state.stack.pop(); + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } - .'(fv) - .' - .* pm - .' ! - .' . - .' ! - .' . b - .' ! - * . - p ! - 90° . ... (pv) - ...-*-''' - ...---''' rpd - ...---''' d - *--''' - rp + // saves callers program + var cip = state.ip; + var cprog = state.prog; - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y + state.prog = state.funcs[fn]; + + // executes the function + exec(state); - equation of line b: - pvns... normal slope to pv + // restores the callers program + state.ip = cip; + state.prog = cprog; - y - rpdy = pvns * (x - rpdx) + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } + } - equation of freedom vector line: - fvs ... slope of freedom vector (=fy/fx) + // CINDEX[] Copy the INDEXed element to the top of the stack + // 0x25 + function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); - y - py = fvs * (x - px) + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); + } - on pm both equations are true for same x/y + // MINDEX[] Move the INDEXed element to the top of the stack + // 0x26 + function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); - y - rpdy = pvns * (x - rpdx) + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } - y - py = fvs * (x - px) + stack.push(stack.splice(stack.length - k, 1)[0]); + } - form to y and set equal: + // FDEF[] Function DEFinition + // 0x2C + function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; - pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + var fn = stack.pop(); + var ipBegin = ip; - expand: + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } - pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + while (prog[++ip] !== 0x2D){ } - switch: + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); + } - fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + // MDAP[a] Move Direct Absolute Point + // 0x2E-0x2F + function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; - solve for x: + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } - fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + var d = pv.distance(p, HPZero); + if (round) { d = state.round(d); } + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); - fvs * px - pvns * rpdx + rpdy - py - x = ----------------------------------- - fvs - pvns + state.rp0 = state.rp1 = pi; + } - and: + // IUP[a] Interpolate Untouched Points through the outline + // 0x30 + function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; - y = fvs * (x - px) + py + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + // if this point has been touched go on + if (v.touched(cp)) { continue; } -INTERPOLATE: -============ + pp = cp.prevTouched(v); -Examples of point interpolation. + // no point on the contour has been touched? + if (pp === cp) { continue; } -The weight of the movement of the reference point gets bigger -the further the other reference point is away, thus the safest -option (that is avoiding 0/0 divisions) is to weight the -original distance of the other point by the sum of both distances. + np = cp.nextTouched(v); -If the sum of both distances is 0, then move the point by the -arithmetic average of the movement of both reference points. + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + } + v.interpolate(cp, pp, np, v); + } + } + // SHP[] SHift Point using reference point + // 0x32-0x33 + function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; - (+6) - rp1o *---->*rp1 - . . (+12) - . . rp2o *---------->* rp2 - . . . . - . . . . - . 10 20 . . - |.........|...................| . - . . . - . . (+8) . - po *------>*p . - . . . - . 12 . 24 . - |...........|.......................| - 36 + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); -------- + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); + } + } + state.loop = 1; + } + // SHC[] SHift Contour using reference point + // 0x36-0x37 + function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; - (+10) - rp1o *-------->*rp1 - . . (-10) - . . rp2 *<---------* rpo2 - . . . . - . . . . - . 10 . 30 . . - |.........|.............................| - . . - . (+5) . - po *--->* p . - . . . - . . 20 . - |....|..............| - 5 15 + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + var d = pv.distance(rp, rp, false, true); -------- + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); + } + // SHZ[] SHift Zone using reference point + // 0x36-0x37 + function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; - (+10) - rp1o *-------->*rp1 - . . - . . - rp2o *-------->*rp2 + var e = stack.pop(); + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } - (+10) - po *-------->* p + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } -------- + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } + } + // SHPIX[] SHift point by a PIXel amount + // 0x38 + function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; - (+10) - rp1o *-------->*rp1 - . . - . .(+30) - rp2o *---------------------------->*rp2 + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); + } - (+25) - po *----------------------->* p + fv.setRelative(p, p, d); + fv.touch(p); + } + state.loop = 1; + } + // IP[] Interpolate Point + // 0x39 + function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; -vim: set ts=4 sw=4 expandtab: -*****/ + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; -/** - * Converts a string into a list of tokens. - */ + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); + } -/** - * Create a new token - * @param {string} char a single char - */ -function Token(char) { - this.char = char; - this.state = {}; - this.activeState = null; -} + fv.interpolate(p, rp1, rp2, pv); -/** - * Create a new context range - * @param {number} startIndex range start index - * @param {number} endOffset range end index offset - * @param {string} contextName owner context name - */ -function ContextRange(startIndex, endOffset, contextName) { - this.contextName = contextName; - this.startIndex = startIndex; - this.endOffset = endOffset; -} + fv.touch(p); + } -/** - * Check context start and end - * @param {string} contextName a unique context name - * @param {function} checkStart a predicate function the indicates a context's start - * @param {function} checkEnd a predicate function the indicates a context's end - */ -function ContextChecker(contextName, checkStart, checkEnd) { - this.contextName = contextName; - this.openRange = null; - this.ranges = []; - this.checkStart = checkStart; - this.checkEnd = checkEnd; -} + state.loop = 1; + } -/** - * @typedef ContextParams - * @type Object - * @property {array} context context items - * @property {number} currentIndex current item index - */ + // MSIRP[a] Move Stack Indirect Relative Point + // 0x3A-0x3B + function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; -/** - * Create a context params - * @param {array} context a list of items - * @param {number} currentIndex current item index - */ -function ContextParams(context, currentIndex) { - this.context = context; - this.index = currentIndex; - this.length = context.length; - this.current = context[currentIndex]; - this.backtrack = context.slice(0, currentIndex); - this.lookahead = context.slice(currentIndex + 1); -} + fv.setRelative(p, rp0, d, pv); + fv.touch(p); -/** - * Create an event instance - * @param {string} eventId event unique id - */ -function Event(eventId) { - this.eventId = eventId; - this.subscribers = []; -} + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } -/** - * Initialize a core events and auto subscribe required event handlers - * @param {any} events an object that enlists core events handlers - */ -function initializeCoreEvents(events) { - var this$1 = this; + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } + } - var coreEvents = [ - 'start', 'end', 'next', 'newToken', 'contextStart', - 'contextEnd', 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' - ]; + // ALIGNRP[] Align to reference point. + // 0x3C + function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; - coreEvents.forEach(function (eventId) { - Object.defineProperty(this$1.events, eventId, { - value: new Event(eventId) - }); - }); + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; - if (!!events) { - coreEvents.forEach(function (eventId) { - var event = events[eventId]; - if (typeof event === 'function') { - this$1.events[eventId].subscribe(event); + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); } - }); + + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + + state.loop = 1; } - var requiresContextUpdate = [ - 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD' - ]; - requiresContextUpdate.forEach(function (eventId) { - this$1.events[eventId].subscribe( - this$1.updateContextsRanges - ); - }); -} -/** - * Converts a string into a list of tokens - * @param {any} events tokenizer core events - */ -function Tokenizer(events) { - this.tokens = []; - this.registeredContexts = {}; - this.contextCheckers = []; - this.events = {}; - this.registeredModifiers = []; + // RTG[] Round To Double Grid + // 0x3D + function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } - initializeCoreEvents.call(this, events); -} + state.round = roundToDoubleGrid; + } -/** - * Sets the state of a token, usually called by a state modifier. - * @param {string} key state item key - * @param {any} value state item value - */ -Token.prototype.setState = function(key, value) { - this.state[key] = value; - this.activeState = { key: key, value: this.state[key] }; - return this.activeState; -}; + // MIAP[a] Move Indirect Absolute Point + // 0x3E-0x3F + function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; -Token.prototype.getState = function (stateId) { - return this.state[stateId] || null; -}; + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); + } -/** - * Checks if an index exists in the tokens list. - * @param {number} index token index - */ -Tokenizer.prototype.inboundIndex = function(index) { - return index >= 0 && index < this.tokens.length; -}; + var d = pv.distance(p, HPZero); -/** - * Compose and apply a list of operations (replace, update, delete) - * @param {array} RUDs replace, update and delete operations - * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) - */ -Tokenizer.prototype.composeRUD = function (RUDs) { - var this$1 = this; - - var silent = true; - var state = RUDs.map(function (RUD) { return ( - this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) - ); }); - var hasFAILObject = function (obj) { return ( - typeof obj === 'object' && - obj.hasOwnProperty('FAIL') - ); }; - if (state.every(hasFAILObject)) { - return { - FAIL: "composeRUD: one or more operations hasn't completed successfully", - report: state.filter(hasFAILObject) - }; - } - this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); -}; + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } -/** - * Replace a range of tokens with a list of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {token} tokens a list of tokens to replace - * @param {boolean} silent dispatch events and update context ranges - */ -Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { - offset = offset !== null ? offset : this.tokens.length; - var isTokenType = tokens.every(function (token) { return token instanceof Token; }); - if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { - var replaced = this.tokens.splice.apply( - this.tokens, [startIndex, offset].concat(tokens) - ); - if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } - return [replaced, tokens]; - } else { - return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; - } -}; + d = state.round(d); + } -/** - * Replace a token with another token - * @param {number} index token index - * @param {token} token a token to replace - * @param {boolean} silent dispatch events and update context ranges - */ -Tokenizer.prototype.replaceToken = function (index, token, silent) { - if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { - var replaced = this.tokens.splice(index, 1, token); - if (!silent) { this.dispatch('replaceToken', [index, token]); } - return [replaced[0], token]; - } else { - return { FAIL: 'replaceToken: invalid token or index.' }; - } -}; + fv.setRelative(p, HPZero, d, pv); -/** - * Removes a range of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {boolean} silent dispatch events and update context ranges - */ -Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { - offset = !isNaN(offset) ? offset : this.tokens.length; - var tokens = this.tokens.splice(startIndex, offset); - if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } - return tokens; -}; + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } -/** - * Remove a token at a certain index - * @param {number} index token index - * @param {boolean} silent dispatch events and update context ranges - */ -Tokenizer.prototype.removeToken = function(index, silent) { - if (!isNaN(index) && this.inboundIndex(index)) { - var token = this.tokens.splice(index, 1); - if (!silent) { this.dispatch('removeToken', [token, index]); } - return token; - } else { - return { FAIL: 'removeToken: invalid token index.' }; + fv.touch(p); + + state.rp0 = state.rp1 = pi; } -}; -/** - * Insert a list of tokens at a certain index - * @param {array} tokens a list of tokens to insert - * @param {number} index insert the list of tokens at index - * @param {boolean} silent dispatch events and update context ranges - */ -Tokenizer.prototype.insertToken = function (tokens, index, silent) { - var tokenType = tokens.every( - function (token) { return token instanceof Token; } - ); - if (tokenType) { - this.tokens.splice.apply( - this.tokens, [index, 0].concat(tokens) - ); - if (!silent) { this.dispatch('insertToken', [tokens, index]); } - return tokens; - } else { - return { FAIL: 'insertToken: invalid token(s).' }; + // NPUSB[] PUSH N Bytes + // 0x40 + function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; } -}; -/** - * A state modifier that is called on 'newToken' event - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a function to update token state - */ -Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { - this.events.newToken.subscribe(function(token, contextParams) { - var conditionParams = [token, contextParams]; - var canApplyModifier = ( - condition === null || - condition.apply(this, conditionParams) === true - ); - var modifierParams = [token, contextParams]; - if (canApplyModifier) { - var newStateValue = modifier.apply(this, modifierParams); - token.setState(modifierId, newStateValue); + // NPUSHW[] PUSH N Words + // 0x41 + function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); } - }); - this.registeredModifiers.push(modifierId); -}; -/** - * Subscribe a handler to an event - * @param {function} eventHandler an event handler function - */ -Event.prototype.subscribe = function (eventHandler) { - if (typeof eventHandler === 'function') { - return ((this.subscribers.push(eventHandler)) - 1); - } else { - return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + state.ip = ip; } -}; -/** - * Unsubscribe an event handler - * @param {string} subsId subscription id - */ -Event.prototype.unsubscribe = function (subsId) { - this.subscribers.splice(subsId, 1); -}; + // WS[] Write Store + // 0x42 + function WS(state) { + var stack = state.stack; + var store = state.store; -/** - * Sets context params current value index - * @param {number} index context params current value index - */ -ContextParams.prototype.setCurrentIndex = function(index) { - this.index = index; - this.current = this.context[index]; - this.backtrack = this.context.slice(0, index); - this.lookahead = this.context.slice(index + 1); -}; + if (!store) { store = state.store = []; } -/** - * Get an item at an offset from the current value - * example (current value is 3): - * 1 2 [3] 4 5 | items values - * -2 -1 0 1 2 | offset values - * @param {number} offset an offset from current value index - */ -ContextParams.prototype.get = function (offset) { - switch (true) { - case (offset === 0): - return this.current; - case (offset < 0 && Math.abs(offset) <= this.backtrack.length): - return this.backtrack.slice(offset)[0]; - case (offset > 0 && offset <= this.lookahead.length): - return this.lookahead[offset - 1]; - default: - return null; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + + store[l] = v; } -}; -/** - * Converts a context range into a string value - * @param {contextRange} range a context range - */ -Tokenizer.prototype.rangeToText = function (range) { - if (range instanceof ContextRange) { - return ( - this.getRangeTokens(range) - .map(function (token) { return token.char; }).join('') - ); - } -}; + // RS[] Read Store + // 0x43 + function RS(state) { + var stack = state.stack; + var store = state.store; -/** - * Converts all tokens into a string - */ -Tokenizer.prototype.getText = function () { - return this.tokens.map(function (token) { return token.char; }).join(''); -}; + var l = stack.pop(); -/** - * Get a context by name - * @param {string} contextName context name to get - */ -Tokenizer.prototype.getContext = function (contextName) { - var context = this.registeredContexts[contextName]; - return !!context ? context : null; -}; + if (exports.DEBUG) { console.log(state.step, 'RS', l); } -/** - * Subscribes a new event handler to an event - * @param {string} eventName event name to subscribe to - * @param {function} eventHandler a function to be invoked on event - */ -Tokenizer.prototype.on = function(eventName, eventHandler) { - var event = this.events[eventName]; - if (!!event) { - return event.subscribe(eventHandler); - } else { - return null; + var v = (store && store[l]) || 0; + + stack.push(v); } -}; -/** - * Dispatches an event - * @param {string} eventName event name - * @param {any} args event handler arguments - */ -Tokenizer.prototype.dispatch = function(eventName, args) { - var this$1 = this; + // WCVTP[] Write Control Value Table in Pixel units + // 0x44 + function WCVTP(state) { + var stack = state.stack; - var event = this.events[eventName]; - if (event instanceof Event) { - event.subscribers.forEach(function (subscriber) { - subscriber.apply(this$1, args || []); - }); + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + + state.cvt[l] = v / 0x40; } -}; -/** - * Register a new context checker - * @param {string} contextName a unique context name - * @param {function} contextStartCheck a predicate function that returns true on context start - * @param {function} contextEndCheck a predicate function that returns true on context end - * TODO: call tokenize on registration to update context ranges with the new context. - */ -Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { - if (!!this.getContext(contextName)) { return { - FAIL: - ("context name '" + contextName + "' is already registered.") - }; } - if (typeof contextStartCheck !== 'function') { return { - FAIL: - "missing context start check." - }; } - if (typeof contextEndCheck !== 'function') { return { - FAIL: - "missing context end check." - }; } - var contextCheckers = new ContextChecker( - contextName, contextStartCheck, contextEndCheck - ); - this.registeredContexts[contextName] = contextCheckers; - this.contextCheckers.push(contextCheckers); - return contextCheckers; -}; + // RCVT[] Read Control Value Table entry + // 0x45 + function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); -/** - * Gets a context range tokens - * @param {contextRange} range a context range - */ -Tokenizer.prototype.getRangeTokens = function(range) { - var endIndex = range.startIndex + range.endOffset; - return [].concat( - this.tokens - .slice(range.startIndex, endIndex) - ); -}; + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } -/** - * Gets the ranges of a context - * @param {string} contextName context name - */ -Tokenizer.prototype.getContextRanges = function(contextName) { - var context = this.getContext(contextName); - if (!!context) { - return context.ranges; - } else { - return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + stack.push(state.cvt[cvte] * 0x40); } -}; -/** - * Resets context ranges to run context update - */ -Tokenizer.prototype.resetContextsRanges = function () { - var registeredContexts = this.registeredContexts; - for (var contextName in registeredContexts) { - if (registeredContexts.hasOwnProperty(contextName)) { - var context = registeredContexts[contextName]; - context.ranges = []; - } - } -}; + // GC[] Get Coordinate projected onto the projection vector + // 0x46-0x47 + function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; -/** - * Updates context ranges - */ -Tokenizer.prototype.updateContextsRanges = function () { - this.resetContextsRanges(); - var chars = this.tokens.map(function (token) { return token.char; }); - for (var i = 0; i < chars.length; i++) { - var contextParams = new ContextParams(chars, i); - this.runContextCheck(contextParams); - } - this.dispatch('updateContextsRanges', [this.registeredContexts]); -}; + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } -/** - * Sets the end offset of an open range - * @param {number} offset range end offset - * @param {string} contextName context name - */ -Tokenizer.prototype.setEndOffset = function (offset, contextName) { - var startIndex = this.getContext(contextName).openRange.startIndex; - var range = new ContextRange(startIndex, offset, contextName); - var ranges = this.getContext(contextName).ranges; - range.rangeId = contextName + "." + (ranges.length); - ranges.push(range); - this.getContext(contextName).openRange = null; - return range; -}; + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); + } -/** - * Runs a context check on the current context - * @param {contextParams} contextParams current context params - */ -Tokenizer.prototype.runContextCheck = function(contextParams) { - var this$1 = this; - - var index = contextParams.index; - this.contextCheckers.forEach(function (contextChecker) { - var contextName = contextChecker.contextName; - var openRange = this$1.getContext(contextName).openRange; - if (!openRange && contextChecker.checkStart(contextParams)) { - openRange = new ContextRange(index, null, contextName); - this$1.getContext(contextName).openRange = openRange; - this$1.dispatch('contextStart', [contextName, index]); - } - if (!!openRange && contextChecker.checkEnd(contextParams)) { - var offset = (index - openRange.startIndex) + 1; - var range = this$1.setEndOffset(offset, contextName); - this$1.dispatch('contextEnd', [contextName, range]); - } - }); -}; + // MD[a] Measure Distance + // 0x49-0x4A + function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); -/** - * Converts a text into a list of tokens - * @param {string} text a text to tokenize - */ -Tokenizer.prototype.tokenize = function (text) { - this.tokens = []; - this.resetContextsRanges(); - var chars = Array.from(text); - this.dispatch('start'); - for (var i = 0; i < chars.length; i++) { - var char = chars[i]; - var contextParams = new ContextParams(chars, i); - this.dispatch('next', [contextParams]); - this.runContextCheck(contextParams); - var token = new Token(char); - this.tokens.push(token); - this.dispatch('newToken', [token, contextParams]); - } - this.dispatch('end', [this.tokens]); - return this.tokens; -}; + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } -// ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ -// ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ -// ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ -// jscs:disable maximumLineLength -/** - * Check if a char is Arabic - * @param {string} c a single char - */ -function isArabicChar(c) { - return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); -} + state.stack.push(Math.round(d * 64)); + } -/** - * Check if a char is an isolated arabic char - * @param {string} c a single char - */ -function isIsolatedArabicChar(char) { - return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); -} + // MPPEM[] Measure Pixels Per EM + // 0x4B + function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); + } -/** - * Check if a char is an Arabic Tashkeel char - * @param {string} c a single char - */ -function isTashkeelArabicChar(char) { - return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); -} + // FLIPON[] set the auto FLIP Boolean to ON + // 0x4D + function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; + } -/** - * Check if a char is Latin - * @param {string} c a single char - */ -function isLatinChar(c) { - return /[A-z]/.test(c); -} + // LT[] Less Than + // 0x50 + function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -/** - * Check if a char is whitespace char - * @param {string} c a single char - */ -function isWhiteSpace(c) { - return /\s/.test(c); -} + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } -/** - * Query a feature by some of it's properties to lookup a glyph substitution. - */ + stack.push(e1 < e2 ? 1 : 0); + } -/** - * Create feature query instance - * @param {Font} font opentype font instance - */ -function FeatureQuery(font) { - this.font = font; - this.features = {}; -} + // LTEQ[] Less Than or EQual + // 0x53 + function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -/** - * @typedef SubstitutionAction - * @type Object - * @property {number} id substitution type - * @property {string} tag feature tag - * @property {any} substitution substitution value(s) - */ + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } -/** - * Create a substitution action instance - * @param {SubstitutionAction} action - */ -function SubstitutionAction(action) { - this.id = action.id; - this.tag = action.tag; - this.substitution = action.substitution; -} + stack.push(e1 <= e2 ? 1 : 0); + } -/** - * Lookup a coverage table - * @param {number} glyphIndex glyph index - * @param {CoverageTable} coverage coverage table - */ -function lookupCoverage(glyphIndex, coverage) { - if (!glyphIndex) { return -1; } - switch (coverage.format) { - case 1: - return coverage.glyphs.indexOf(glyphIndex); + // GTEQ[] Greater Than + // 0x52 + function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - case 2: - var ranges = coverage.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (glyphIndex >= range.start && glyphIndex <= range.end) { - var offset = glyphIndex - range.start; - return range.index + offset; - } - } - break; - default: - return -1; // not found + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + + stack.push(e1 > e2 ? 1 : 0); } - return -1; -} -/** - * Handle a single substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ -function singleSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return glyphIndex + subtable.deltaGlyphId; -} + // GTEQ[] Greater Than or EQual + // 0x53 + function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -/** - * Handle a single substitution - format 2 - * @param {ContextParams} contextParams context params to lookup - */ -function singleSubstitutionFormat2(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.substitute[substituteIndex]; -} + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } -/** - * Lookup a list of coverage tables - * @param {any} coverageList a list of coverage tables - * @param {ContextParams} contextParams context params to lookup - */ -function lookupCoverageList(coverageList, contextParams) { - var lookupList = []; - for (var i = 0; i < coverageList.length; i++) { - var coverage = coverageList[i]; - var glyphIndex = contextParams.current; - glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; - var lookupIndex = lookupCoverage(glyphIndex, coverage); - if (lookupIndex !== -1) { - lookupList.push(lookupIndex); - } + stack.push(e1 >= e2 ? 1 : 0); } - if (lookupList.length !== coverageList.length) { return -1; } - return lookupList; -} -/** - * Handle chaining context substitution - format 3 - * @param {ContextParams} contextParams context params to lookup - */ -function chainingSubstitutionFormat3(contextParams, subtable) { - var lookupsCount = ( - subtable.inputCoverage.length + - subtable.lookaheadCoverage.length + - subtable.backtrackCoverage.length - ); - if (contextParams.context.length < lookupsCount) { return []; } - // INPUT LOOKUP // - var inputLookups = lookupCoverageList( - subtable.inputCoverage, contextParams - ); - if (inputLookups === -1) { return []; } - // LOOKAHEAD LOOKUP // - var lookaheadOffset = subtable.inputCoverage.length - 1; - if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } - var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); - while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { - lookaheadContext.shift(); - } - var lookaheadParams = new ContextParams(lookaheadContext, 0); - var lookaheadLookups = lookupCoverageList( - subtable.lookaheadCoverage, lookaheadParams - ); - // BACKTRACK LOOKUP // - var backtrackContext = [].concat(contextParams.backtrack); - backtrackContext.reverse(); - while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { - backtrackContext.shift(); - } - if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } - var backtrackParams = new ContextParams(backtrackContext, 0); - var backtrackLookups = lookupCoverageList( - subtable.backtrackCoverage, backtrackParams - ); - var contextRulesMatch = ( - inputLookups.length === subtable.inputCoverage.length && - lookaheadLookups.length === subtable.lookaheadCoverage.length && - backtrackLookups.length === subtable.backtrackCoverage.length - ); - var substitutions = []; - if (contextRulesMatch) { - for (var i = 0; i < subtable.lookupRecords.length; i++) { - var lookupRecord = subtable.lookupRecords[i]; - var lookupListIndex = lookupRecord.lookupListIndex; - var lookupTable = this.getLookupByIndex(lookupListIndex); - for (var s = 0; s < lookupTable.subtables.length; s++) { - var subtable$1 = lookupTable.subtables[s]; - var lookup = this.getLookupMethod(lookupTable, subtable$1); - var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); - if (substitutionType === '12') { - for (var n = 0; n < inputLookups.length; n++) { - var glyphIndex = contextParams.get(n); - var substitution = lookup(glyphIndex); - if (substitution) { substitutions.push(substitution); } - } - } - } - } + // EQ[] EQual + // 0x54 + function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); } - return substitutions; -} -/** - * Handle ligature substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ -function ligatureSubstitutionFormat1(contextParams, subtable) { - // COVERAGE LOOKUP // - var glyphIndex = contextParams.current; - var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (ligSetIndex === -1) { return null; } - // COMPONENTS LOOKUP - // (!) note, components are ordered in the written direction. - var ligature; - var ligatureSet = subtable.ligatureSets[ligSetIndex]; - for (var s = 0; s < ligatureSet.length; s++) { - ligature = ligatureSet[s]; - for (var l = 0; l < ligature.components.length; l++) { - var lookaheadItem = contextParams.lookahead[l]; - var component = ligature.components[l]; - if (lookaheadItem !== component) { break; } - if (l === ligature.components.length - 1) { return ligature; } - } - } - return null; -} + // NEQ[] Not EQual + // 0x55 + function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -/** - * Handle decomposition substitution - format 1 - * @param {number} glyphIndex glyph index - * @param {any} subtable subtable - */ -function decompositionSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.sequences[substituteIndex]; -} + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } -/** - * Get default script features indexes - */ -FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { - var scripts = this.font.tables.gsub.scripts; - for (var s = 0; s < scripts.length; s++) { - var script = scripts[s]; - if (script.tag === 'DFLT') { return ( - script.script.defaultLangSys.featureIndexes - ); } + stack.push(e2 !== e1 ? 1 : 0); } - return []; -}; -/** - * Get feature indexes of a specific script - * @param {string} scriptTag script tag - */ -FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { - var tables = this.font.tables; - if (!tables.gsub) { return []; } - if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } - var scripts = this.font.tables.gsub.scripts; - for (var i = 0; i < scripts.length; i++) { - var script = scripts[i]; - if (script.tag === scriptTag && script.script.defaultLangSys) { - return script.script.defaultLangSys.featureIndexes; - } else { - var langSysRecords = script.langSysRecords; - if (!!langSysRecords) { - for (var j = 0; j < langSysRecords.length; j++) { - var langSysRecord = langSysRecords[j]; - if (langSysRecord.tag === scriptTag) { - var langSys = langSysRecord.langSys; - return langSys.featureIndexes; - } - } - } - } - } - return this.getDefaultScriptFeaturesIndexes(); -}; + // ODD[] ODD + // 0x56 + function ODD(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Map a feature tag to a gsub feature - * @param {any} features gsub features - * @param {string} scriptTag script tag - */ -FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { - var tags = {}; - for (var i = 0; i < features.length; i++) { - var tag = features[i].tag; - var feature = features[i].feature; - tags[tag] = feature; - } - this.features[scriptTag].tags = tags; -}; + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } -/** - * Get features of a specific script - * @param {string} scriptTag script tag - */ -FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { - var features = this.features[scriptTag]; - if (this.features.hasOwnProperty(scriptTag)) { return features; } - var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); - if (!featuresIndexes) { return null; } - var gsub = this.font.tables.gsub; - features = featuresIndexes.map(function (index) { return gsub.features[index]; }); - this.features[scriptTag] = features; - this.mapTagsToFeatures(features, scriptTag); - return features; -}; + stack.push(Math.trunc(n) % 2 ? 1 : 0); + } -/** - * Get substitution type - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ -FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { - var lookupType = lookupTable.lookupType.toString(); - var substFormat = subtable.substFormat.toString(); - return lookupType + substFormat; -}; + // EVEN[] EVEN + // 0x57 + function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Get lookup method - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ -FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { - var this$1 = this; - - var substitutionType = this.getSubstitutionType(lookupTable, subtable); - switch (substitutionType) { - case '11': - return function (glyphIndex) { return singleSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - case '12': - return function (glyphIndex) { return singleSubstitutionFormat2.apply( - this$1, [glyphIndex, subtable] - ); }; - case '63': - return function (contextParams) { return chainingSubstitutionFormat3.apply( - this$1, [contextParams, subtable] - ); }; - case '41': - return function (contextParams) { return ligatureSubstitutionFormat1.apply( - this$1, [contextParams, subtable] - ); }; - case '21': - return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - default: - throw new Error( - "lookupType: " + (lookupTable.lookupType) + " - " + - "substFormat: " + (subtable.substFormat) + " " + - "is not yet supported" - ); + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); } -}; -/** - * [ LOOKUP TYPES ] - * ------------------------------- - * Single 1; - * Multiple 2; - * Alternate 3; - * Ligature 4; - * Context 5; - * ChainingContext 6; - * ExtensionSubstitution 7; - * ReverseChainingContext 8; - * ------------------------------- - * - */ + // IF[] IF test + // 0x58 + function IF(state) { + var test = state.stack.pop(); -/** - * @typedef FQuery - * @type Object - * @param {string} tag feature tag - * @param {string} script feature script - * @param {ContextParams} contextParams context params - */ + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } -/** - * Lookup a feature using a query parameters - * @param {FQuery} query feature query - */ -FeatureQuery.prototype.lookupFeature = function (query) { - var contextParams = query.contextParams; - var currentIndex = contextParams.index; - var feature = this.getFeature({ - tag: query.tag, script: query.script - }); - if (!feature) { return new Error( - "font '" + (this.font.names.fullName.en) + "' " + - "doesn't support feature '" + (query.tag) + "' " + - "for script '" + (query.script) + "'." - ); } - var lookups = this.getFeatureLookups(feature); - var substitutions = [].concat(contextParams.context); - for (var l = 0; l < lookups.length; l++) { - var lookupTable = lookups[l]; - var subtables = this.getLookupSubtables(lookupTable); - for (var s = 0; s < subtables.length; s++) { - var subtable = subtables[s]; - var substType = this.getSubstitutionType(lookupTable, subtable); - var lookup = this.getLookupMethod(lookupTable, subtable); - var substitution = (void 0); - switch (substType) { - case '11': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 11, tag: query.tag, substitution: substitution - })); - } - break; - case '12': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 12, tag: query.tag, substitution: substitution - })); - } - break; - case '63': - substitution = lookup(contextParams); - if (Array.isArray(substitution) && substitution.length) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 63, tag: query.tag, substitution: substitution - })); - } - break; - case '41': - substitution = lookup(contextParams); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 41, tag: query.tag, substitution: substitution - })); - } - break; - case '21': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 21, tag: query.tag, substitution: substitution - })); - } - break; - } - contextParams = new ContextParams(substitutions, currentIndex); - if (Array.isArray(substitution) && !substitution.length) { continue; } - substitution = null; + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } } } - return substitutions.length ? substitutions : null; -}; -/** - * Checks if a font supports a specific features - * @param {FQuery} query feature query object - */ -FeatureQuery.prototype.supports = function (query) { - if (!query.script) { return false; } - this.getScriptFeatures(query.script); - var supportedScript = this.features.hasOwnProperty(query.script); - if (!query.tag) { return supportedScript; } - var supportedFeature = ( - this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) - ); - return supportedScript && supportedFeature; -}; + // EIF[] End IF + // 0x59 + function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it -/** - * Get lookup table subtables - * @param {any} lookupTable lookup table - */ -FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { - return lookupTable.subtables || null; -}; + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } -/** - * Get lookup table by index - * @param {number} index lookup table index - */ -FeatureQuery.prototype.getLookupByIndex = function (index) { - var lookups = this.font.tables.gsub.lookups; - return lookups[index] || null; -}; + // AND[] logical AND + // 0x5A + function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -/** - * Get lookup tables for a feature - * @param {string} feature - */ -FeatureQuery.prototype.getFeatureLookups = function (feature) { - // TODO: memoize - return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); -}; + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } -/** - * Query a feature by it's properties - * @param {any} query an object that describes the properties of a query - */ -FeatureQuery.prototype.getFeature = function getFeature(query) { - if (!this.font) { return { FAIL: "No font was found"}; } - if (!this.features.hasOwnProperty(query.script)) { - this.getScriptFeatures(query.script); + stack.push(e2 && e1 ? 1 : 0); } - var scriptFeatures = this.features[query.script]; - if (!scriptFeatures) { return ( - { FAIL: ("No feature for script " + (query.script))} - ); } - if (!scriptFeatures.tags[query.tag]) { return null; } - return this.features[query.script].tags[query.tag]; -}; -/** - * Arabic word context checkers - */ + // OR[] logical OR + // 0x5B + function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -function arabicWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? arabic first char - (prevChar === null && isArabicChar(char)) || - // ? arabic char preceded with a non arabic char - (!isArabicChar(prevChar) && isArabicChar(char)) - ); -} - -function arabicWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last arabic char - (nextChar === null) || - // ? next char is not arabic - (!isArabicChar(nextChar)) - ); -} - -var arabicWordCheck = { - startCheck: arabicWordStartCheck, - endCheck: arabicWordEndCheck -}; + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } -/** - * Arabic sentence context checkers - */ + stack.push(e2 || e1 ? 1 : 0); + } -function arabicSentenceStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? an arabic char preceded with a non arabic char - (isArabicChar(char) || isTashkeelArabicChar(char)) && - !isArabicChar(prevChar) - ); -} - -function arabicSentenceEndCheck(contextParams) { - var nextChar = contextParams.get(1); - switch (true) { - case nextChar === null: - return true; - case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): - var nextIsWhitespace = isWhiteSpace(nextChar); - if (!nextIsWhitespace) { return true; } - if (nextIsWhitespace) { - var arabicCharAhead = false; - arabicCharAhead = ( - contextParams.lookahead.some( - function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } - ) - ); - if (!arabicCharAhead) { return true; } - } - break; - default: - return false; + // NOT[] logical NOT + // 0x5C + function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + + stack.push(e ? 0 : 1); } -} -var arabicSentenceCheck = { - startCheck: arabicSentenceStartCheck, - endCheck: arabicSentenceEndCheck -}; + // DELTAP1[] DELTA exception P1 + // DELTAP2[] DELTA exception P2 + // DELTAP3[] DELTA exception P3 + // 0x5D, 0x71, 0x72 + function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; -/** - * Apply single substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ -function singleSubstitutionFormat1$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); -} + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } -/** - * Apply single substitution format 2 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ -function singleSubstitutionFormat2$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); -} + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } -/** - * Apply chaining context substitution format 3 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ -function chainingSubstitutionFormat3$1(action, tokens, index) { - action.substitution.forEach(function (subst, offset) { - var token = tokens[index + offset]; - token.setState(action.tag, subst); - }); -} + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } -/** - * Apply ligature substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ -function ligatureSubstitutionFormat1$1(action, tokens, index) { - var token = tokens[index]; - token.setState(action.tag, action.substitution.ligGlyph); - var compsCount = action.substitution.components.length; - for (var i = 0; i < compsCount; i++) { - token = tokens[index + i + 1]; - token.setState('deleted', true); + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } } -} -/** - * Supported substitutions - */ -var SUBSTITUTIONS = { - 11: singleSubstitutionFormat1$1, - 12: singleSubstitutionFormat2$1, - 63: chainingSubstitutionFormat3$1, - 41: ligatureSubstitutionFormat1$1 -}; + // SDB[] Set Delta Base in the graphics state + // 0x5E + function SDB(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Apply substitutions to a list of tokens - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ -function applySubstitution(action, tokens, index) { - if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { - SUBSTITUTIONS[action.id](action, tokens, index); + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + + state.deltaBase = n; } -} -/** - * Apply Arabic presentation forms to a range of tokens - */ + // SDS[] Set Delta Shift in the graphics state + // 0x5F + function SDS(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Check if a char can be connected to it's preceding char - * @param {ContextParams} charContextParams context params of a char - */ -function willConnectPrev(charContextParams) { - var backtrack = [].concat(charContextParams.backtrack); - for (var i = backtrack.length - 1; i >= 0; i--) { - var prevChar = backtrack[i]; - var isolated = isIsolatedArabicChar(prevChar); - var tashkeel = isTashkeelArabicChar(prevChar); - if (!isolated && !tashkeel) { return true; } - if (isolated) { return false; } - } - return false; -} + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } -/** - * Check if a char can be connected to it's proceeding char - * @param {ContextParams} charContextParams context params of a char - */ -function willConnectNext(charContextParams) { - if (isIsolatedArabicChar(charContextParams.current)) { return false; } - for (var i = 0; i < charContextParams.lookahead.length; i++) { - var nextChar = charContextParams.lookahead[i]; - var tashkeel = isTashkeelArabicChar(nextChar); - if (!tashkeel) { return true; } + state.deltaShift = Math.pow(0.5, n); } - return false; -} -/** - * Apply arabic presentation forms to a list of tokens - * @param {ContextRange} range a range of tokens - */ -function arabicPresentationForms(range) { - var this$1 = this; - - var script = 'arab'; - var tags = this.featuresTags[script]; - var tokens = this.tokenizer.getRangeTokens(range); - if (tokens.length === 1) { return; } - var contextParams = new ContextParams( - tokens.map(function (token) { return token.getState('glyphIndex'); } - ), 0); - var charContextParams = new ContextParams( - tokens.map(function (token) { return token.char; } - ), 0); - tokens.forEach(function (token, index) { - if (isTashkeelArabicChar(token.char)) { return; } - contextParams.setCurrentIndex(index); - charContextParams.setCurrentIndex(index); - var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) - if (willConnectPrev(charContextParams)) { CONNECT |= 1; } - if (willConnectNext(charContextParams)) { CONNECT |= 2; } - var tag; - switch (CONNECT) { - case 1: (tag = 'fina'); break; - case 2: (tag = 'init'); break; - case 3: (tag = 'medi'); break; - } - if (tags.indexOf(tag) === -1) { return; } - var substitutions = this$1.query.lookupFeature({ - tag: tag, script: script, contextParams: contextParams - }); - if (substitutions instanceof Error) { return console.info(substitutions.message); } - substitutions.forEach(function (action, index) { - if (action instanceof SubstitutionAction) { - applySubstitution(action, tokens, index); - contextParams.context[index] = action.substitution; - } - }); - }); -} + // ADD[] ADD + // 0x60 + function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); -/** - * Apply Arabic required ligatures feature to a range of tokens - */ + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } -/** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ -function getContextParams(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); -} + stack.push(n1 + n2); + } -/** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ -function arabicRequiredLigatures(range) { - var this$1 = this; - - var script = 'arab'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'rlig', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams(tokens); - } - }); -} + // SUB[] SUB + // 0x61 + function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); -/** - * Latin word context checkers - */ + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } -function latinWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? latin first char - (prevChar === null && isLatinChar(char)) || - // ? latin char preceded with a non latin char - (!isLatinChar(prevChar) && isLatinChar(char)) - ); -} - -function latinWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last latin char - (nextChar === null) || - // ? next char is not latin - (!isLatinChar(nextChar)) - ); -} - -var latinWordCheck = { - startCheck: latinWordStartCheck, - endCheck: latinWordEndCheck -}; + stack.push(n1 - n2); + } -/** - * Apply Latin ligature feature to a range of tokens - */ + // DIV[] DIV + // 0x62 + function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); -/** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ -function getContextParams$1(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); -} + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } -/** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ -function latinLigature(range) { - var this$1 = this; - - var script = 'latn'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams$1(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'liga', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams$1(tokens); - } - }); -} + stack.push(n1 * 64 / n2); + } -/** - * Infer bidirectional properties for a given text and apply - * the corresponding layout rules. - */ + // MUL[] MUL + // 0x63 + function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); -/** - * Create Bidi. features - * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' - */ -function Bidi(baseDir) { - this.baseDir = baseDir || 'ltr'; - this.tokenizer = new Tokenizer(); - this.featuresTags = {}; -} + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } -/** - * Sets Bidi text - * @param {string} text a text input - */ -Bidi.prototype.setText = function (text) { - this.text = text; -}; + stack.push(n1 * n2 / 64); + } -/** - * Store essential context checks: - * arabic word check for applying gsub features - * arabic sentence check for adjusting arabic layout - */ -Bidi.prototype.contextChecks = ({ - latinWordCheck: latinWordCheck, - arabicWordCheck: arabicWordCheck, - arabicSentenceCheck: arabicSentenceCheck -}); + // ABS[] ABSolute value + // 0x64 + function ABS(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Register arabic word check - */ -function registerContextChecker(checkId) { - var check = this.contextChecks[(checkId + "Check")]; - return this.tokenizer.registerContextChecker( - checkId, check.startCheck, check.endCheck - ); -} + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } -/** - * Perform pre tokenization procedure then - * tokenize text input - */ -function tokenizeText() { - registerContextChecker.call(this, 'latinWord'); - registerContextChecker.call(this, 'arabicWord'); - registerContextChecker.call(this, 'arabicSentence'); - return this.tokenizer.tokenize(this.text); -} + stack.push(Math.abs(n)); + } -/** - * Reverse arabic sentence layout - * TODO: check base dir before applying adjustments - priority low - */ -function reverseArabicSentences() { - var this$1 = this; - - var ranges = this.tokenizer.getContextRanges('arabicSentence'); - ranges.forEach(function (range) { - var rangeTokens = this$1.tokenizer.getRangeTokens(range); - this$1.tokenizer.replaceRange( - range.startIndex, - range.endOffset, - rangeTokens.reverse() - ); - }); -} + // NEG[] NEGate + // 0x65 + function NEG(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Register supported features tags - * @param {script} script script tag - * @param {Array} tags features tags list - */ -Bidi.prototype.registerFeatures = function (script, tags) { - var this$1 = this; - - var supportedTags = tags.filter( - function (tag) { return this$1.query.supports({script: script, tag: tag}); } - ); - if (!this.featuresTags.hasOwnProperty(script)) { - this.featuresTags[script] = supportedTags; - } else { - this.featuresTags[script] = - this.featuresTags[script].concat(supportedTags); - } -}; + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } -/** - * Apply GSUB features - * @param {Array} tagsList a list of features tags - * @param {string} script a script tag - * @param {Font} font opentype font instance - */ -Bidi.prototype.applyFeatures = function (font, features) { - if (!font) { throw new Error( - 'No valid font was provided to apply features' - ); } - if (!this.query) { this.query = new FeatureQuery(font); } - for (var f = 0; f < features.length; f++) { - var feature = features[f]; - if (!this.query.supports({script: feature.script})) { continue; } - this.registerFeatures(feature.script, feature.tags); + stack.push(-n); } -}; -/** - * Register a state modifier - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a modifier function to set token state - */ -Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { - this.tokenizer.registerModifier(modifierId, condition, modifier); -}; + // FLOOR[] FLOOR + // 0x66 + function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Check if 'glyphIndex' is registered - */ -function checkGlyphIndexStatus() { - if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { - throw new Error( - 'glyphIndex modifier is required to apply ' + - 'arabic presentation features.' - ); + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + + stack.push(Math.floor(n / 0x40) * 0x40); } -} -/** - * Apply arabic presentation forms features - */ -function applyArabicPresentationForms() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicPresentationForms.call(this$1, range); - }); -} + // CEILING[] CEILING + // 0x67 + function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Apply required arabic ligatures - */ -function applyArabicRequireLigatures() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('rlig') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicRequiredLigatures.call(this$1, range); - }); -} + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } -/** - * Apply required arabic ligatures - */ -function applyLatinLigatures() { - var this$1 = this; - - var script = 'latn'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('liga') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('latinWord'); - ranges.forEach(function (range) { - latinLigature.call(this$1, range); - }); -} + stack.push(Math.ceil(n / 0x40) * 0x40); + } -/** - * Check if a context is registered - * @param {string} contextId context id - */ -Bidi.prototype.checkContextReady = function (contextId) { - return !!this.tokenizer.getContext(contextId); -}; + // ROUND[ab] ROUND value + // 0x68-0x6B + function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); -/** - * Apply features to registered contexts - */ -Bidi.prototype.applyFeaturesToContexts = function () { - if (this.checkContextReady('arabicWord')) { - applyArabicPresentationForms.call(this); - applyArabicRequireLigatures.call(this); - } - if (this.checkContextReady('latinWord')) { - applyLatinLigatures.call(this); - } - if (this.checkContextReady('arabicSentence')) { - reverseArabicSentences.call(this); + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + + stack.push(state.round(n / 0x40) * 0x40); } -}; -/** - * process text input - * @param {string} text an input text - */ -Bidi.prototype.processText = function(text) { - if (!this.text || this.text !== text) { - this.setText(text); - tokenizeText.call(this); - this.applyFeaturesToContexts(); + // WCVTF[] Write Control Value Table in Funits + // 0x70 + function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; } -}; -/** - * Process a string of text to identify and adjust - * bidirectional text entities. - * @param {string} text input text - */ -Bidi.prototype.getBidiText = function (text) { - this.processText(text); - return this.tokenizer.getText(); -}; + // DELTAC1[] DELTA exception C1 + // DELTAC2[] DELTA exception C2 + // DELTAC3[] DELTA exception C3 + // 0x73, 0x74, 0x75 + function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; -/** - * Get the current state index of each token - * @param {text} text an input text - */ -Bidi.prototype.getTextGlyphs = function (text) { - this.processText(text); - var indexes = []; - for (var i = 0; i < this.tokenizer.tokens.length; i++) { - var token = this.tokenizer.tokens[i]; - if (token.state.deleted) { continue; } - var index = token.activeState.value; - indexes.push(Array.isArray(index) ? index[0] : index); - } - return indexes; -}; + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } -// The Font object + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } -/** - * @typedef FontOptions - * @type Object - * @property {Boolean} empty - whether to create a new empty font - * @property {string} familyName - * @property {string} styleName - * @property {string=} fullName - * @property {string=} postScriptName - * @property {string=} designer - * @property {string=} designerURL - * @property {string=} manufacturer - * @property {string=} manufacturerURL - * @property {string=} license - * @property {string=} licenseURL - * @property {string=} version - * @property {string=} description - * @property {string=} copyright - * @property {string=} trademark - * @property {Number} unitsPerEm - * @property {Number} ascender - * @property {Number} descender - * @property {Number} createdTimestamp - * @property {string=} weightClass - * @property {string=} widthClass - * @property {string=} fsSelection - */ + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } -/** - * A Font represents a loaded OpenType font file. - * It contains a set of glyphs and methods to draw text on a drawing context, - * or to get a path representing the text. - * @exports opentype.Font - * @class - * @param {FontOptions} - * @constructor - */ -function Font(options) { - options = options || {}; - options.tables = options.tables || {}; - - if (!options.empty) { - // Check that we've provided the minimum set of names. - checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); - checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); - checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); - checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); - checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); - - // OS X will complain if the names are empty, so we put a single space everywhere by default. - this.names = { - fontFamily: {en: options.familyName || ' '}, - fontSubfamily: {en: options.styleName || ' '}, - fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, - // postScriptName may not contain any whitespace - postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, - designer: {en: options.designer || ' '}, - designerURL: {en: options.designerURL || ' '}, - manufacturer: {en: options.manufacturer || ' '}, - manufacturerURL: {en: options.manufacturerURL || ' '}, - license: {en: options.license || ' '}, - licenseURL: {en: options.licenseURL || ' '}, - version: {en: options.version || 'Version 0.1'}, - description: {en: options.description || ' '}, - copyright: {en: options.copyright || ' '}, - trademark: {en: options.trademark || ' '} - }; - this.unitsPerEm = options.unitsPerEm || 1000; - this.ascender = options.ascender; - this.descender = options.descender; - this.createdTimestamp = options.createdTimestamp; - this.tables = Object.assign(options.tables, { - os2: Object.assign({ - usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, - usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, - fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, - }, options.tables.os2) - }); + var delta = mag * ds; + + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + + state.cvt[c] += delta; + } } - this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. - this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); - this.encoding = new DefaultEncoding(this); - this.position = new Position(this); - this.substitution = new Substitution(this); - this.tables = this.tables || {}; + // SROUND[] Super ROUND + // 0x76 + function SROUND(state) { + var n = state.stack.pop(); - // needed for low memory mode only. - this._push = null; - this._hmtxTableData = {}; + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } - Object.defineProperty(this, 'hinting', { - get: function() { - if (this._hinting) { return this._hinting; } - if (this.outlinesFormat === 'truetype') { - return (this._hinting = new Hinting(this)); - } + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); } - }); -} -/** - * Check if the font has a glyph for the given character. - * @param {string} - * @return {Boolean} - */ -Font.prototype.hasChar = function(c) { - return this.encoding.charToGlyphIndex(c) !== null; -}; + state.srPeriod = period; -/** - * Convert the given character to a single glyph index. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {Number} - */ -Font.prototype.charToGlyphIndex = function(s) { - return this.encoding.charToGlyphIndex(s); -}; + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } -/** - * Convert the given character to a single Glyph object. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {opentype.Glyph} - */ -Font.prototype.charToGlyph = function(c) { - var glyphIndex = this.charToGlyphIndex(c); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } } - return glyph; -}; + // S45ROUND[] Super ROUND 45 degrees + // 0x77 + function S45ROUND(state) { + var n = state.stack.pop(); -/** - * Update features - * @param {any} options features options - */ -Font.prototype.updateFeatures = function (options) { - // TODO: update all features options not only 'latn'. - return this.defaultRenderOptions.features.map(function (feature) { - if (feature.script === 'latn') { - return { - script: 'latn', - tags: feature.tags.filter(function (tag) { return options[tag]; }) - }; - } else { - return feature; + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); } - }); -}; -/** - * Convert the given text to a list of Glyph objects. - * Note that there is no strict one-to-one mapping between characters and - * glyphs, so the list of returned glyphs can be larger or smaller than the - * length of the given string. - * @param {string} - * @param {GlyphRenderOptions} [options] - * @return {opentype.Glyph[]} - */ -Font.prototype.stringToGlyphs = function(s, options) { - var this$1 = this; + state.srPeriod = period; + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } - var bidi = new Bidi(); + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } + } - // Create and register 'glyphIndex' state modifier - var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; - bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + // ROFF[] Round Off + // 0x7A + function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } - // roll-back to default features - var features = options ? - this.updateFeatures(options.features) : - this.defaultRenderOptions.features; + state.round = roundOff; + } - bidi.applyFeatures(this, features); + // RUTG[] Round Up To Grid + // 0x7C + function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } - var indexes = bidi.getTextGlyphs(s); + state.round = roundUpToGrid; + } - var length = indexes.length; + // RDTG[] Round Down To Grid + // 0x7D + function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } - // convert glyph indexes to glyph objects - var glyphs = new Array(length); - var notdef = this.glyphs.get(0); - for (var i = 0; i < length; i += 1) { - glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + state.round = roundDownToGrid; } - return glyphs; -}; -/** - * @param {string} - * @return {Number} - */ -Font.prototype.nameToGlyphIndex = function(name) { - return this.glyphNames.nameToGlyphIndex(name); -}; + // SCANCTRL[] SCAN conversion ConTRoL + // 0x85 + function SCANCTRL(state) { + var n = state.stack.pop(); -/** - * @param {string} - * @return {opentype.Glyph} - */ -Font.prototype.nameToGlyph = function(name) { - var glyphIndex = this.nameToGlyphIndex(name); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); + // ignored by opentype.js + + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } } - return glyph; -}; + // SDPVTL[a] Set Dual Projection Vector To Line + // 0x86-0x87 + function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; -/** - * @param {Number} - * @return {String} - */ -Font.prototype.glyphIndexToName = function(gid) { - if (!this.glyphNames.glyphIndexToName) { - return ''; + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.dpv = getUnitVector(dx, dy); } - return this.glyphNames.glyphIndexToName(gid); -}; + // GETINFO[] GET INFOrmation + // 0x88 + function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; -/** - * Retrieve the value of the kerning pair between the left glyph (or its index) - * and the right glyph (or its index). If no kerning pair is found, return 0. - * The kerning value gets added to the advance width when calculating the spacing - * between glyphs. - * For GPOS kerning, this method uses the default script and language, which covers - * most use cases. To have greater control, use font.position.getKerningValue . - * @param {opentype.Glyph} leftGlyph - * @param {opentype.Glyph} rightGlyph - * @return {Number} - */ -Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { - leftGlyph = leftGlyph.index || leftGlyph; - rightGlyph = rightGlyph.index || rightGlyph; - var gposKerning = this.position.defaultKerningTables; - if (gposKerning) { - return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); - } - // "kern" table - return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; -}; + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } -/** - * @typedef GlyphRenderOptions - * @type Object - * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. - * See https://www.microsoft.com/typography/otspec/scripttags.htm - * @property {string} [language='dflt'] - language system used to determine which features to apply. - * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx - * @property {boolean} [kerning=true] - whether to include kerning values - * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. - * See https://www.microsoft.com/typography/otspec/featuretags.htm - */ -Font.prototype.defaultRenderOptions = { - kerning: true, - features: [ - /** - * these 4 features are required to render Arabic text properly - * and shouldn't be turned off when rendering arabic text. - */ - { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, - { script: 'latn', tags: ['liga', 'rlig'] } - ] -}; + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } -/** - * Helper function that invokes the given callback for each glyph in the given text. - * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text - * @param {string} text - The text to apply. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @param {Function} callback - */ -Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - options = Object.assign({}, this.defaultRenderOptions, options); - var fontScale = 1 / this.unitsPerEm * fontSize; - var glyphs = this.stringToGlyphs(text, options); - var kerningLookups; - if (options.kerning) { - var script = options.script || this.position.getDefaultScriptName(); - kerningLookups = this.position.getKerningTables(script, options.language); - } - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs[i]; - callback.call(this, glyph, x, y, fontSize, options); - if (glyph.advanceWidth) { - x += glyph.advanceWidth * fontScale; - } - - if (options.kerning && i < glyphs.length - 1) { - // We should apply position adjustment lookups in a more generic way. - // Here we only use the xAdvance value. - var kerningValue = kerningLookups ? - this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : - this.getKerningValue(glyph, glyphs[i + 1]); - x += kerningValue * fontScale; - } - - if (options.letterSpacing) { - x += options.letterSpacing * fontSize; - } else if (options.tracking) { - x += (options.tracking / 1000) * fontSize; - } - } - return x; -}; + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. -/** - * Create a Path object that represents the given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path} - */ -Font.prototype.getPath = function(text, x, y, fontSize, options) { - var fullPath = new Path(); - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - fullPath.extend(glyphPath); - }); - return fullPath; -}; + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } -/** - * Create an array of Path objects that represent the glyphs of a given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path[]} - */ -Font.prototype.getPaths = function(text, x, y, fontSize, options) { - var glyphPaths = []; - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - glyphPaths.push(glyphPath); - }); + stack.push(r); + } - return glyphPaths; -}; + // ROLL[] ROLL the top three stack elements + // 0x8A + function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); -/** - * Returns the advance width of a text. - * - * This is something different than Path.getBoundingBox() as for example a - * suffixed whitespace increases the advanceWidth but not the bounding box - * or an overhanging letter like a calligraphic 'f' might have a quite larger - * bounding box than its advance width. - * - * This corresponds to canvas2dContext.measureText(text).width - * - * @param {string} text - The text to create. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return advance width - */ -Font.prototype.getAdvanceWidth = function(text, fontSize, options) { - return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); -}; + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } -/** - * Draw the text on the given drawing context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ -Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { - this.getPath(text, x, y, fontSize, options).draw(ctx); -}; + stack.push(b); + stack.push(a); + stack.push(c); + } -/** - * Draw the points of all glyphs in the text. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ -Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawPoints(ctx, gX, gY, gFontSize); - }); -}; + // MAX[] MAXimum of top two stack elements + // 0x8B + function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); -/** - * Draw lines indicating important font measurements for all glyphs in the text. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ -Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawMetrics(ctx, gX, gY, gFontSize); - }); -}; + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } -/** - * @param {string} - * @return {string} - */ -Font.prototype.getEnglishName = function(name) { - var translations = this.names[name]; - if (translations) { - return translations.en; + stack.push(Math.max(e1, e2)); } -}; -/** - * Validate - */ -Font.prototype.validate = function() { - var _this = this; + // MIN[] MINimum of top two stack elements + // 0x8C + function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } - function assert(predicate, message) { + stack.push(Math.min(e1, e2)); } - function assertNamePresent(name) { - var englishName = _this.getEnglishName(name); - assert(englishName && englishName.trim().length > 0); + // SCANTYPE[] SCANTYPE + // 0x8D + function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } } - // Identification information - assertNamePresent('fontFamily'); - assertNamePresent('weightName'); - assertNamePresent('manufacturer'); - assertNamePresent('copyright'); - assertNamePresent('version'); + // INSTCTRL[] INSTCTRL + // 0x8D + function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); - // Dimension information - assert(this.unitsPerEm > 0); -}; + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } -/** - * Convert the font object to a SFNT data structure. - * This structure contains all the necessary tables and metadata to create a binary OTF file. - * @return {opentype.Table} - */ -Font.prototype.toTables = function() { - return sfnt.fontToTable(this); -}; -/** - * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. - */ -Font.prototype.toBuffer = function() { - console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); - return this.toArrayBuffer(); -}; -/** - * Converts a `opentype.Font` into an `ArrayBuffer` - * @return {ArrayBuffer} - */ -Font.prototype.toArrayBuffer = function() { - var sfntTable = this.toTables(); - var bytes = sfntTable.encode(); - var buffer = new ArrayBuffer(bytes.length); - var intArray = new Uint8Array(buffer); - for (var i = 0; i < bytes.length; i++) { - intArray[i] = bytes[i]; + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } } - return buffer; -}; + // PUSHB[abc] PUSH Bytes + // 0xB0-0xB7 + function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; -/** - * Initiate a download of the OpenType font. - */ -Font.prototype.download = function(fileName) { - var familyName = this.getEnglishName('fontFamily'); - var styleName = this.getEnglishName('fontSubfamily'); - fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; - var arrayBuffer = this.toArrayBuffer(); - - if (isBrowser()) { - window.URL = window.URL || window.webkitURL; - - if (window.URL) { - var dataView = new DataView(arrayBuffer); - var blob = new Blob([dataView], {type: 'font/opentype'}); - - var link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = fileName; - - var event = document.createEvent('MouseEvents'); - event.initEvent('click', true, false); - link.dispatchEvent(event); - } else { - console.warn('Font file could not be downloaded. Try using a different browser.'); + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; + } + + // PUSHW[abc] PUSH Words + // 0xB8-0xBF + function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); } - } else { - var fs = require('fs'); - var buffer = arrayBufferToNodeBuffer(arrayBuffer); - fs.writeFileSync(fileName, buffer); + + state.ip = ip; } -}; -/** - * @private - */ -Font.prototype.fsSelectionValues = { - ITALIC: 0x001, //1 - UNDERSCORE: 0x002, //2 - NEGATIVE: 0x004, //4 - OUTLINED: 0x008, //8 - STRIKEOUT: 0x010, //16 - BOLD: 0x020, //32 - REGULAR: 0x040, //64 - USER_TYPO_METRICS: 0x080, //128 - WWS: 0x100, //256 - OBLIQUE: 0x200 //512 -}; -/** - * @private - */ -Font.prototype.usWidthClasses = { - ULTRA_CONDENSED: 1, - EXTRA_CONDENSED: 2, - CONDENSED: 3, - SEMI_CONDENSED: 4, - MEDIUM: 5, - SEMI_EXPANDED: 6, - EXPANDED: 7, - EXTRA_EXPANDED: 8, - ULTRA_EXPANDED: 9 -}; + // MDRP[abcde] Move Direct Relative Point + // 0xD0-0xEF + // (if indirect is 0) + // + // and + // + // MIRP[abcde] Move Indirect Relative Point + // 0xE0-0xFF + // (if indirect is 1) + + function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); + } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } + } + + /* + * The instruction table. + */ + instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) + ]; + + /***************************** + Mathematical Considerations + ****************************** + + fv ... refers to freedom vector + pv ... refers to projection vector + rp ... refers to reference point + p ... refers to to point being operated on + d ... refers to distance + + SETRELATIVE: + ============ + + case freedom vector == x-axis: + ------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b + + y - rpdy = pvns * (x- rpdx) + + y = p.y + + x = rpdx + ( p.y - rpdy ) / pvns + + + case freedom vector == y-axis: + ------------------------------ + + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns ... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + x = p.x + + y = rpdy + pvns * (p.x - rpdx) + + + + generic case: + ------------- + + + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) + + y - py = fvs * (x - px) + + + on pm both equations are true for same x/y + + y - rpdy = pvns * (x - rpdx) + + y - py = fvs * (x - px) + + form to y and set equal: + + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + + expand: + + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + + switch: + + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + + solve for x: + + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + + + + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns + + and: + + y = fvs * (x - px) + py + + + + INTERPOLATE: + ============ + + Examples of point interpolation. + + The weight of the movement of the reference point gets bigger + the further the other reference point is away, thus the safest + option (that is avoiding 0/0 divisions) is to weight the + original distance of the other point by the sum of both distances. + + If the sum of both distances is 0, then move the point by the + arithmetic average of the movement of both reference points. + + + + + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 + + + ------- + + + + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 + + + ------- + + + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 + + + (+10) + po *-------->* p + + ------- + + + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 + + + (+25) + po *----------------------->* p + + + + vim: set ts=4 sw=4 expandtab: + *****/ + + /** + * Converts a string into a list of tokens. + */ + + /** + * Create a new token + * @param {string} char a single char + */ + function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; + } + + /** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ + function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; + } + + /** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ + function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; + } + + /** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ + + /** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ + function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); + } + + /** + * Create an event instance + * @param {string} eventId event unique id + */ + function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; + } + + /** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ + function initializeCoreEvents(events) { + var this$1 = this; + + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; + + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); + } + + /** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ + function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + + initializeCoreEvents.call(this, events); + } + + /** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ + Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; + }; + + Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; + }; + + /** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ + Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; + }; + + /** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ + Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); + }; + + /** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; + } + }; + + /** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } + }; + + /** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; + }; + + /** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } + }; + + /** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } + }; + + /** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ + Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); + }; + + /** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ + Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + } + }; + + /** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ + Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); + }; + + /** + * Sets context params current value index + * @param {number} index context params current value index + */ + ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); + }; + + /** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ + ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } + }; + + /** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ + Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); + } + }; + + /** + * Converts all tokens into a string + */ + Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); + }; + + /** + * Get a context by name + * @param {string} contextName context name to get + */ + Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; + }; + + /** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ + Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } + }; + + /** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ + Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); + } + }; + + /** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ + Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; + }; + + /** + * Gets a context range tokens + * @param {contextRange} range a context range + */ + Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); + }; + + /** + * Gets the ranges of a context + * @param {string} contextName context name + */ + Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } + }; + + /** + * Resets context ranges to run context update + */ + Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } + }; + + /** + * Updates context ranges + */ + Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); + }; + + /** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ + Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; + }; + + /** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ + Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); + }; + + /** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ + Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; + }; + + // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ + // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ + // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ + // jscs:disable maximumLineLength + /** + * Check if a char is Arabic + * @param {string} c a single char + */ + function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); + } + + /** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ + function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); + } + + /** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ + function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); + } + + /** + * Check if a char is Latin + * @param {string} c a single char + */ + function isLatinChar(c) { + return /[A-z]/.test(c); + } + + /** + * Check if a char is whitespace char + * @param {string} c a single char + */ + function isWhiteSpace(c) { + return /\s/.test(c); + } + + /** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ + + /** + * Create feature query instance + * @param {Font} font opentype font instance + */ + function FeatureQuery(font) { + this.font = font; + this.features = {}; + } + + /** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ + + /** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ + function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; + } + + /** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ + function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; // not found + } + return -1; + } + + /** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ + function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; + } + + /** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ + function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; + } + + /** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ + function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; + } + + /** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ + function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } + } + } + } + return substitutions; + } + + /** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ + function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; + } + + /** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ + function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; + } + + /** + * Get default script features indexes + */ + FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } + } + return []; + }; + + /** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); + }; + + /** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; + }; + + /** + * Get features of a specific script + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; + }; + + /** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ + FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; + }; + + /** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ + FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); + } + }; + + /** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ + + /** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ + + /** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ + FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; + } + } + return substitutions.length ? substitutions : null; + }; + + /** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ + FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; + }; + + /** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ + FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; + }; + + /** + * Get lookup table by index + * @param {number} index lookup table index + */ + FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; + }; + + /** + * Get lookup tables for a feature + * @param {string} feature + */ + FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); + }; + + /** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ + FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; + }; + + /** + * Arabic word context checkers + */ + + function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); + } + + function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); + } + + var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck + }; + + /** + * Arabic sentence context checkers + */ + + function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); + } + + function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; + } + } + + var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck + }; + + /** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + + /** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + + /** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); + } + + /** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); + } + } + + /** + * Supported substitutions + */ + var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 + }; + + /** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } + } + + /** + * Apply Arabic presentation forms to a range of tokens + */ + + /** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ + function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; + } + + /** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ + function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; + } + + /** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ + function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); + } + + /** + * Apply Arabic required ligatures feature to a range of tokens + */ + + /** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ + function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); + } + + /** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ + function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); + } + }); + } + + /** + * Latin word context checkers + */ + + function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); + } + + function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); + } + + var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck + }; + + /** + * Apply Latin ligature feature to a range of tokens + */ + + /** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ + function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); + } + + /** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ + function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); + } + }); + } + + /** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ + + /** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ + function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; + } + + /** + * Sets Bidi text + * @param {string} text a text input + */ + Bidi.prototype.setText = function (text) { + this.text = text; + }; + + /** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ + Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck + }); + + /** + * Register arabic word check + */ + function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); + } + + /** + * Perform pre tokenization procedure then + * tokenize text input + */ + function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); + } + + /** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ + function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); + } + + /** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ + Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); + } + }; + + /** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ + Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); + } + }; + + /** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ + Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); + }; + + /** + * Check if 'glyphIndex' is registered + */ + function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); + } + } + + /** + * Apply arabic presentation forms features + */ + function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); + } + + /** + * Apply required arabic ligatures + */ + function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); + } + + /** + * Apply required arabic ligatures + */ + function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); + } + + /** + * Check if a context is registered + * @param {string} contextId context id + */ + Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); + }; + + /** + * Apply features to registered contexts + */ + Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); + } + }; + + /** + * process text input + * @param {string} text an input text + */ + Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } + }; + + /** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ + Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); + }; + + /** + * Get the current state index of each token + * @param {text} text an input text + */ + Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; + }; + + // The Font object + + /** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ + + /** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ + function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); + } + + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; + + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } + } + }); + } + + /** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ + Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; + }; + + /** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ + Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); + }; + + /** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ + Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; + }; + + /** + * Update features + * @param {any} options features options + */ + Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; + } + }); + }; + + /** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ + Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + + + var bidi = new Bidi(); + + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; + + bidi.applyFeatures(this, features); + + var indexes = bidi.getTextGlyphs(s); + + var length = indexes.length; + + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + } + return glyphs; + }; + + /** + * @param {string} + * @return {Number} + */ + Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); + }; + + /** + * @param {string} + * @return {opentype.Glyph} + */ + Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; + }; + + /** + * @param {Number} + * @return {String} + */ + Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); + }; + + /** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ + Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; + }; + + /** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ + Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] + }; + + /** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ + Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; + }; + + /** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ + Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; + }; + + /** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ + Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); + + return glyphPaths; + }; + + /** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ + Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); + }; + + /** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); + }; + + /** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); + }; + + /** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); + }; + + /** + * @param {string} + * @return {string} + */ + Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; + } + }; + + /** + * Validate + */ + Font.prototype.validate = function() { + var _this = this; + + function assert(predicate, message) { + } + + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); + } + + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); + + // Dimension information + assert(this.unitsPerEm > 0); + }; + + /** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ + Font.prototype.toTables = function() { + return sfnt.fontToTable(this); + }; + /** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ + Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); + }; + /** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ + Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; + }; + + /** + * Initiate a download of the OpenType font. + */ + Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); + } else { + console.warn('Font file could not be downloaded. Try using a different browser.'); + } + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); + } + }; + /** + * @private + */ + Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 + }; + + /** + * @private + */ + Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 + }; + + /** + * @private + */ + Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 + }; -/** - * @private - */ -Font.prototype.usWeightClasses = { - THIN: 100, - EXTRA_LIGHT: 200, - LIGHT: 300, - NORMAL: 400, - MEDIUM: 500, - SEMI_BOLD: 600, - BOLD: 700, - EXTRA_BOLD: 800, - BLACK: 900 -}; + // The `fvar` table stores font variation axes and instances. -// The `fvar` table stores font variation axes and instances. + function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } -function addName(name, names) { - var nameString = JSON.stringify(name); - var nameID = 256; - for (var nameKey in names) { - var n = parseInt(nameKey); - if (!n || n < 256) { - continue; - } + if (JSON.stringify(names[nameKey]) === nameString) { + return n; + } - if (JSON.stringify(names[nameKey]) === nameString) { - return n; + if (nameID <= n) { + nameID = n + 1; + } } - if (nameID <= n) { - nameID = n + 1; - } + names[nameID] = name; + return nameID; } - names[nameID] = name; - return nameID; -} - -function makeFvarAxis(n, axis, names) { - var nameID = addName(axis.name, names); - return [ - {name: 'tag_' + n, type: 'TAG', value: axis.tag}, - {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, - {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, - {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, - {name: 'flags_' + n, type: 'USHORT', value: 0}, - {name: 'nameID_' + n, type: 'USHORT', value: nameID} - ]; -} - -function parseFvarAxis(data, start, names) { - var axis = {}; - var p = new parse.Parser(data, start); - axis.tag = p.parseTag(); - axis.minValue = p.parseFixed(); - axis.defaultValue = p.parseFixed(); - axis.maxValue = p.parseFixed(); - p.skip('uShort', 1); // reserved for flags; no values defined - axis.name = names[p.parseUShort()] || {}; - return axis; -} - -function makeFvarInstance(n, inst, axes, names) { - var nameID = addName(inst.name, names); - var fields = [ - {name: 'nameID_' + n, type: 'USHORT', value: nameID}, - {name: 'flags_' + n, type: 'USHORT', value: 0} - ]; + function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; + } - for (var i = 0; i < axes.length; ++i) { - var axisTag = axes[i].tag; - fields.push({ - name: 'axis_' + n + ' ' + axisTag, - type: 'FIXED', - value: inst.coordinates[axisTag] << 16 - }); + function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; } - return fields; -} + function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; -function parseFvarInstance(data, start, axes, names) { - var inst = {}; - var p = new parse.Parser(data, start); - inst.name = names[p.parseUShort()] || {}; - p.skip('uShort', 1); // reserved for flags; no values defined + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); + } - inst.coordinates = {}; - for (var i = 0; i < axes.length; ++i) { - inst.coordinates[axes[i].tag] = p.parseFixed(); + return fields; } - return inst; -} + function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined -function makeFvarTable(fvar, names) { - var result = new table.Table('fvar', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'offsetToData', type: 'USHORT', value: 0}, - {name: 'countSizePairs', type: 'USHORT', value: 2}, - {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, - {name: 'axisSize', type: 'USHORT', value: 20}, - {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, - {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} - ]); - result.offsetToData = result.sizeOf(); + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); + } - for (var i = 0; i < fvar.axes.length; i++) { - result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + return inst; } - for (var j = 0; j < fvar.instances.length; j++) { - result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); - } + function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); - return result; -} + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + } -function parseFvarTable(data, start, names) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); - var offsetToData = p.parseOffset16(); - // Skip countSizePairs. - p.skip('uShort', 1); - var axisCount = p.parseUShort(); - var axisSize = p.parseUShort(); - var instanceCount = p.parseUShort(); - var instanceSize = p.parseUShort(); + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } - var axes = []; - for (var i = 0; i < axisCount; i++) { - axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + return result; } - var instances = []; - var instanceStart = start + offsetToData + axisCount * axisSize; - for (var j = 0; j < instanceCount; j++) { - instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); - } + function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); - return {axes: axes, instances: instances}; -} + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + } -var fvar = { make: makeFvarTable, parse: parseFvarTable }; + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } -// The `GDEF` table contains various glyph properties + return {axes: axes, instances: instances}; + } -var attachList = function() { - return { - coverage: this.parsePointer(Parser.coverage), - attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) - }; -}; + var fvar = { make: makeFvarTable, parse: parseFvarTable }; -var caretValue = function() { - var format = this.parseUShort(); - check.argument(format === 1 || format === 2 || format === 3, - 'Unsupported CaretValue table version.'); - if (format === 1) { - return { coordinate: this.parseShort() }; - } else if (format === 2) { - return { pointindex: this.parseShort() }; - } else if (format === 3) { - // Device / Variation Index tables unsupported - return { coordinate: this.parseShort() }; - } -}; + // The `GDEF` table contains various glyph properties -var ligGlyph = function() { - return this.parseList(Parser.pointer(caretValue)); -}; + var attachList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) + }; + }; -var ligCaretList = function() { - return { - coverage: this.parsePointer(Parser.coverage), - ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) + var caretValue = function() { + var format = this.parseUShort(); + check.argument(format === 1 || format === 2 || format === 3, + 'Unsupported CaretValue table version.'); + if (format === 1) { + return { coordinate: this.parseShort() }; + } else if (format === 2) { + return { pointindex: this.parseShort() }; + } else if (format === 3) { + // Device / Variation Index tables unsupported + return { coordinate: this.parseShort() }; + } }; -}; -var markGlyphSets = function() { - this.parseUShort(); // Version - return this.parseList(Parser.pointer(Parser.coverage)); -}; + var ligGlyph = function() { + return this.parseList(Parser.pointer(caretValue)); + }; -function parseGDEFTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, - 'Unsupported GDEF table version.'); - var gdef = { - version: tableVersion, - classDef: p.parsePointer(Parser.classDef), - attachList: p.parsePointer(attachList), - ligCaretList: p.parsePointer(ligCaretList), - markAttachClassDef: p.parsePointer(Parser.classDef) - }; - if (tableVersion >= 1.2) { - gdef.markGlyphSets = p.parsePointer(markGlyphSets); - } - return gdef; -} -var gdef = { parse: parseGDEFTable }; - -// The `GPOS` table contains kerning pairs, among other things. - -var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused - -// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable -// this = Parser instance -subtableParsers$1[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var posformat = this.parseUShort(); - if (posformat === 1) { + var ligCaretList = function() { return { - posFormat: 1, coverage: this.parsePointer(Parser.coverage), - value: this.parseValueRecord() + ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) }; - } else if (posformat === 2) { - return { - posFormat: 2, - coverage: this.parsePointer(Parser.coverage), - values: this.parseValueRecordList() + }; + + var markGlyphSets = function() { + this.parseUShort(); // Version + return this.parseList(Parser.pointer(Parser.coverage)); + }; + + function parseGDEFTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, + 'Unsupported GDEF table version.'); + var gdef = { + version: tableVersion, + classDef: p.parsePointer(Parser.classDef), + attachList: p.parsePointer(attachList), + ligCaretList: p.parsePointer(ligCaretList), + markAttachClassDef: p.parsePointer(Parser.classDef) }; + if (tableVersion >= 1.2) { + gdef.markGlyphSets = p.parsePointer(markGlyphSets); + } + return gdef; } - check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); -}; + var gdef = { parse: parseGDEFTable }; + + // The `GPOS` table contains kerning pairs, among other things. + + var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable + // this = Parser instance + subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); + }; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable + subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; + } + }; + + subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; + subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; + subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; + subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; + subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; + subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; + subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } -// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable -subtableParsers$1[2] = function parseLookup2() { - var start = this.offset + this.relativeOffset; - var posFormat = this.parseUShort(); - check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); - var coverage = this.parsePointer(Parser.coverage); - var valueFormat1 = this.parseUShort(); - var valueFormat2 = this.parseUShort(); - if (posFormat === 1) { - // Adjustments for Glyph Pairs - return { - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - pairSets: this.parseList(Parser.pointer(Parser.list(function() { - return { // pairValueRecord - secondGlyph: this.parseUShort(), - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - }))) - }; - } else if (posFormat === 2) { - var classDef1 = this.parsePointer(Parser.classDef); - var classDef2 = this.parsePointer(Parser.classDef); - var class1Count = this.parseUShort(); - var class2Count = this.parseUShort(); - return { - // Class Pair Adjustment - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - classDef1: classDef1, - classDef2: classDef2, - class1Count: class1Count, - class2Count: class2Count, - classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { - return { - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - })) - }; } -}; -subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; -subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; -subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; -subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; -subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; -subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; -subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; - -// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos -function parseGposTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + // GPOS Writing ////////////////////////////////////////////// + // NOT SUPPORTED + var subtableMakers$1 = new Array(10); - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1), - variations: p.parseFeatureVariationsList() - }; + function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); } -} + var gpos = { parse: parseGposTable, make: makeGposTable }; -// GPOS Writing ////////////////////////////////////////////// -// NOT SUPPORTED -var subtableMakers$1 = new Array(10); + // The `kern` table contains kerning pairs. -function makeGposTable(gpos) { - return new table.Table('GPOS', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} - ]); -} - -var gpos = { parse: parseGposTable, make: makeGposTable }; - -// The `kern` table contains kerning pairs. - -function parseWindowsKernTable(p) { - var pairs = {}; - // Skip nTables. - p.skip('uShort'); - var subtableVersion = p.parseUShort(); - check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); - // Skip subtableLength, subtableCoverage - p.skip('uShort', 2); - var nPairs = p.parseUShort(); - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - for (var i = 0; i < nPairs; i += 1) { - var leftIndex = p.parseUShort(); - var rightIndex = p.parseUShort(); - var value = p.parseShort(); - pairs[leftIndex + ',' + rightIndex] = value; - } - return pairs; -} - -function parseMacKernTable(p) { - var pairs = {}; - // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. - // Skip the rest. - p.skip('uShort'); - var nTables = p.parseULong(); - //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); - if (nTables > 1) { - console.warn('Only the first kern subtable is supported.'); - } - p.skip('uLong'); - var coverage = p.parseUShort(); - var subtableVersion = coverage & 0xFF; - p.skip('uShort'); - if (subtableVersion === 0) { + function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); var nPairs = p.parseUShort(); // Skip searchRange, entrySelector, rangeShift. p.skip('uShort', 3); @@ -14099,460 +14074,491 @@ function parseMacKernTable(p) { var value = p.parseShort(); pairs[leftIndex + ',' + rightIndex] = value; } + return pairs; + } + + function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + } + return pairs; } - return pairs; -} - -// Parse the `kern` table which contains kerning pairs. -function parseKernTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseUShort(); - if (tableVersion === 0) { - return parseWindowsKernTable(p); - } else if (tableVersion === 1) { - return parseMacKernTable(p); - } else { - throw new Error('Unsupported kern table version (' + tableVersion + ').'); - } -} - -var kern = { parse: parseKernTable }; -// The `loca` table stores the offsets to the locations of the glyphs in the font. + // Parse the `kern` table which contains kerning pairs. + function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } + } + + var kern = { parse: parseKernTable }; + + // The `loca` table stores the offsets to the locations of the glyphs in the font. + + // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, + // relative to the beginning of the glyphData table. + // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) + // The loca table has two versions: a short version where offsets are stored as uShorts, and a long + // version where offsets are stored as uLongs. The `head` table specifies which version to use + // (under indexToLocFormat). + function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } -// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, -// relative to the beginning of the glyphData table. -// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) -// The loca table has two versions: a short version where offsets are stored as uShorts, and a long -// version where offsets are stored as uLongs. The `head` table specifies which version to use -// (under indexToLocFormat). -function parseLocaTable(data, start, numGlyphs, shortVersion) { - var p = new parse.Parser(data, start); - var parseFn = shortVersion ? p.parseUShort : p.parseULong; - // There is an extra entry after the last index element to compute the length of the last glyph. - // That's why we use numGlyphs + 1. - var glyphOffsets = []; - for (var i = 0; i < numGlyphs + 1; i += 1) { - var glyphOffset = parseFn.call(p); - if (shortVersion) { - // The short table version stores the actual offset divided by 2. - glyphOffset *= 2; + glyphOffsets.push(glyphOffset); } - glyphOffsets.push(glyphOffset); + return glyphOffsets; } - return glyphOffsets; -} + var loca = { parse: parseLocaTable }; -var loca = { parse: parseLocaTable }; + // opentype.js -// opentype.js + /** + * The opentype library. + * @namespace opentype + */ -/** - * The opentype library. - * @namespace opentype - */ + // File loaders ///////////////////////////////////////////////////////// + /** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ + function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } -// File loaders ///////////////////////////////////////////////////////// -/** - * Loads a font from a file. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} path - The path of the file - * @param {Function} callback - The function to call when the font load completes - */ -function loadFromFile(path, callback) { - var fs = require('fs'); - fs.readFile(path, function(err, buffer) { - if (err) { - return callback(err.message); - } + callback(null, nodeBufferToArrayBuffer(buffer)); + }); + } + /** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ + function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; - callback(null, nodeBufferToArrayBuffer(buffer)); - }); -} -/** - * Loads a font from a URL. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} url - The URL of the font file. - * @param {Function} callback - The function to call when the font load completes - */ -function loadFromUrl(url, callback) { - var request = new XMLHttpRequest(); - request.open('get', url, true); - request.responseType = 'arraybuffer'; - request.onload = function() { - if (request.response) { - return callback(null, request.response); - } else { - return callback('Font could not be loaded: ' + request.statusText); - } - }; + request.onerror = function () { + callback('Font could not be loaded'); + }; - request.onerror = function () { - callback('Font could not be loaded'); - }; + request.send(); + } - request.send(); -} + // Table Directory Entries ////////////////////////////////////////////// + /** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ + function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; + } -// Table Directory Entries ////////////////////////////////////////////// -/** - * Parses OpenType table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ -function parseOpenTypeTableEntries(data, numTables) { - var tableEntries = []; - var p = 12; - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var checksum = parse.getULong(data, p + 4); - var offset = parse.getULong(data, p + 8); - var length = parse.getULong(data, p + 12); - tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); - p += 16; + return tableEntries; } - return tableEntries; -} + /** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ + function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; + } -/** - * Parses WOFF table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ -function parseWOFFTableEntries(data, numTables) { - var tableEntries = []; - var p = 44; // offset to the first table directory entry. - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var offset = parse.getULong(data, p + 4); - var compLength = parse.getULong(data, p + 8); - var origLength = parse.getULong(data, p + 12); - var compression = (void 0); - if (compLength < origLength) { - compression = 'WOFF'; - } else { - compression = false; + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; } - tableEntries.push({tag: tag, offset: offset, compression: compression, - compressedLength: compLength, length: origLength}); - p += 20; + return tableEntries; } - return tableEntries; -} + /** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ -/** - * @typedef TableData - * @type Object - * @property {DataView} data - The DataView - * @property {number} offset - The data offset. - */ + /** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ + function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + } -/** - * @param {DataView} - * @param {Object} - * @return {TableData} - */ -function uncompressTable(data, tableEntry) { - if (tableEntry.compression === 'WOFF') { - var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); - var outBuffer = new Uint8Array(tableEntry.length); - tinyInflate(inBuffer, outBuffer); - if (outBuffer.byteLength !== tableEntry.length) { - throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; } - - var view = new DataView(outBuffer.buffer, 0); - return {data: view, offset: 0}; - } else { - return {data: data, offset: tableEntry.offset}; } -} -// Public API /////////////////////////////////////////////////////////// + // Public API /////////////////////////////////////////////////////////// -/** - * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. - * Throws an error if the font could not be parsed. - * @param {ArrayBuffer} - * @param {Object} opt - options for parsing - * @return {opentype.Font} - */ -function parseBuffer(buffer, opt) { - opt = (opt === undefined || opt === null) ? {} : opt; - - var indexToLocFormat; - var ltagTable; - - // Since the constructor can also be called to create new fonts from scratch, we indicate this - // should be an empty font that we'll fill with our own data. - var font = new Font({empty: true}); - - // OpenType fonts use big endian byte ordering. - // We can't rely on typed array view types, because they operate with the endianness of the host computer. - // Instead we use DataViews where we can specify endianness. - var data = new DataView(buffer, 0); - var numTables; - var tableEntries = []; - var signature = parse.getTag(data, 0); - if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { - font.outlinesFormat = 'truetype'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'OTTO') { - font.outlinesFormat = 'cff'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'wOFF') { - var flavor = parse.getTag(data, 4); - if (flavor === String.fromCharCode(0, 1, 0, 0)) { + /** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ + function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { font.outlinesFormat = 'truetype'; - } else if (flavor === 'OTTO') { + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = 'truetype'; + } else if (flavor === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); } else { - throw new Error('Unsupported OpenType flavor ' + signature); - } - - numTables = parse.getUShort(data, 12); - tableEntries = parseWOFFTableEntries(data, numTables); - } else { - throw new Error('Unsupported OpenType signature ' + signature); - } - - var cffTableEntry; - var fvarTableEntry; - var glyfTableEntry; - var gdefTableEntry; - var gposTableEntry; - var gsubTableEntry; - var hmtxTableEntry; - var kernTableEntry; - var locaTableEntry; - var nameTableEntry; - var metaTableEntry; - var p; - - for (var i = 0; i < numTables; i += 1) { - var tableEntry = tableEntries[i]; - var table = (void 0); - switch (tableEntry.tag) { - case 'cmap': - table = uncompressTable(data, tableEntry); - font.tables.cmap = cmap.parse(table.data, table.offset); - font.encoding = new CmapEncoding(font.tables.cmap); - break; - case 'cvt ' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.cvt = p.parseShortList(tableEntry.length / 2); - break; - case 'fvar': - fvarTableEntry = tableEntry; - break; - case 'fpgm' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.fpgm = p.parseByteList(tableEntry.length); - break; - case 'head': - table = uncompressTable(data, tableEntry); - font.tables.head = head.parse(table.data, table.offset); - font.unitsPerEm = font.tables.head.unitsPerEm; - indexToLocFormat = font.tables.head.indexToLocFormat; - break; - case 'hhea': - table = uncompressTable(data, tableEntry); - font.tables.hhea = hhea.parse(table.data, table.offset); - font.ascender = font.tables.hhea.ascender; - font.descender = font.tables.hhea.descender; - font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; - break; - case 'hmtx': - hmtxTableEntry = tableEntry; - break; - case 'ltag': - table = uncompressTable(data, tableEntry); - ltagTable = ltag.parse(table.data, table.offset); - break; - case 'COLR': - table = uncompressTable(data, tableEntry); - font.tables.colr = colr.parse(table.data, table.offset); - break; - case 'CPAL': - table = uncompressTable(data, tableEntry); - font.tables.cpal = cpal.parse(table.data, table.offset); - break; - case 'maxp': - table = uncompressTable(data, tableEntry); - font.tables.maxp = maxp.parse(table.data, table.offset); - font.numGlyphs = font.tables.maxp.numGlyphs; - break; - case 'name': - nameTableEntry = tableEntry; - break; - case 'OS/2': - table = uncompressTable(data, tableEntry); - font.tables.os2 = os2.parse(table.data, table.offset); - break; - case 'post': - table = uncompressTable(data, tableEntry); - font.tables.post = post.parse(table.data, table.offset); - font.glyphNames = new GlyphNames(font.tables.post); - break; - case 'prep' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.prep = p.parseByteList(tableEntry.length); - break; - case 'glyf': - glyfTableEntry = tableEntry; - break; - case 'loca': - locaTableEntry = tableEntry; - break; - case 'CFF ': - cffTableEntry = tableEntry; - break; - case 'kern': - kernTableEntry = tableEntry; - break; - case 'GDEF': - gdefTableEntry = tableEntry; - break; - case 'GPOS': - gposTableEntry = tableEntry; - break; - case 'GSUB': - gsubTableEntry = tableEntry; - break; - case 'meta': - metaTableEntry = tableEntry; - break; + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gdefTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'COLR': + table = uncompressTable(data, tableEntry); + font.tables.colr = colr.parse(table.data, table.offset); + break; + case 'CPAL': + table = uncompressTable(data, tableEntry); + font.tables.cpal = cpal.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GDEF': + gdefTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; + } } - } - - var nameTable = uncompressTable(data, nameTableEntry); - font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); - font.names = font.tables.name; - if (glyfTableEntry && locaTableEntry) { - var shortVersion = indexToLocFormat === 0; - var locaTable = uncompressTable(data, locaTableEntry); - var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); - var glyfTable = uncompressTable(data, glyfTableEntry); - font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); - } else if (cffTableEntry) { - var cffTable = uncompressTable(data, cffTableEntry); - cff.parse(cffTable.data, cffTable.offset, font, opt); - } else { - throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); - } + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; - var hmtxTable = uncompressTable(data, hmtxTableEntry); - hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); - addGlyphNames(font, opt); + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } - if (kernTableEntry) { - var kernTable = uncompressTable(data, kernTableEntry); - font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); - } else { - font.kerningPairs = {}; - } + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); - if (gdefTableEntry) { - var gdefTable = uncompressTable(data, gdefTableEntry); - font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); - } + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } - if (gposTableEntry) { - var gposTable = uncompressTable(data, gposTableEntry); - font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); - font.position.init(); - } + if (gdefTableEntry) { + var gdefTable = uncompressTable(data, gdefTableEntry); + font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); + } - if (gsubTableEntry) { - var gsubTable = uncompressTable(data, gsubTableEntry); - font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); - } + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } - if (fvarTableEntry) { - var fvarTable = uncompressTable(data, fvarTableEntry); - font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); - } + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } - if (metaTableEntry) { - var metaTable = uncompressTable(data, metaTableEntry); - font.tables.meta = meta.parse(metaTable.data, metaTable.offset); - font.metas = font.tables.meta; - } + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } - return font; -} + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } -/** - * Asynchronously load the font from a URL or a filesystem. When done, call the callback - * with two arguments `(err, font)`. The `err` will be null on success, - * the `font` is a Font object. - * We use the node.js callback convention so that - * opentype.js can integrate with frameworks like async.js. - * @alias opentype.load - * @param {string} url - The URL of the font to load. - * @param {Function} callback - The callback. - */ -function load(url, callback, opt) { - opt = (opt === undefined || opt === null) ? {} : opt; - var isNode = typeof window === 'undefined'; - var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; + return font; + } - return new Promise(function (resolve, reject) { - loadFn(url, function(err, arrayBuffer) { - if (err) { - if (callback) { - return callback(err); - } else { - reject(err); + /** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ + function load(url, callback, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + var isNode = typeof window === 'undefined'; + var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); + } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { + if (callback) { + return callback(e, null); + } else { + reject(e); + } } - } - var font; - try { - font = parseBuffer(arrayBuffer, opt); - } catch (e) { if (callback) { - return callback(e, null); + return callback(null, font); } else { - reject(e); + resolve(font); } - } - if (callback) { - return callback(null, font); - } else { - resolve(font); - } + }); }); - }); -} + } -/** - * Synchronously load the font from a URL or file. - * When done, returns the font object or throws an error. - * @alias opentype.loadSync - * @param {string} url - The URL of the font to load. - * @param {Object} opt - opt.lowMemory - * @return {opentype.Font} - */ -function loadSync(url, opt) { - var fs = require('fs'); - var buffer = fs.readFileSync(url); - return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); -} + /** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ + function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); + } + + return { BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer }; -var opentype = /*#__PURE__*/Object.freeze({ +} )(); + +const opentype = { __proto__: null, Font: Font, Glyph: Glyph, @@ -14562,7 +14568,7 @@ var opentype = /*#__PURE__*/Object.freeze({ parse: parseBuffer, load: load, loadSync: loadSync -}); +}; export default opentype; export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse }; From 3547f8c399d1961ea7ae615ed964e1eec7ed2347 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:52:27 -0500 Subject: [PATCH 13/45] utif: tree-shake as IIFE --- examples/jsm/libs/utif.module.js | 3026 +++++++++++++++--------------- 1 file changed, 1516 insertions(+), 1510 deletions(-) diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index 9a655bd26d1dfc..aa5c2c61cdd440 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -1,1112 +1,1033 @@ -var UTIF = {}; - -// Following lines add a JPEG decoder to UTIF.JpegDecoder -(function(){"use strict";var W=function a1(){function W(p){this.message="JPEG error: "+p}W.prototype=new Error;W.prototype.name="JpegError";W.constructor=W;return W}(),ak=function ag(){var p=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),t=4017,ac=799,ah=3406,ao=2276,ar=1567,ai=3784,s=5793,ad=2896;function ak(Q){if(Q==null)Q={};if(Q.w==null)Q.w=-1;this.V=Q.n;this.N=Q.w}function a5(Q,h){var f=0,G=[],n,E,a=16,F;while(a>0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n0){C=G.pop()}C.index++;G.push(C);while(G.length<=n){G.push(F={children:[],index:0});C.children[C.index]=F.children;C=F}f++}if(n+10){V--;return J>>V&1}J=Q[h++];if(J===255){var I=Q[h++];if(I){if(I===220&&d){h+=2;var l=Z(Q,h);h+=2;if(l>0&&l!==f.s){throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",l)}}else if(I===217){if(d){var M=q*8; -if(M>0&&M>>7}function u(I){var l=I;while(!0){l=l[Y()];switch(typeof l){case"number":return l;case"object":continue}throw new W("invalid huffman sequence")}}function m(I){var e=0;while(I>0){e=e<<1|Y();I--}return e}function j(I){if(I===1){return Y()===1?1:-1}var e=m(I);if(e>=1<>4;if(i===0){if(A<15){break}N+=16;continue}N+=A;var o=p[N];X.D[I+o]=j(i);N++}}function $(X,I){var l=u(X.J),M=l===0?0:j(l)<0){r--;return}var N=E,l=a;while(N<=l){var M=u(X.i),S=M&15,i=M>>4;if(S===0){if(i<15){r=m(i)+(1<>4;if(S===0){if(M<15){r=m(M)+(1<0){for(O=0;O0?"unexpected":"excessive";h=k.offset}if(k.M>=65488&&k.M<=65495){h+=2}else{break}}return h-z}function al(Q,h,f){var G=Q.$,n=Q.D,E,a,C,F,d,T,U,z,J,V,Y,u,m,j,v,$,b;if(!G){throw new W("missing required Quantization Table.")}for(var r=0;r<64;r+=8){J=n[h+r];V=n[h+r+1];Y=n[h+r+2];u=n[h+r+3];m=n[h+r+4];j=n[h+r+5];v=n[h+r+6];$=n[h+r+7];J*=G[r];if((V|Y|u|m|j|v|$)===0){b=s*J+512>>10;f[r]=b;f[r+1]=b;f[r+2]=b;f[r+3]=b;f[r+4]=b;f[r+5]=b;f[r+6]=b;f[r+7]=b;continue}V*=G[r+1];Y*=G[r+2];u*=G[r+3];m*=G[r+4];j*=G[r+5];v*=G[r+6];$*=G[r+7];E=s*J+128>>8;a=s*m+128>>8;C=Y;F=v;d=ad*(V-$)+128>>8;z=ad*(V+$)+128>>8; -T=u<<4;U=j<<4;E=E+a+1>>1;a=E-a;b=C*ai+F*ar+128>>8;C=C*ar-F*ai+128>>8;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b;b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;f[r]=E+z;f[r+7]=E-z;f[r+1]=a+U;f[r+6]=a-U;f[r+2]=C+T;f[r+5]=C-T;f[r+3]=F+d;f[r+4]=F-d}for(var P=0;P<8;++P){J=f[P];V=f[P+8];Y=f[P+16];u=f[P+24];m=f[P+32];j=f[P+40];v=f[P+48];$=f[P+56];if((V|Y|u|m|j|v|$)===0){b=s*J+8192>>14;if(b<-2040){b=0}else if(b>=2024){b=255}else{b=b+2056>>4}n[h+P]=b;n[h+P+8]=b;n[h+P+16]=b;n[h+P+24]=b;n[h+P+32]=b;n[h+P+40]=b;n[h+P+48]=b;n[h+P+56]=b;continue}E=s*J+2048>>12;a=s*m+2048>>12;C=Y;F=v;d=ad*(V-$)+2048>>12;z=ad*(V+$)+2048>>12;T=u;U=j;E=(E+a+1>>1)+4112;a=E-a;b=C*ai+F*ar+2048>>12;C=C*ar-F*ai+2048>>12;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b; -b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;J=E+z;$=E-z;V=a+U;v=a-U;Y=C+T;j=C-T;u=F+d;m=F-d;if(J<16){J=0}else if(J>=4080){J=255}else{J>>=4}if(V<16){V=0}else if(V>=4080){V=255}else{V>>=4}if(Y<16){Y=0}else if(Y>=4080){Y=255}else{Y>>=4}if(u<16){u=0}else if(u>=4080){u=255}else{u>>=4}if(m<16){m=0}else if(m>=4080){m=255}else{m>>=4}if(j<16){j=0}else if(j>=4080){j=255}else{j>>=4}if(v<16){v=0}else if(v>=4080){v=255}else{v>>=4}if($<16){$=0}else if($>=4080){$=255}else{$>>=4}n[h+P]=J; -n[h+P+8]=V;n[h+P+16]=Y;n[h+P+24]=u;n[h+P+32]=m;n[h+P+40]=j;n[h+P+48]=v;n[h+P+56]=$}}function a0(Q,h){var f=h.P,G=h.c,n=new Int16Array(64);for(var E=0;E=G){return null}var E=Z(Q,h);if(E>=65472&&E<=65534){return{u:null,M:E,offset:h}}var a=Z(Q,n);while(!(a>=65472&&a<=65534)){if(++n>=G){return null}a=Z(Q,n)}return{u:E.toString(16),M:a,offset:n}}ak.prototype={parse(Q,h){if(h==null)h={}; -var f=h.F,E=0,a=null,C=null,F,d,T=0;function G(){var o=Z(Q,E);E+=2;var B=E+o-2,V=an(Q,B,E);if(V&&V.u){B=V.offset}var ab=Q.subarray(E,B);E+=ab.length;return ab}function n(F){var o=Math.ceil(F.o/8/F.X),B=Math.ceil(F.s/8/F.B);for(var Y=0;Y>4===0){for(u=0;u<64;u++){b=p[u];P[b]=Q[E++]}}else if(r>>4===1){for(u=0;u<64;u++){b=p[u];P[b]=Z(Q,E);E+=2}}else{throw new W("DQT - invalid table spec")}U[r&15]=P}break;case 65472:case 65473:case 65474:if(F){throw new W("Only single frame JPEGs supported")}E+=2;F={};F.G=V===65473;F.Z=V===65474;F.precision=Q[E++];var D=Z(Q,E),a4,q=0,H=0;E+=2;F.s=f||D;F.o=Z(Q,E);E+=2;F.W=[];F._={};var a8=Q[E++];for(Y=0;Y>4,y=Q[E+1]&15;if(q>4===0?J:z)[_&15]=a5(N,K)}break;case 65501:E+=2;d=Z(Q,E);E+=2;break;case 65498:var x=++T===1&&!f,R;E+=2;var k=Q[E++],g=[];for(Y=0;Y>4];R.i=z[a6&15];g.push(R)}var I=Q[E++],l=Q[E++],M=Q[E++];try{var S=a7(Q,E,F,g,d,I,l,M>>4,M&15,x);E+=S}catch(ex){if(ex instanceof DNLMarkerError){return this.parse(Q,{F:ex.s})}else if(ex instanceof EOIMarkerError){break markerLoop}throw ex}break;case 65500:E+=4;break;case 65535:if(Q[E]!==255){E--}break;default:var i=an(Q,E-2,E-3);if(i&&i.u){E=i.offset;break}if(E>=Q.length-1){break markerLoop}throw new W("JpegImage.parse - unknown marker: "+V.toString(16))}V=Z(Q,E);E+=2}this.width=F.o;this.height=F.s;this.g=a;this.b=C;this.W=[];for(Y=0;Y>8)+P[J+1]}}}return v},get f(){if(this.b){return!!this.b.a}if(this.p===3){if(this.N===0){return!1}else if(this.W[0].index===82&&this.W[1].index===71&&this.W[2].index===66){return!1}return!0}if(this.N===1){return!0}return!1},z:function aj(Q){var h,f,G; -for(var n=0,E=Q.length;n4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p===1&&G){var a=E.length,C=new Uint8ClampedArray(a*3),F=0;for(var d=0;d>24}function Z(p,t){return p[t]<<8|p[t+1]}function am(p,t){return(p[t]<<24|p[t+1]<<16|p[t+2]<<8|p[t+3])>>>0}UTIF.JpegDecoder=ak}()); - -//UTIF.JpegDecoder = PDFJS.JpegImage; - - -UTIF.encodeImage = function(rgba, w, h, metadata) -{ - var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset - "t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts - "t282":[[72,1]], "t283":[[72,1]], "t284":[1], "t286":[[0,1]], "t287":[[0,1]], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1] - }; - if (metadata) for (var i in metadata) idf[i] = metadata[i]; - - var prfx = new Uint8Array(UTIF.encode([idf])); - var img = new Uint8Array(rgba); - var data = new Uint8Array(1000+w*h*4); - for(var i=0; i { + + var UTIF = {}; + + // Following lines add a JPEG decoder to UTIF.JpegDecoder + (function(){"use strict";var W=function a1(){function W(p){this.message="JPEG error: "+p}W.prototype=new Error;W.prototype.name="JpegError";W.constructor=W;return W}(),ak=function ag(){var p=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),t=4017,ac=799,ah=3406,ao=2276,ar=1567,ai=3784,s=5793,ad=2896;function ak(Q){if(Q==null)Q={};if(Q.w==null)Q.w=-1;this.V=Q.n;this.N=Q.w}function a5(Q,h){var f=0,G=[],n,E,a=16,F;while(a>0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n0){C=G.pop()}C.index++;G.push(C);while(G.length<=n){G.push(F={children:[],index:0});C.children[C.index]=F.children;C=F}f++}if(n+10){V--;return J>>V&1}J=Q[h++];if(J===255){var I=Q[h++];if(I){if(I===220&&d){h+=2;var l=Z(Q,h);h+=2;if(l>0&&l!==f.s){throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",l)}}else if(I===217){if(d){var M=q*8; + if(M>0&&M>>7}function u(I){var l=I;while(!0){l=l[Y()];switch(typeof l){case"number":return l;case"object":continue}throw new W("invalid huffman sequence")}}function m(I){var e=0;while(I>0){e=e<<1|Y();I--}return e}function j(I){if(I===1){return Y()===1?1:-1}var e=m(I);if(e>=1<>4;if(i===0){if(A<15){break}N+=16;continue}N+=A;var o=p[N];X.D[I+o]=j(i);N++}}function $(X,I){var l=u(X.J),M=l===0?0:j(l)<0){r--;return}var N=E,l=a;while(N<=l){var M=u(X.i),S=M&15,i=M>>4;if(S===0){if(i<15){r=m(i)+(1<>4;if(S===0){if(M<15){r=m(M)+(1<0){for(O=0;O0?"unexpected":"excessive";h=k.offset}if(k.M>=65488&&k.M<=65495){h+=2}else{break}}return h-z}function al(Q,h,f){var G=Q.$,n=Q.D,E,a,C,F,d,T,U,z,J,V,Y,u,m,j,v,$,b;if(!G){throw new W("missing required Quantization Table.")}for(var r=0;r<64;r+=8){J=n[h+r];V=n[h+r+1];Y=n[h+r+2];u=n[h+r+3];m=n[h+r+4];j=n[h+r+5];v=n[h+r+6];$=n[h+r+7];J*=G[r];if((V|Y|u|m|j|v|$)===0){b=s*J+512>>10;f[r]=b;f[r+1]=b;f[r+2]=b;f[r+3]=b;f[r+4]=b;f[r+5]=b;f[r+6]=b;f[r+7]=b;continue}V*=G[r+1];Y*=G[r+2];u*=G[r+3];m*=G[r+4];j*=G[r+5];v*=G[r+6];$*=G[r+7];E=s*J+128>>8;a=s*m+128>>8;C=Y;F=v;d=ad*(V-$)+128>>8;z=ad*(V+$)+128>>8; + T=u<<4;U=j<<4;E=E+a+1>>1;a=E-a;b=C*ai+F*ar+128>>8;C=C*ar-F*ai+128>>8;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b;b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;f[r]=E+z;f[r+7]=E-z;f[r+1]=a+U;f[r+6]=a-U;f[r+2]=C+T;f[r+5]=C-T;f[r+3]=F+d;f[r+4]=F-d}for(var P=0;P<8;++P){J=f[P];V=f[P+8];Y=f[P+16];u=f[P+24];m=f[P+32];j=f[P+40];v=f[P+48];$=f[P+56];if((V|Y|u|m|j|v|$)===0){b=s*J+8192>>14;if(b<-2040){b=0}else if(b>=2024){b=255}else{b=b+2056>>4}n[h+P]=b;n[h+P+8]=b;n[h+P+16]=b;n[h+P+24]=b;n[h+P+32]=b;n[h+P+40]=b;n[h+P+48]=b;n[h+P+56]=b;continue}E=s*J+2048>>12;a=s*m+2048>>12;C=Y;F=v;d=ad*(V-$)+2048>>12;z=ad*(V+$)+2048>>12;T=u;U=j;E=(E+a+1>>1)+4112;a=E-a;b=C*ai+F*ar+2048>>12;C=C*ar-F*ai+2048>>12;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b; + b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;J=E+z;$=E-z;V=a+U;v=a-U;Y=C+T;j=C-T;u=F+d;m=F-d;if(J<16){J=0}else if(J>=4080){J=255}else{J>>=4}if(V<16){V=0}else if(V>=4080){V=255}else{V>>=4}if(Y<16){Y=0}else if(Y>=4080){Y=255}else{Y>>=4}if(u<16){u=0}else if(u>=4080){u=255}else{u>>=4}if(m<16){m=0}else if(m>=4080){m=255}else{m>>=4}if(j<16){j=0}else if(j>=4080){j=255}else{j>>=4}if(v<16){v=0}else if(v>=4080){v=255}else{v>>=4}if($<16){$=0}else if($>=4080){$=255}else{$>>=4}n[h+P]=J; + n[h+P+8]=V;n[h+P+16]=Y;n[h+P+24]=u;n[h+P+32]=m;n[h+P+40]=j;n[h+P+48]=v;n[h+P+56]=$}}function a0(Q,h){var f=h.P,G=h.c,n=new Int16Array(64);for(var E=0;E=G){return null}var E=Z(Q,h);if(E>=65472&&E<=65534){return{u:null,M:E,offset:h}}var a=Z(Q,n);while(!(a>=65472&&a<=65534)){if(++n>=G){return null}a=Z(Q,n)}return{u:E.toString(16),M:a,offset:n}}ak.prototype={parse(Q,h){if(h==null)h={}; + var f=h.F,E=0,a=null,C=null,F,d,T=0;function G(){var o=Z(Q,E);E+=2;var B=E+o-2,V=an(Q,B,E);if(V&&V.u){B=V.offset}var ab=Q.subarray(E,B);E+=ab.length;return ab}function n(F){var o=Math.ceil(F.o/8/F.X),B=Math.ceil(F.s/8/F.B);for(var Y=0;Y>4===0){for(u=0;u<64;u++){b=p[u];P[b]=Q[E++]}}else if(r>>4===1){for(u=0;u<64;u++){b=p[u];P[b]=Z(Q,E);E+=2}}else{throw new W("DQT - invalid table spec")}U[r&15]=P}break;case 65472:case 65473:case 65474:if(F){throw new W("Only single frame JPEGs supported")}E+=2;F={};F.G=V===65473;F.Z=V===65474;F.precision=Q[E++];var D=Z(Q,E),a4,q=0,H=0;E+=2;F.s=f||D;F.o=Z(Q,E);E+=2;F.W=[];F._={};var a8=Q[E++];for(Y=0;Y>4,y=Q[E+1]&15;if(q>4===0?J:z)[_&15]=a5(N,K)}break;case 65501:E+=2;d=Z(Q,E);E+=2;break;case 65498:var x=++T===1&&!f,R;E+=2;var k=Q[E++],g=[];for(Y=0;Y>4];R.i=z[a6&15];g.push(R)}var I=Q[E++],l=Q[E++],M=Q[E++];try{var S=a7(Q,E,F,g,d,I,l,M>>4,M&15,x);E+=S}catch(ex){if(ex instanceof DNLMarkerError){return this.parse(Q,{F:ex.s})}else if(ex instanceof EOIMarkerError){break markerLoop}throw ex}break;case 65500:E+=4;break;case 65535:if(Q[E]!==255){E--}break;default:var i=an(Q,E-2,E-3);if(i&&i.u){E=i.offset;break}if(E>=Q.length-1){break markerLoop}throw new W("JpegImage.parse - unknown marker: "+V.toString(16))}V=Z(Q,E);E+=2}this.width=F.o;this.height=F.s;this.g=a;this.b=C;this.W=[];for(Y=0;Y>8)+P[J+1]}}}return v},get f(){if(this.b){return!!this.b.a}if(this.p===3){if(this.N===0){return!1}else if(this.W[0].index===82&&this.W[1].index===71&&this.W[2].index===66){return!1}return!0}if(this.N===1){return!0}return!1},z:function aj(Q){var h,f,G; + for(var n=0,E=Q.length;n4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p===1&&G){var a=E.length,C=new Uint8ClampedArray(a*3),F=0;for(var d=0;d>24}function Z(p,t){return p[t]<<8|p[t+1]}function am(p,t){return(p[t]<<24|p[t+1]<<16|p[t+2]<<8|p[t+3])>>>0}UTIF.JpegDecoder=ak}()); + + //UTIF.JpegDecoder = PDFJS.JpegImage; + + + UTIF.encodeImage = function(rgba, w, h, metadata) { - var noffs = UTIF._writeIFD(bin, UTIF._types.basic, data, ifdo, ifds[i]); - ifdo = noffs[1]; - if(i probably not an image - img.isLE = id=="II"; - img.width = img["t256"][0]; //delete img["t256"]; - img.height = img["t257"][0]; //delete img["t257"]; - - var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; - var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; - if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); - if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); - - var spp = img["t277"]?img["t277"][0]:1; - var bps = img["t258"]?img["t258"][0]:1; - var bipp = bps*spp; // bits per pixel - /* - var bipp; // bits per pixel - if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; - else bipp = (img["t277"]?img["t277"][0]:1); - */ - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { - bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); + var prfx = new Uint8Array(UTIF.encode([idf])); + var img = new Uint8Array(rgba); + var data = new Uint8Array(1000+w*h*4); + for(var i=0; i>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; - - if(img["t322"]!=null) // tiled + + UTIF.encode = function(ifds) { - var tw = img["t322"][0], th = img["t323"][0]; - var tx = Math.floor((img.width + tw - 1) / tw); - var ty = Math.floor((img.height + th - 1) / th); - var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); - console.log("====", tx,ty); - for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); - - // convert to Little Endian /* - if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG - for(var y=0; y probably not an image + img.isLE = id=="II"; + img.width = img["t256"][0]; //delete img["t256"]; + img.height = img["t257"][0]; //delete img["t257"]; + + var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; + var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; + if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); + if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); + + var spp = img["t277"]?img["t277"][0]:1; + var bps = img["t258"]?img["t258"][0]:1; + var bipp = bps*spp; // bits per pixel + /* + var bipp; // bits per pixel + if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; + else bipp = (img["t277"]?img["t277"][0]:1); + */ + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { + bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); + } + if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 + var bipl = Math.ceil(img.width*bipp/8)*8; + var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; + var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; + + if(img["t322"]!=null) // tiled { - var ntoff = toff+y*bpl; - if(bps==16) for(var j=bpp; j>>8)&255; - } - else if(noc==3) for(var j= 3; j> 3 ^ 0x3ff0; - return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); - } - } - // Raw Format 6 - function getBufferDataRW6(i) { - return buffer[vpos + 15 - i]; - } - function readPageRW6() { - bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit - bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; - bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); - bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); - bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); - bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; - bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); - bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); - bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; - bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); - bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; - bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; - vpos += 16; - byte = 0; - } - function readPageRw6_bps12() { - bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); - bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; - bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); - bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); - bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); - bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; - bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); - bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); - bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); - bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); - bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); - bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); - bytes[14] = getBufferDataRW6(12) & 0x3; - bytes[15] = getBufferDataRW6(13); - bytes[16] = getBufferDataRW6(14); - bytes[17] = getBufferDataRW6(15); - - vpos += 16; - byte = 0; - } - // Main loop - function resetPredNonzeros(){ - pred[0]=0; pred[1]=0; - nonz[0]=0; nonz[1]=0; - } - if (RW2_Format == 7) { - throw RW2_Format; - - // Skatch of version 7 - /* - var pixels_per_block = bitsPerSample == 14 ? 9 : 10; - rowbytes = 0|(rawWidth / pixels_per_block * 16); - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - i = 0; - for (crow = 0; crow < rowstoread; crow++) { - idx = (row + crow) * rawWidth; - for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { - for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; - if (bitsPerSample == 12) { - result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - } - */ - } - else if(RW2_Format == 6) { - var is12bit = bitsPerSample == 12, - readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, - pixelsPerBlock = is12bit ? 14 : 11, - pixelbase0 = is12bit ? 0x80 : 0x200, - pixelbase_compare = is12bit ? 0x800 : 0x2000, - spix_compare = is12bit ? 0x3fff : 0xffff, - pixel_mask = is12bit ? 0xfff : 0x3fff, - blocksperrow = rawWidth / pixelsPerBlock, - rowbytes = blocksperrow * 16, - bufferSize = is12bit ? 18 : 14; - - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { - idx = (row + crow) * rawWidth; - for (var rblock = 0; rblock < blocksperrow; rblock++) { - readPageRw6Fn(); - resetPredNonzeros(); - sh=0; pixel_base=0; - for (i = 0; i < pixelsPerBlock; i++){ - isOdd = i & 1; - if (i % 3 == 2) { - var base = byte < bufferSize ? bytes[byte++] : 0; - if (base == 3) base = 4; - pixel_base = pixelbase0 << base; - sh = 1 << base; - } - var epixel = byte < bufferSize ? bytes[byte++] : 0; - if (pred[isOdd]) { - epixel *= sh; - if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) - epixel += nonz[isOdd] - pixel_base; - nonz[isOdd] = epixel; - } else { - pred[isOdd] = epixel; - if (epixel) - nonz[isOdd] = epixel; - else - epixel = nonz[isOdd]; - } - result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; - } - } - } - } - } - else if (RW2_Format == 5) { - var blockSize = bitsPerSample == 12 ? 10 : 9; - for (row = 0; row < rawHeight; row++) { - for (col = 0; col < rawWidth; col+=blockSize) { - getDataRaw(0); - // Tuhle podminku pouziva i RW2_Format 7 - if (bitsPerSample == 12) { - result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - //console.log(result[1000000 - 1]) - } else if(RW2_Format == 4) { - for (row = 0; row < rawHeight; row++){ - for(col = 0; col < rawWidth; col++){ - i = col % 14; - isOdd = i & 1; - if (i==0) resetPredNonzeros(); - if (i%3 == 2) - sh = 4 >> (3 - getDataRaw(2)); - if (nonz[isOdd]) { - j = getDataRaw(8); - if(j != 0){ - pred[isOdd] -= 0x80 << sh; - if (pred[isOdd] < 0 || sh == 4) - pred[isOdd] &= ~((-1) << sh); - pred[isOdd] += j << sh; - } - } else { - nonz[isOdd] = getDataRaw(8); - if(nonz[isOdd] || i > 11) - pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); - } - result[idx++] = pred[col & 1]; - } - } - } - else throw RW2_Format; - } - - -UTIF.decode._decodeVC5 = function(){var x=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],o,C,k,P=[3,3,3,3,2,2,2,1,1,1],V=24576,ar=16384,H=8192,az=ar|H; -function d(t){var E=t[1],h=t[0][E>>>3]>>>7-(E&7)&1;t[1]++;return h}function ag(t,E){if(o==null){o={}; -for(var h=0;h>>1}return t}function A(t,E){return t>>E}function O(t,E,h,L,g,n){E[h]=A(A(11*t[g]-4*t[g+n]+t[g+n+n]+4,3)+t[L],1); -E[h+n]=A(A(5*t[g]+4*t[g+n]-t[g+n+n]+4,3)-t[L],1)}function J(t,E,h,L,g,n){var W=t[g-n]-t[g+n],j=t[g],$=t[L]; -E[h]=A(A(W+4,3)+j+$,1);E[h+n]=A(A(-W+4,3)+j-$,1)}function y(t,E,h,L,g,n){E[h]=A(A(5*t[g]+4*t[g-n]-t[g-n-n]+4,3)+t[L],1); -E[h+n]=A(A(11*t[g]-4*t[g-n]+t[g-n-n]+4,3)-t[L],1)}function q(t){t=t<0?0:t>4095?4095:t;t=k[t]>>>2;return t}function av(t,E,h,L,g,n){L=new Uint16Array(L.buffer); -var W=Date.now(),j=UTIF._binBE,$=E+h,r,u,X,I,ax,a3,R,ai,aa,ap,ah,ae,aD,al,i,aE,T,B;E+=4;var a5=n[0]==1; -while(E<$){var S=j.readShort(t,E),s=j.readUshort(t,E+2);E+=4;if(S==12)r=s;else if(S==20)u=s;else if(S==21)X=s; -else if(S==48)I=s;else if(S==53)ax=s;else if(S==35)a3=s;else if(S==62)R=s;else if(S==101)ai=s;else if(S==109)aa=s; -else if(S==84)ap=s;else if(S==106)ah=s;else if(S==107)ae=s;else if(S==108)aD=s;else if(S==102)al=s;else if(S==104)i=s; -else if(S==105)aE=s;else{var F=S<0?-S:S,D=F&65280,_=0;if(F&az){if(F&H){_=s&65535;_+=(F&255)<<16}else{_=s&65535}}if((F&V)==V){if(T==null){T=[]; -for(var M=0;M<4;M++)T[M]=new Int16Array((u>>>1)*(X>>>1));B=new Int16Array((u>>>1)*(X>>>1));C=new Int16Array(1024); -for(var M=0;M<1024;M++){var aG=M-512,p=Math.abs(aG),r=Math.floor(768*p*p*p/(255*255*255))+p;C[M]=Math.sign(aG)*r}k=new Uint16Array(4096); -var aA=(1<<16)-1;for(var M=0;M<4096;M++){var at=M,a1=aA*(Math.pow(113,at/4095)-1)/112;k[M]=Math.min(a1,aA)}}var w=T[R],v=m(u,1+P[I]),N=m(X,1+P[I]); -if(I==0){for(var b=0;b>>1)+G]=t[c]<<8|t[c+1]}}else{var a7=[t,E*8],a4=[],ay=0,aw=v*N,f=[0,0],Q=0,s=0; -while(ay0){a4[ay++]=s;Q--}}var l=(I-1)%3,aF=l!=1?v:0,a2=l!=0?N:0; -for(var b=0;b>>1)+aF,au=b*v;for(var G=0;G>>1,an=v*2,a9=N*2; -for(var b=0;b>14-K*2&3;var a6=aC[aB];if(a6!=0)for(var b=0;b>>1)*(u>>>1)+(G>>>1),z=a8[c],ao=ab[c]-2048,ak=aq[c]-2048,ad=as[c]-2048,aj=(ao<<1)+z,a0=(ak<<1)+z,aH=z+ad,am=z-ad; -if(a5){L[U]=q(aH);L[U+1]=q(a0);L[U+u]=q(aj);L[U+u+1]=q(am)}else{L[U]=q(aj);L[U+1]=q(aH);L[U+u]=q(am); -L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==9216){}else throw F.toString(16)}}console.log(Date.now()-W)}return av}() - - - -UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { - var w = img.width, qw=w*4; - var io = 0, out = new Uint8Array(qw); - - while(io>>3, bpl = Math.ceil(bps*noc*w/8); - for(var x=0; x>>8)&255; + } + else if(noc==3) for(var j= 3; j>> (tab[i] >>> 8); - for(var c=0; c> 3 ^ 0x3ff0; + return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); + } + } + // Raw Format 6 + function getBufferDataRW6(i) { + return buffer[vpos + 15 - i]; + } + function readPageRW6() { + bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit + bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; + bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); + bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); + bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); + bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; + bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); + bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); + bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; + bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); + bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; + bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; + vpos += 16; + byte = 0; + } + function readPageRw6_bps12() { + bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); + bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; + bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); + bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); + bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); + bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; + bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); + bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); + bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); + bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); + bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); + bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); + bytes[14] = getBufferDataRW6(12) & 0x3; + bytes[15] = getBufferDataRW6(13); + bytes[16] = getBufferDataRW6(14); + bytes[17] = getBufferDataRW6(15); + + vpos += 16; + byte = 0; + } + // Main loop + function resetPredNonzeros(){ + pred[0]=0; pred[1]=0; + nonz[0]=0; nonz[1]=0; + } + if (RW2_Format == 7) { + throw RW2_Format; + + // Skatch of version 7 + /* + var pixels_per_block = bitsPerSample == 14 ? 9 : 10; + rowbytes = 0|(rawWidth / pixels_per_block * 16); + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + i = 0; + for (crow = 0; crow < rowstoread; crow++) { + idx = (row + crow) * rawWidth; + for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { + for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; + if (bitsPerSample == 12) { + result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + } + */ + } + else if(RW2_Format == 6) { + var is12bit = bitsPerSample == 12, + readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, + pixelsPerBlock = is12bit ? 14 : 11, + pixelbase0 = is12bit ? 0x80 : 0x200, + pixelbase_compare = is12bit ? 0x800 : 0x2000, + spix_compare = is12bit ? 0x3fff : 0xffff, + pixel_mask = is12bit ? 0xfff : 0x3fff, + blocksperrow = rawWidth / pixelsPerBlock, + rowbytes = blocksperrow * 16, + bufferSize = is12bit ? 18 : 14; + + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { + idx = (row + crow) * rawWidth; + for (var rblock = 0; rblock < blocksperrow; rblock++) { + readPageRw6Fn(); + resetPredNonzeros(); + sh=0; pixel_base=0; + for (i = 0; i < pixelsPerBlock; i++){ + isOdd = i & 1; + if (i % 3 == 2) { + var base = byte < bufferSize ? bytes[byte++] : 0; + if (base == 3) base = 4; + pixel_base = pixelbase0 << base; + sh = 1 << base; + } + var epixel = byte < bufferSize ? bytes[byte++] : 0; + if (pred[isOdd]) { + epixel *= sh; + if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) + epixel += nonz[isOdd] - pixel_base; + nonz[isOdd] = epixel; + } else { + pred[isOdd] = epixel; + if (epixel) + nonz[isOdd] = epixel; + else + epixel = nonz[isOdd]; + } + result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; + } + } + } + } + } + else if (RW2_Format == 5) { + var blockSize = bitsPerSample == 12 ? 10 : 9; + for (row = 0; row < rawHeight; row++) { + for (col = 0; col < rawWidth; col+=blockSize) { + getDataRaw(0); + // Tuhle podminku pouziva i RW2_Format 7 + if (bitsPerSample == 12) { + result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + //console.log(result[1000000 - 1]) + } else if(RW2_Format == 4) { + for (row = 0; row < rawHeight; row++){ + for(col = 0; col < rawWidth; col++){ + i = col % 14; + isOdd = i & 1; + if (i==0) resetPredNonzeros(); + if (i%3 == 2) + sh = 4 >> (3 - getDataRaw(2)); + if (nonz[isOdd]) { + j = getDataRaw(8); + if(j != 0){ + pred[isOdd] -= 0x80 << sh; + if (pred[isOdd] < 0 || sh == 4) + pred[isOdd] &= ~((-1) << sh); + pred[isOdd] += j << sh; + } + } else { + nonz[isOdd] = getDataRaw(8); + if(nonz[isOdd] || i > 11) + pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); + } + result[idx++] = pred[col & 1]; + } + } + } + else throw RW2_Format; + } + + + UTIF.decode._decodeVC5 = function(){var x=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],o,C,k,P=[3,3,3,3,2,2,2,1,1,1],V=24576,ar=16384,H=8192,az=ar|H; + function d(t){var E=t[1],h=t[0][E>>>3]>>>7-(E&7)&1;t[1]++;return h}function ag(t,E){if(o==null){o={}; + for(var h=0;h>>1}return t}function A(t,E){return t>>E}function O(t,E,h,L,g,n){E[h]=A(A(11*t[g]-4*t[g+n]+t[g+n+n]+4,3)+t[L],1); + E[h+n]=A(A(5*t[g]+4*t[g+n]-t[g+n+n]+4,3)-t[L],1)}function J(t,E,h,L,g,n){var W=t[g-n]-t[g+n],j=t[g],$=t[L]; + E[h]=A(A(W+4,3)+j+$,1);E[h+n]=A(A(-W+4,3)+j-$,1)}function y(t,E,h,L,g,n){E[h]=A(A(5*t[g]+4*t[g-n]-t[g-n-n]+4,3)+t[L],1); + E[h+n]=A(A(11*t[g]-4*t[g-n]+t[g-n-n]+4,3)-t[L],1)}function q(t){t=t<0?0:t>4095?4095:t;t=k[t]>>>2;return t}function av(t,E,h,L,g,n){L=new Uint16Array(L.buffer); + var W=Date.now(),j=UTIF._binBE,$=E+h,r,u,X,I,ax,a3,R,ai,aa,ap,ah,ae,aD,al,i,aE,T,B;E+=4;var a5=n[0]==1; + while(E<$){var S=j.readShort(t,E),s=j.readUshort(t,E+2);E+=4;if(S==12)r=s;else if(S==20)u=s;else if(S==21)X=s; + else if(S==48)I=s;else if(S==53)ax=s;else if(S==35)a3=s;else if(S==62)R=s;else if(S==101)ai=s;else if(S==109)aa=s; + else if(S==84)ap=s;else if(S==106)ah=s;else if(S==107)ae=s;else if(S==108)aD=s;else if(S==102)al=s;else if(S==104)i=s; + else if(S==105)aE=s;else{var F=S<0?-S:S,D=F&65280,_=0;if(F&az){if(F&H){_=s&65535;_+=(F&255)<<16}else{_=s&65535}}if((F&V)==V){if(T==null){T=[]; + for(var M=0;M<4;M++)T[M]=new Int16Array((u>>>1)*(X>>>1));B=new Int16Array((u>>>1)*(X>>>1));C=new Int16Array(1024); + for(var M=0;M<1024;M++){var aG=M-512,p=Math.abs(aG),r=Math.floor(768*p*p*p/(255*255*255))+p;C[M]=Math.sign(aG)*r}k=new Uint16Array(4096); + var aA=(1<<16)-1;for(var M=0;M<4096;M++){var at=M,a1=aA*(Math.pow(113,at/4095)-1)/112;k[M]=Math.min(a1,aA)}}var w=T[R],v=m(u,1+P[I]),N=m(X,1+P[I]); + if(I==0){for(var b=0;b>>1)+G]=t[c]<<8|t[c+1]}}else{var a7=[t,E*8],a4=[],ay=0,aw=v*N,f=[0,0],Q=0,s=0; + while(ay0){a4[ay++]=s;Q--}}var l=(I-1)%3,aF=l!=1?v:0,a2=l!=0?N:0; + for(var b=0;b>>1)+aF,au=b*v;for(var G=0;G>>1,an=v*2,a9=N*2; + for(var b=0;b>14-K*2&3;var a6=aC[aB];if(a6!=0)for(var b=0;b>>1)*(u>>>1)+(G>>>1),z=a8[c],ao=ab[c]-2048,ak=aq[c]-2048,ad=as[c]-2048,aj=(ao<<1)+z,a0=(ak<<1)+z,aH=z+ad,am=z-ad; + if(a5){L[U]=q(aH);L[U+1]=q(a0);L[U+u]=q(aj);L[U+u+1]=q(am)}else{L[U]=q(aj);L[U+1]=q(aH);L[U+u]=q(am); + L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==9216){}else throw F.toString(16)}}console.log(Date.now()-W)}return av}() + + + + UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { + var w = img.width, qw=w*4; + var io = 0, out = new Uint8Array(qw); + + while(io>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } - return; + + UTIF.decode._ljpeg_diff = function(data, prm, huff) { + var getbithuff = UTIF.decode._getbithuff; + var len, diff; + len = getbithuff(data, prm, huff[0], huff); + diff = getbithuff(data, prm, len, 0); + if ((diff & (1 << (len-1))) == 0) diff -= (1 << len) - 1; + return diff; } - - var pix = new Uint16Array(16); - var row, col, val, max, min, imax, imin, sh, bit, i, dp; - - var data = new Uint8Array(raw_width+1); - for (row=0; row < height; row++) { - //fread (data, 1, raw_width, ifp); - for(var j=0; j>> 11); - imax = 0x0f & (val >>> 22); - imin = 0x0f & (val >>> 26); - for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); - for (bit=30, i=0; i < 16; i++) - if (i == imax) pix[i] = max; - else if (i == imin) pix[i] = min; - else { - pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; - if (pix[i] > 0x7ff) pix[i] = 0x7ff; - bit += 7; + UTIF.decode._decodeARW = function(img, inp, off, src_length, tgt, toff) { + var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; + var bin=(img.isLE ? UTIF._binLE : UTIF._binBE); + //console.log(raw_width, height, tiff_bps, raw_width*height, src_length); + var arw2 = (raw_width*height == src_length) || (raw_width*height*1.5 == src_length); + //arw2 = true; + //console.log("ARW2: ", arw2, raw_width*height, src_length, tgt.length); + if(!arw2) { //"sony_arw_load_raw"; // not arw2 + height+=8; + var prm = [off,0,0,0]; + var huff = new Uint16Array(32770); + var tab = [ 0xf11,0xf10,0xe0f,0xd0e,0xc0d,0xb0c,0xa0b,0x90a,0x809, + 0x708,0x607,0x506,0x405,0x304,0x303,0x300,0x202,0x201 ]; + var i, c, n, col, row, sum=0; + var ljpeg_diff = UTIF.decode._ljpeg_diff; + + huff[0] = 15; + for (n=i=0; i < 18; i++) { + var lim = 32768 >>> (tab[i] >>> 8); + for(var c=0; c> 2; - var clr = pix[i]<<1; //clr = 0xffff; - UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); + return; + } + if(raw_width*height*1.5==src_length) { + //console.log("weird compression"); + for(var i=0; i>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } + return; + } + + var pix = new Uint16Array(16); + var row, col, val, max, min, imax, imin, sh, bit, i, dp; + + var data = new Uint8Array(raw_width+1); + for (row=0; row < height; row++) { + //fread (data, 1, raw_width, ifp); + for(var j=0; j>> 11); + imax = 0x0f & (val >>> 22); + imin = 0x0f & (val >>> 26); + for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); + for (bit=30, i=0; i < 16; i++) + if (i == imax) pix[i] = max; + else if (i == imin) pix[i] = min; + else { + pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; + if (pix[i] > 0x7ff) pix[i] = 0x7ff; + bit += 7; + } + for (i=0; i < 16; i++, col+=2) { + //RAW(row,col) = curve[pix[i] << 1] >> 2; + var clr = pix[i]<<1; //clr = 0xffff; + UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); + } + col -= col & 1 ? 1:31; } - col -= col & 1 ? 1:31; } } -} - -UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) -{ - var nikon_tree = [ - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ - 5,4,3,6,2,7,1,0,8,9,11,10,12 ], - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ - 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], - [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ - 5,4,6,3,7,2,8,1,9,0,10,11,12 ], - [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ - 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], - [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ - 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], - [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ - 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; - - var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; - - var tree = 0, split = 0; - var make_decoder = UTIF.decode._make_decoder; - var getbithuff = UTIF.decode._getbithuff; - - var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); - //console.log(md[0].toString(16), md[1].toString(16), tiff_bps); - var ver0 = md[mdo++], ver1 = md[mdo++]; - if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; - if (ver0 == 0x46) tree = 2; - if (tiff_bps == 14) tree += 3; - - var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); - for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] - //console.log(vpred); - - - var max = 1 << tiff_bps & 0x7fff, step=0; - var csize = bin.readShort(md,mdo); mdo+=2; - if (csize > 1) step = Math.floor(max / (csize-1)); - if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); - - - var i; - var row, col; - var len, shl, diff; - var min_v = 0; - var hpred = [0,0]; - var huff = make_decoder(nikon_tree[tree]); - - //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; - var prm = [off,0,0,0]; - //console.log(split); split = 170; - - for (min_v=row=0; row < height; row++) { - if (split && row == split) { - //free (huff); - huff = make_decoder (nikon_tree[tree+1]); - //max_v += (min_v = 16) << 1; - } - for (col=0; col < raw_width; col++) { - i = getbithuff(data,prm,huff[0],huff); - len = i & 15; - shl = i >>> 4; - diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; - if ((diff & (1 << (len-1))) == 0) - diff -= (1 << len) - (shl==0?1:0); - if (col < 2) hpred[col] = vpred[row & 1][col] += diff; - else hpred[col & 1] += diff; + + UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) + { + var nikon_tree = [ + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ + 5,4,3,6,2,7,1,0,8,9,11,10,12 ], + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ + 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], + [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ + 5,4,6,3,7,2,8,1,9,0,10,11,12 ], + [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ + 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], + [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ + 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], + [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ + 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; - var clr = Math.min(Math.max(hpred[col & 1],0),(1< 1) step = Math.floor(max / (csize-1)); + if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); + + + var i; + var row, col; + var len, shl, diff; + var min_v = 0; + var hpred = [0,0]; + var huff = make_decoder(nikon_tree[tree]); + + //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; + var prm = [off,0,0,0]; + //console.log(split); split = 170; + + for (min_v=row=0; row < height; row++) { + if (split && row == split) { + //free (huff); + huff = make_decoder (nikon_tree[tree+1]); + //max_v += (min_v = 16) << 1; + } + for (col=0; col < raw_width; col++) { + i = getbithuff(data,prm,huff[0],huff); + len = i & 15; + shl = i >>> 4; + diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - (shl==0?1:0); + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + + var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } - - -UTIF.decode._getbithuff = function(data,prm,nbits, huff) { - var zero_after_ff = 0; - var get_byte = UTIF.decode._get_byte; - var c; - - var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; - - //if (nbits > 25) return 0; - //if (nbits < 0) return bitbuf = vbits = reset = 0; - if (nbits == 0 || vbits < 0) return 0; - while (!reset && vbits < nbits && (c = data[off++]) != -1 && - !(reset = zero_after_ff && c == 0xff && data[off++])) { - //console.log("byte read into c"); - bitbuf = (bitbuf << 8) + c; - vbits += 8; - } - c = (bitbuf << (32-vbits)) >>> (32-nbits); - if (huff) { - vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); - c = huff[c+1]&255; - } else - vbits -= nbits; - if (vbits < 0) throw "e"; - - prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; - - return c; -} - -UTIF.decode._make_decoder = function(source) { - var max, len, h, i, j; - var huff = []; - - for (max=16; max!=0 && !source[max]; max--); - var si=17; - - huff[0] = max; - for (h=len=1; len <= max; len++) - for (i=0; i < source[len]; i++, ++si) - for (j=0; j < 1 << (max-len); j++) - if (h <= 1 << max) - huff[h++] = (len << 8) | source[si]; - return huff; -} - -UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff) -{ - len = Math.min(len, data.length-off); - var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); - - if (tables) { - var SOI = 216, EOI = 217, boff = 0; - for (var i=0; i<(tlen-1); i++) - { - // Skip EOI marker from JPEGTables - if (tables[i]==255 && tables[i+1]==EOI) break; - buff[boff++] = tables[i]; - } + // put 16 bits + UTIF.decode._putsF= function(dt, pos, val) { val = val<<(8-(pos&7)); var o=(pos>>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } - // Skip SOI marker from data - var byte1 = data[off], byte2 = data[off + 1]; - if (byte1!=255 || byte2!=SOI) - { - buff[boff++] = byte1; - buff[boff++] = byte2; - } - for (var i=2; i 25) return 0; + //if (nbits < 0) return bitbuf = vbits = reset = 0; + if (nbits == 0 || vbits < 0) return 0; + while (!reset && vbits < nbits && (c = data[off++]) != -1 && + !(reset = zero_after_ff && c == 0xff && data[off++])) { + //console.log("byte read into c"); + bitbuf = (bitbuf << 8) + c; + vbits += 8; + } + c = (bitbuf << (32-vbits)) >>> (32-nbits); + if (huff) { + vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); + c = huff[c+1]&255; + } else + vbits -= nbits; + if (vbits < 0) throw "e"; + + prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; + + return c; + } + + UTIF.decode._make_decoder = function(source) { + var max, len, h, i, j; + var huff = []; + + for (max=16; max!=0 && !source[max]; max--); + var si=17; + + huff[0] = max; + for (h=len=1; len <= max; len++) + for (i=0; i < source[len]; i++, ++si) + for (j=0; j < 1 << (max-len); j++) + if (h <= 1 << max) + huff[h++] = (len << 8) | source[si]; + return huff; } - else for (var i=0; i>>8); } - else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } + if (tables) { + var SOI = 216, EOI = 217, boff = 0; + for (var i=0; i<(tlen-1); i++) + { + // Skip EOI marker from JPEGTables + if (tables[i]==255 && tables[i+1]==EOI) break; + buff[boff++] = tables[i]; + } + + // Skip SOI marker from data + var byte1 = data[off], byte2 = data[off + 1]; + if (byte1!=255 || byte2!=SOI) + { + buff[boff++] = byte1; + buff[boff++] = byte2; + } + for (var i=2; i>>8); } + else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } + } + else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 + var rst = 16-bps; + for(var i=0; i 1); + // PhotometricInterpretation is 6 (YCbCr) for JPEG, but after decoding we populate data in + // RGB format, so updating the tag value + if(img["t262"][0] == 6) img["t262"][0] = 2; } - if(!isTiled) + UTIF.decode._decodeOldJPEGInit = function(img, data, off, len) { - if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; - if(jpgIchgFmt!=null) + var SOI = 216, EOI = 217, DQT = 219, DHT = 196, DRI = 221, SOF0 = 192, SOS = 218; + var joff = 0, soff = 0, tables, sosMarker, isTiled = false, i, j, k; + var jpgIchgFmt = img["t513"], jifoff = jpgIchgFmt ? jpgIchgFmt[0] : 0; + var jpgIchgFmtLen = img["t514"], jiflen = jpgIchgFmtLen ? jpgIchgFmtLen[0] : 0; + var soffTag = img["t324"] || img["t273"] || jpgIchgFmt; + var ycbcrss = img["t530"], ssx = 0, ssy = 0; + var spp = img["t277"]?img["t277"][0]:1; + var jpgresint = img["t515"]; + + if(soffTag) { - if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; - else log("JPEGInterchangeFormat does not point to SOI"); + soff = soffTag[0]; + isTiled = (soffTag.length > 1); + } + + if(!isTiled) + { + if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; + if(jpgIchgFmt!=null) + { + if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; + else log("JPEGInterchangeFormat does not point to SOI"); - if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); - else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); + if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); + else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); - if(joff != null) return { jpegOffset: joff }; + if(joff != null) return { jpegOffset: joff }; + } } - } - if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } + if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } + + if(jpgIchgFmt!=null) + if(jpgIchgFmtLen!=null) + if(jiflen >= 2 && (jifoff+jiflen) <= soff) + { + if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); + else tables = new Uint8Array(jiflen); + + for(i=0; i offset to first strip or tile"); + + if(tables == null) + { + var ooff = 0, out = []; + out[ooff++] = 255; out[ooff++] = SOI; - if(jpgIchgFmt!=null) - if(jpgIchgFmtLen!=null) - if(jiflen >= 2 && (jifoff+jiflen) <= soff) + var qtables = img["t519"]; + if(qtables==null) throw new Error("JPEGQTables tag is missing"); + for(i=0; i>> 8); out[ooff++] = nc & 255; + out[ooff++] = (i | (k << 4)); + for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; + for(j=0; j offset to first strip or tile"); - if(tables == null) - { - var ooff = 0, out = []; - out[ooff++] = 255; out[ooff++] = SOI; + out[ooff++] = 255; out[ooff++] = SOF0; + out[ooff++] = 0; out[ooff++] = 8 + 3*spp; out[ooff++] = 8; + out[ooff++] = (img.height >>> 8) & 255; out[ooff++] = img.height & 255; + out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; + out[ooff++] = spp; + if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } + else for(i=0; i<3; i++) + { + out[ooff++] = i + 1; + out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + out[ooff++] = i; + } - var qtables = img["t519"]; - if(qtables==null) throw new Error("JPEGQTables tag is missing"); - for(i=0; i>> 8) & 255; + out[ooff++] = jpgresint[0] & 255; + } + + tables = new Uint8Array(out); + } + + var sofpos = -1; + i = 0; + while(i < (tables.length - 1)) { + if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } + i++; } - for(k=0; k<2; k++) + if(sofpos == -1) { - var htables = img[(k == 0) ? "t520" : "t521"]; - if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing"); - for(i=0; i>> 8) & 255; tables[tmpoff++] = img.height & 255; + tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; + tables[tmpoff++] = spp; + if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } + else for(i=0; i<3; i++) { - out[ooff++] = 255; out[ooff++] = DHT; - //out[ooff++] = 0; out[ooff++] = 67; out[ooff++] = i; - var nc = 19; - for(j=0; j<16; j++) nc += data[off+htables[i]+j]; - - out[ooff++] = (nc >>> 8); out[ooff++] = nc & 255; - out[ooff++] = (i | (k << 4)); - for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; - for(j=0; j>> 8) & 255; out[ooff++] = img.height & 255; - out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; - out[ooff++] = spp; - if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } - else for(i=0; i<3; i++) + if(data[soff]==255 && data[soff+1]==SOS) { - out[ooff++] = i + 1; - out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - out[ooff++] = i; + var soslen = (data[soff+2]<<8) | data[soff+3]; + sosMarker = new Uint8Array(soslen+2); + sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; + for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; } - - if(jpgresint!=null && jpgresint[0]!=0) + else { - out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; - out[ooff++] = (jpgresint[0] >>> 8) & 255; - out[ooff++] = jpgresint[0] & 255; + sosMarker = new Uint8Array(2 + 6 + 2*spp); + var sosoff = 0; + sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; + if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } + else for(i=0; i<3; i++) + { + sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; + } + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; } - tables = new Uint8Array(out); - } - - var sofpos = -1; - i = 0; - while(i < (tables.length - 1)) { - if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } - i++; + return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; } - if(sofpos == -1) + UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) { - var tmptab = new Uint8Array(tables.length + 10 + 3*spp); - tmptab.set(tables); - var tmpoff = tables.length; - sofpos = tables.length; - tables = tmptab; - - tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; - tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; - tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; - tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; - tables[tmpoff++] = spp; - if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } - else for(i=0; i<3; i++) - { - tables[tmpoff++] = i + 1; - tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - tables[tmpoff++] = i; - } - } + var i, dlen, tlen, buff, buffoff; + var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); - if(data[soff]==255 && data[soff+1]==SOS) - { - var soslen = (data[soff+2]<<8) | data[soff+3]; - sosMarker = new Uint8Array(soslen+2); - sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; - for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; - } - else - { - sosMarker = new Uint8Array(2 + 6 + 2*spp); - var sosoff = 0; - sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; - if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } - else for(i=0; i<3; i++) + if(jpegData.jpegOffset!=null) { - sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; + dlen = off+len-jpegData.jpegOffset; + buff = new Uint8Array(dlen); + for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; + buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; -UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) -{ - var i, dlen, tlen, buff, buffoff; - var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); + if(data[off]!=255 || data[off+1]!=SOS) + { + buff.set(jpegData.sosMarker, buffoff); + buffoff += sosMarker.length; + } + for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; - buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; + // PhotometricInterpretation is 6 (YCbCr) for JPEG, but after decoding we populate data in + // RGB format, so updating the tag value + if(img["t262"] && img["t262"][0] == 6) img["t262"][0] = 2; + } - if(data[off]!=255 || data[off+1]!=SOS) + UTIF.decode._decodePackBits = function(data, off, len, tgt, toff) + { + var sa = new Int8Array(data.buffer), ta = new Int8Array(tgt.buffer), lim = off+len; + while(off=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } + if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } } - for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } - if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } - } - return toff; -} - -UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) -{ - var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; - var lim = off+len, qoff = toff*2, px = 0; - while(off>>6), n = (b&63); off++; - if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; + var lim = off+len, qoff = toff*2, px = 0; + while(off>>6), n = (b&63); off++; + if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + } } -} - -UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 }; -UTIF.decode._lens = ( function() -{ - var addKeys = function(lens, arr, i0, inc) { for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - if(mode=="H") - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } - } - } - else - { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } - } - if(line.length==w && mode=="") - { - U._writeBits(line, tgt, toff*8+y*bipl); - clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; - } - //if(wrd.length>150) { log(wrd); break; throw "e"; } - } -} - -UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } - -UTIF.decode._makeDiff = function(line) -{ - var out = []; if(line[0]==1) out.push(0,1); - for(var i=1; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - len = U._lens[clr][wrd]; - if(len!=null) { - U._addNtimes(line,len,clr); wrd=""; - if(len<64) clr = 1-clr; - if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } - } - } -} - -UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) -{ - var U = UTIF.decode, boff=off<<3, len=0, wrd=""; - var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; + var U = UTIF.decode, boff=off<<3, len=0, wrd=""; // previous starts with 1 + var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; if(mode=="H") { if(U._lens[clr][wrd]!=null) @@ -1121,545 +1042,630 @@ UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } } - } - if(wrd.endsWith("000000000001")) // needed for some files - { - if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); - if(twoDim) { - if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; - if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; - boff++; + if(line.length==w && mode=="") + { + U._writeBits(line, tgt, toff*8+y*bipl); + clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; } - //log("EOL",y, "next 1D:", is1D); - wrd=""; clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; - } - } - if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); -} - -UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); -} - -UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<150) { log(wrd); break; throw "e"; } } } -}(); -UTIF._readIFD = function(bin, data, offset, ifds, depth, prm) -{ - var cnt = bin.readUshort(data, offset); offset+=2; - var ifd = {}; + UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } - if(prm.debug) log(" ".repeat(depth),ifds.length-1,">>>----------------"); - for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } - if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); - if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); - else arr = new Uint8Array(data.buffer, o0, len); } - if(type== 3) { for(var j=0; j>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + len = U._lens[clr][wrd]; + if(len!=null) { + U._addNtimes(line,len,clr); wrd=""; + if(len<64) clr = 1-clr; + if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } + } } - if(tag==37500 && prm.parseMN) { - var mn = arr; - //console.log(bin.readASCII(mn,0,mn.length), mn); - if(bin.readASCII(mn,0,5)=="Nikon") ifd.makerNote = UTIF["decode"](mn.slice(10).buffer)[0]; - else if(bin.readASCII(mn,0,5)=="OLYMP" || bin.readASCII(mn,0,9)=="OM SYSTEM") { // ??? - var inds = [8208,8224,8240,8256,8272]; - var subsub = []; UTIF._readIFD(bin, mn, mn[1]==77 ? 16 : (mn[5]==85 ? 12 : 8), subsub, depth+1, prm); - var obj = ifd.makerNote = subsub.pop(); - for(var j=0; j>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + if(is1D) + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } + } + } + else + { + if(mode=="H") + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + } } - if(obj["t12288"]) { - UTIF._readIFD(bin, obj["t12288"], 0, subsub, depth+1, prm); - obj["t12288"]=subsub.pop(); + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } } } - else if(bin.readUshort(data,voff)<300 && bin.readUshort(data,voff+4)<=12){ - var subsub=[]; UTIF._readIFD(bin, data, voff, subsub, depth+1, prm); - ifd.makerNote = subsub[0]; + if(wrd.endsWith("000000000001")) // needed for some files + { + if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); + if(twoDim) { + if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; + if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; + boff++; + } + //log("EOL",y, "next 1D:", is1D); + wrd=""; clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; } } + if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); } - ifds.push(ifd); - if(prm.debug) log(" ".repeat(depth),"<<<---------------"); - return offset; -} - -UTIF._writeIFD = function(bin, types, data, offset, ifd) -{ - var keys = Object.keys(ifd), knum=keys.length; if(ifd["exifIFD"]) knum--; if(ifd["gpsiIFD"]) knum--; - bin.writeUshort(data, offset, knum); offset+=2; - var eoff = offset + knum*12 + 4; + UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i4) { bin.writeUint(data, offset, eoff); toff=eoff; } - - if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } - offset += 4; + for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); } - return [offset, eoff]; -} - -UTIF.toRGBA8 = function(out, scl) -{ - function gamma(x) { return x < 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; } - - - var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; - var img = new Uint8Array(area*4); - //console.log(out); - // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK - var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); - if(out["t262"]==null && bps==1) intp=0; - - var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); - var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format - var bpl = Math.ceil(smpls*bps*w/8); - - //log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); - - if(false) {} - else if(intp==0) - { - scl = 1/256; // "Photopeatest.tif" - for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } - if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } - if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } + var cnt = bin.readUshort(data, offset); offset+=2; + var ifd = {}; + + if(prm.debug) log(" ".repeat(depth),ifds.length-1,">>>----------------"); + for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } + if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); + if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); + else arr = new Uint8Array(data.buffer, o0, len); } + if(type== 3) { for(var j=0; j=4) for(var i=0; i4) { bin.writeUint(data, offset, eoff); toff=eoff; } + + if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } + offset += 4; } - else throw bps; + return [offset, eoff]; } - else if(intp==3) + + UTIF.toRGBA8 = function(out, scl) { - var map = out["t320"]; - var cn = 1<1 && out["t338"] && out["t338"][0]!=0; - for(var y=0; y>>3)]>>>(7- (x&7)))& 1; - else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; - else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; - else if(bps==8) mi= data[dof+x*smpls]; - else throw bps; - img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; + var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; + var img = new Uint8Array(area*4); + //console.log(out); + // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK + var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); + if(out["t262"]==null && bps==1) intp=0; + + var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); + var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format + var bpl = Math.ceil(smpls*bps*w/8); + + //log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); + + if(false) {} + else if(intp==0) + { + scl = 1/256; // "Photopeatest.tif" + for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } + if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } + if(bps== 8) for(var i=0; i4 ? 1 : 0; - for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } + if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } } - else { - var C=255-data[si], M=255-data[si+1], Y=255-data[si+2], K=(255-data[si+3])*(1/255); - img[qi]=~~(C*K+0.5); img[qi+1]=~~(M*K+0.5); img[qi+2]=~~(Y*K+0.5); + } + else if(intp==2) + { + if(bps== 8) + { + if(smpls==1) for(var i=0; i=4) for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; - for(var j=0; j>>1); - var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; + for(var y=0; y>>3)]>>>(7- (x&7)))& 1; + else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; + else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; + else if(bps==8) mi= data[dof+x*smpls]; + else throw bps; + img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; + } + } + else if(intp==5) + { + var gotAlpha = smpls>4 ? 1 : 0; + for(var i=0; i> 2) + (Cr >> 3) + (Cr >> 5) ) ; - var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; - var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; + if(window.UDOC) { + var C=data[si], M=data[si+1], Y=data[si+2], K=data[si+3]; + var c = UDOC.C.cmykToRgb([C*(1/255), M*(1/255), Y*(1/255), K*(1/255)]); + img[qi] = ~~(0.5+255*c[0]); img[qi+1] = ~~(0.5+255*c[1]); img[qi+2] = ~~(0.5+255*c[2]); + } + else { + var C=255-data[si], M=255-data[si+1], Y=255-data[si+2], K=(255-data[si+3])*(1/255); + img[qi]=~~(C*K+0.5); img[qi+1]=~~(M*K+0.5); img[qi+2]=~~(Y*K+0.5); + } - img[qi ]=Math.max(0,Math.min(255,r)); - img[qi+1]=Math.max(0,Math.min(255,g)); - img[qi+2]=Math.max(0,Math.min(255,b)); - img[qi+3]=255; + img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha; } } - } - else if(intp==32845) { - - for(var y=0; y>>1); + var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; + + var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; + var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; + var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; + + img[qi ]=Math.max(0,Math.min(255,r)); + img[qi+1]=Math.max(0,Math.min(255,g)); + img[qi+2]=Math.max(0,Math.min(255,b)); + img[qi+3]=255; + } } + } + else if(intp==32845) { + + for(var y=0; yma) { ma=ar; page=img; } + + UTIF._xhrs = []; UTIF._imgs = []; + UTIF._imgLoaded = function(e) { + var ind = UTIF._xhrs.indexOf(e.target), img = UTIF._imgs[ind]; + UTIF._xhrs.splice(ind,1); UTIF._imgs.splice(ind,1); + + img.setAttribute("src",UTIF.bufferToURI(e.target.response)); + } + + UTIF.bufferToURI = function(buff) { + var ifds = UTIF.decode(buff); //console.log(ifds); + var vsns = ifds, ma=0, page=vsns[0]; if(ifds[0].subIFD) vsns = vsns.concat(ifds[0].subIFD); + for(var i=0; ima) { ma=ar; page=img; } + } + UTIF.decodeImage(buff, page, ifds); + var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; + + var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; + var ctx = cnv.getContext("2d"); + var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); + ctx.putImageData(imgd,0,0); + return cnv.toDataURL(); + } + + + UTIF._binBE = + { + nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, + readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, + readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, + writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, + writeDouble: function(buff, p, n) + { + UTIF._binBE.fl64[0] = n; + for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; + } } - UTIF.decodeImage(buff, page, ifds); - var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; - - var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; - var ctx = cnv.getContext("2d"); - var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); - ctx.putImageData(imgd,0,0); - return cnv.toDataURL(); -} - - -UTIF._binBE = -{ - nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, - readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, - readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, - writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, - writeDouble: function(buff, p, n) + UTIF._binBE.ui8 = new Uint8Array (8); + UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); + UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer); + UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer); + UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer); + UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); + + UTIF._binLE = { - UTIF._binBE.fl64[0] = n; - for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; + nextZero : UTIF._binBE.nextZero, + readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, + readASCII : UTIF._binBE.readASCII, + readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, + readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, + + writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, + writeASCII : UTIF._binBE.writeASCII } -} -UTIF._binBE.ui8 = new Uint8Array (8); -UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); -UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer); -UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer); -UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer); -UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); - -UTIF._binLE = -{ - nextZero : UTIF._binBE.nextZero, - readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, - readASCII : UTIF._binBE.readASCII, - readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, - readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, - - writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, - writeASCII : UTIF._binBE.writeASCII -} -UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) -{ - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min(tw, w-xoff); - var ylim = Math.min(th, h-yoff); - for(var y=0; y>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); - var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; - w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; - h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; - cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; - d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; - if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); - d+=Y&15;while(w>>4; - if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); - n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; - while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; - H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; - H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; - return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); - (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; - V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; - N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; - N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); - H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); - n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); - - - -UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; -for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x>--s&1; -Y=I[Y+F]}E[w]=Y}}function z(h,V,I,f){if(h[V+3]!=255)return 0;if(I==0)return V;for(var w=0;w<2;w++){if(h[V+w]==0){h[V+w]=h.length; -h.push(0,0,f,255)}var x=z(h,h[V+w],I-1,f+1);if(x!=0)return x}return 0}function i(h){var V=h.b,I=h.f; -while(V<25&&h.a>(V.b-=h)&65535>>16-h}function g(h,V){var I=h[0],f=0,w=255,x=0;if(V.b<16)i(V);var T=V.f>>V.b-8&255; -f=h[1][T];w=I[f+3];V.b-=I[f+2];while(w==255){x=V.f>>--V.b&1;f=I[f+x];w=I[f+3]}return w}function P(h,V){if(h<32768>>16-V)h+=-(1<>4,J&15]}}else if(Y==65476){var a3=O+F-2;while(O>>4];x[v[0]]=v.slice(1)}I=l();O+=2;break}else if(Y==65501){w=m()}else{O+=F-2}}var a4=f>8?Uint16Array:Uint8Array,$=new a4(s*_*E),M={b:0,f:0,c:I==8,a:O,data:b,d:b.length,e:w}; -if(M.c)a1($,_*E,M,G[0],s);else{var c=[],p=0,D=0;for(var t=0;tp)p=S; -if(K>D)D=K;c.push(S*K)}if(p!=1||D!=1){if(E!=3||c[1]!=1||c[2]!=1)throw"e";if(p!=2||D!=1&&D!=2)throw"e"; -var u=[],Z=0;for(var t=0;t>>1)*B+(S>>>1))*Z,y=(K&1)*2+(S&1); -$[q]=n[k+y];$[q+1]=n[k+4];$[q+2]=n[k+5]}else for(var S=0;S<_;S++){var q=(K*_+S)*E,k=(K*B+(S>>>1))*Z,y=S&1; -$[q]=n[k+y];$[q+1]=n[k+2];$[q+2]=n[k+3]}}}else{X($,_*E,M,G,E,s);if(w==0)j($,I,_,s,0,E,E,f);else{var U=Math.floor(w/_); -for(var K=0;K>>1);else if(V==6)Q=h[J]+(r-h[J-G]>>>1);else if(V==7)Q=r+h[J]>>>1;else throw V; -h[a]+=Q}}}}return C}(); - - -(function(){var G=0,F=1,i=2,b=3,J=4,N=5,E=6,s=7,c=8,T=9,a3=10,f=11,q=12,M=13,m=14,x=15,L=16,$=17,p=18; -function a5(t){var Z=UTIF._binBE.readUshort,u={b:Z(t,0),i:t[2],C:t[3],u:t[4],q:Z(t,5),k:Z(t,7),e:Z(t,9),l:Z(t,11),s:t[13],d:Z(t,14)}; -if(u.b!=18771||u.i>1||u.q<6||u.q%6||u.e<768||u.e%24||u.l!=768||u.k=u.l||u.s>16||u.s!=u.k/u.l||u.s!=Math.ceil(u.e/u.l)||u.d!=u.q/6||u.u!=12&&u.u!=14&&u.u!=16||u.C!=16&&u.C!=0){throw"Invalid data"}if(u.i==0){throw"Not implemented. We need this file!"}u.h=u.C==16; -u.m=(u.h?u.l*2/3:u.l>>>1)|0;u.A=u.m+2;u.f=64;u.g=(1<>>6);for(var e=0;e<3;e++){for(var Q=0; -Q<41;Q++){Z[e][Q]=[u,1]}}return Z}function a4(t){for(var Z=-1,u=0;!u;Z++){u=t[t.j]>>>7-t.a&1;t.a++;t.a&=7; -if(!t.a)t.j++}return Z}function K(t,Z){var u=0,e=8-t.a,Q=t.j,V=t.a;if(Z){if(Z>=e){do{u<<=e;Z-=e;u|=t[t.j]&(1<=8)}if(Z){u<<=Z;e-=Z;u|=t[t.j]>>>e&(1<n&&C>>2;if(o){w[X]=h;return}l=Z.t*Z.c[t.g+Y-H]+Z.c[t.g+g-Y]}else{h=Y>g&&Y>P||Y>>2:A+v>>>1; -l=Z.t*Z.c[t.g+Y-g]+Z.c[t.g+g-A]}R=y(l);var W=a4(u);if(W>>1):a>>>1; -O[R][0]+=y(a);if(O[R][1]==t.f){O[R][0]>>>=1;O[R][1]>>>=1}O[R][1]++;h=l<0?h-a:h+a;if(t.i){if(h<0)h+=Z.w; -else if(h>t.g)h-=Z.w}w[X]=h>=0?Math.min(h,t.g):0}function U(t,Z,u){var e=t[0].length;for(var Q=Z;Q<=u; -Q++){t[Q][0]=t[Q-1][1];t[Q][e-1]=t[Q-1][e-2]}}function B(t){U(t,s,q);U(t,i,J);U(t,x,$)}function _(t,Z,u,e,Q,V,O,o,X,k,j,I,a){var l=0,R=1,w=QJ; -while(R8){r(t,Z,u,e,Q,R,o[X]);r(t,Z,u,e,V,R,o[X]);R+=2}}B(e)}function a8(t,Z,u,e,Q,V){_(t,Z,u,e,i,s,Q,V,0,0,1,0,8); -_(t,Z,u,e,c,x,Q,V,1,0,1,0,8);_(t,Z,u,e,b,T,Q,V,2,1,0,3,0);_(t,Z,u,e,a3,L,Q,V,0,0,0,3,2);_(t,Z,u,e,J,f,Q,V,1,0,0,3,2); -_(t,Z,u,e,q,$,Q,V,2,1,0,3,0)}function a9(t,Z,u,e,Q,V){var O=V.length,o=t.l;if(Q+1==t.s)o=t.e-Q*t.l;var X=6*t.e*e+Q*t.l; -for(var k=0;k<6;k++){for(var j=0;j>>1)}else if(I==2){a=x+(k>>>1)}else{a=s+k}var l=t.h?(j*2/3&2147483646|j%3&1)+(j%3>>>1):j>>>1; -Z[X+j]=u[a][l+1]}X+=t.e}}UTIF._decompressRAF=function(t,Z){var u=a5(t),e=a7(t,u),Q=a2(u),V=new Int16Array(u.e*u.q); -if(Z==null){Z=u.h?[[1,1,0,1,1,2],[1,1,2,1,1,0],[2,0,1,0,2,1],[1,1,2,1,1,0],[1,1,0,1,1,2],[0,2,1,2,0,1]]:[[0,1],[3,2]]}var O=[[G,b],[F,J],[N,f],[E,q],[M,L],[m,$]],o=[]; -for(var X=0;X>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); + var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; + w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; + h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; + cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; + d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; + if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); + d+=Y&15;while(w>>4; + if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); + n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; + while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; + H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; + H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; + return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); + (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; + V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; + N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; + N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); + H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); + n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); + + + + UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; + for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x>--s&1; + Y=I[Y+F]}E[w]=Y}}function z(h,V,I,f){if(h[V+3]!=255)return 0;if(I==0)return V;for(var w=0;w<2;w++){if(h[V+w]==0){h[V+w]=h.length; + h.push(0,0,f,255)}var x=z(h,h[V+w],I-1,f+1);if(x!=0)return x}return 0}function i(h){var V=h.b,I=h.f; + while(V<25&&h.a>(V.b-=h)&65535>>16-h}function g(h,V){var I=h[0],f=0,w=255,x=0;if(V.b<16)i(V);var T=V.f>>V.b-8&255; + f=h[1][T];w=I[f+3];V.b-=I[f+2];while(w==255){x=V.f>>--V.b&1;f=I[f+x];w=I[f+3]}return w}function P(h,V){if(h<32768>>16-V)h+=-(1<>4,J&15]}}else if(Y==65476){var a3=O+F-2;while(O>>4];x[v[0]]=v.slice(1)}I=l();O+=2;break}else if(Y==65501){w=m()}else{O+=F-2}}var a4=f>8?Uint16Array:Uint8Array,$=new a4(s*_*E),M={b:0,f:0,c:I==8,a:O,data:b,d:b.length,e:w}; + if(M.c)a1($,_*E,M,G[0],s);else{var c=[],p=0,D=0;for(var t=0;tp)p=S; + if(K>D)D=K;c.push(S*K)}if(p!=1||D!=1){if(E!=3||c[1]!=1||c[2]!=1)throw"e";if(p!=2||D!=1&&D!=2)throw"e"; + var u=[],Z=0;for(var t=0;t>>1)*B+(S>>>1))*Z,y=(K&1)*2+(S&1); + $[q]=n[k+y];$[q+1]=n[k+4];$[q+2]=n[k+5]}else for(var S=0;S<_;S++){var q=(K*_+S)*E,k=(K*B+(S>>>1))*Z,y=S&1; + $[q]=n[k+y];$[q+1]=n[k+2];$[q+2]=n[k+3]}}}else{X($,_*E,M,G,E,s);if(w==0)j($,I,_,s,0,E,E,f);else{var U=Math.floor(w/_); + for(var K=0;K>>1);else if(V==6)Q=h[J]+(r-h[J-G]>>>1);else if(V==7)Q=r+h[J]>>>1;else throw V; + h[a]+=Q}}}}return C}(); + + + (function(){var G=0,F=1,i=2,b=3,J=4,N=5,E=6,s=7,c=8,T=9,a3=10,f=11,q=12,M=13,m=14,x=15,L=16,$=17,p=18; + function a5(t){var Z=UTIF._binBE.readUshort,u={b:Z(t,0),i:t[2],C:t[3],u:t[4],q:Z(t,5),k:Z(t,7),e:Z(t,9),l:Z(t,11),s:t[13],d:Z(t,14)}; + if(u.b!=18771||u.i>1||u.q<6||u.q%6||u.e<768||u.e%24||u.l!=768||u.k=u.l||u.s>16||u.s!=u.k/u.l||u.s!=Math.ceil(u.e/u.l)||u.d!=u.q/6||u.u!=12&&u.u!=14&&u.u!=16||u.C!=16&&u.C!=0){throw"Invalid data"}if(u.i==0){throw"Not implemented. We need this file!"}u.h=u.C==16; + u.m=(u.h?u.l*2/3:u.l>>>1)|0;u.A=u.m+2;u.f=64;u.g=(1<>>6);for(var e=0;e<3;e++){for(var Q=0; + Q<41;Q++){Z[e][Q]=[u,1]}}return Z}function a4(t){for(var Z=-1,u=0;!u;Z++){u=t[t.j]>>>7-t.a&1;t.a++;t.a&=7; + if(!t.a)t.j++}return Z}function K(t,Z){var u=0,e=8-t.a,Q=t.j,V=t.a;if(Z){if(Z>=e){do{u<<=e;Z-=e;u|=t[t.j]&(1<=8)}if(Z){u<<=Z;e-=Z;u|=t[t.j]>>>e&(1<n&&C>>2;if(o){w[X]=h;return}l=Z.t*Z.c[t.g+Y-H]+Z.c[t.g+g-Y]}else{h=Y>g&&Y>P||Y>>2:A+v>>>1; + l=Z.t*Z.c[t.g+Y-g]+Z.c[t.g+g-A]}R=y(l);var W=a4(u);if(W>>1):a>>>1; + O[R][0]+=y(a);if(O[R][1]==t.f){O[R][0]>>>=1;O[R][1]>>>=1}O[R][1]++;h=l<0?h-a:h+a;if(t.i){if(h<0)h+=Z.w; + else if(h>t.g)h-=Z.w}w[X]=h>=0?Math.min(h,t.g):0}function U(t,Z,u){var e=t[0].length;for(var Q=Z;Q<=u; + Q++){t[Q][0]=t[Q-1][1];t[Q][e-1]=t[Q-1][e-2]}}function B(t){U(t,s,q);U(t,i,J);U(t,x,$)}function _(t,Z,u,e,Q,V,O,o,X,k,j,I,a){var l=0,R=1,w=QJ; + while(R8){r(t,Z,u,e,Q,R,o[X]);r(t,Z,u,e,V,R,o[X]);R+=2}}B(e)}function a8(t,Z,u,e,Q,V){_(t,Z,u,e,i,s,Q,V,0,0,1,0,8); + _(t,Z,u,e,c,x,Q,V,1,0,1,0,8);_(t,Z,u,e,b,T,Q,V,2,1,0,3,0);_(t,Z,u,e,a3,L,Q,V,0,0,0,3,2);_(t,Z,u,e,J,f,Q,V,1,0,0,3,2); + _(t,Z,u,e,q,$,Q,V,2,1,0,3,0)}function a9(t,Z,u,e,Q,V){var O=V.length,o=t.l;if(Q+1==t.s)o=t.e-Q*t.l;var X=6*t.e*e+Q*t.l; + for(var k=0;k<6;k++){for(var j=0;j>>1)}else if(I==2){a=x+(k>>>1)}else{a=s+k}var l=t.h?(j*2/3&2147483646|j%3&1)+(j%3>>>1):j>>>1; + Z[X+j]=u[a][l+1]}X+=t.e}}UTIF._decompressRAF=function(t,Z){var u=a5(t),e=a7(t,u),Q=a2(u),V=new Int16Array(u.e*u.q); + if(Z==null){Z=u.h?[[1,1,0,1,1,2],[1,1,2,1,1,0],[2,0,1,0,2,1],[1,1,2,1,1,0],[1,1,0,1,1,2],[0,2,1,2,0,1]]:[[0,1],[3,2]]}var O=[[G,b],[F,J],[N,f],[E,q],[M,L],[m,$]],o=[]; + for(var X=0;X Date: Sat, 7 Oct 2023 08:52:51 -0500 Subject: [PATCH 14/45] upng: create and tree-shake lib --- examples/jsm/libs/upng.module.js | 945 +++++++++++++++++++++++++++ examples/jsm/loaders/LogLuvLoader.js | 564 +--------------- examples/jsm/loaders/RGBMLoader.js | 938 +------------------------- 3 files changed, 947 insertions(+), 1500 deletions(-) create mode 100644 examples/jsm/libs/upng.module.js diff --git a/examples/jsm/libs/upng.module.js b/examples/jsm/libs/upng.module.js new file mode 100644 index 00000000000000..1c8d12607b7db1 --- /dev/null +++ b/examples/jsm/libs/upng.module.js @@ -0,0 +1,945 @@ +// from https://github.com/photopea/UPNG.js (MIT License) + +const UPNG = /* @__PURE__ */ ( () => { + + var UPNG = {}; + + UPNG.toRGBA8 = function ( out ) { + + var w = out.width, h = out.height; + if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; + + var frms = []; + if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; + + var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); + for ( var i = 0; i < out.frames.length; i ++ ) { + + var frm = out.frames[ i ]; + var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; + var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); + + if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; + + if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); + + frms.push( img.buffer.slice( 0 ) ); + + if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; + + } + + return frms; + + }; + + UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { + + var area = w * h, bpp = UPNG.decode._getBPP( out ); + var bpl = Math.ceil( w * bpp / 8 ); // bytes per line + + var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); + var ctype = out.ctype, depth = out.depth; + var rs = UPNG._bin.readUshort; + + if ( ctype == 6 ) { // RGB + alpha + + var qarea = area << 2; + if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { + + bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { + + bf[ i ] = data[ i << 1 ]; + + } + + } else if ( ctype == 2 ) { // RGB + + var ts = out.tabs[ 'tRNS' ]; + if ( ts == null ) { + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + + } + + } else { + + var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + + } + + } + + } else if ( ctype == 3 ) { // palette + + var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; + //console.log(p, ap); + if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } else if ( ctype == 4 ) { // gray + alpha + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; + + } + + } else if ( ctype == 0 ) { // gray + + var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; + for ( var y = 0; y < h; y ++ ) { + + var off = y * bpl, to = y * w; + if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { + + var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { + + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + + } + + } + + //console.log(Date.now()-time); + return bf; + + }; + + + + UPNG.decode = function ( buff ) { + + var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; + var out = { tabs: {}, frames: [] }; + var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it + var fd, foff = 0; // frames + var text, keyw, bfr; + + var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; + for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); + + while ( offset < data.length ) { + + var len = bin.readUint( data, offset ); offset += 4; + var type = bin.readASCII( data, offset, 4 ); offset += 4; + //console.log(type,len); + + if ( type == 'IHDR' ) { + + UPNG.decode._IHDR( data, offset, out ); + + } else if ( type == 'CgBI' ) { + + out.tabs[ type ] = data.slice( offset, offset + 4 ); + + } else if ( type == 'IDAT' ) { + + for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; + doff += len; + + } else if ( type == 'acTL' ) { + + out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; + fd = new Uint8Array( data.length ); + + } else if ( type == 'fcTL' ) { + + if ( foff != 0 ) { + + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + + } + + var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; + var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); + var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; + //console.log(frm); + out.frames.push( frm ); + + } else if ( type == 'fdAT' ) { + + for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; + foff += len - 4; + + } else if ( type == 'pHYs' ) { + + out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; + + } else if ( type == 'cHRM' ) { + + out.tabs[ type ] = []; + for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); + + } else if ( type == 'tEXt' || type == 'zTXt' ) { + + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = bin.nextZero( data, offset ); + keyw = bin.readASCII( data, offset, nz - offset ); + var tl = offset + len - nz - 1; + if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); + else { + + bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); + + } + + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'iTXt' ) { + + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = 0, off = offset; + nz = bin.nextZero( data, off ); + keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; + var cflag = data[ off ]; off += 2; + nz = bin.nextZero( data, off ); + bin.readASCII( data, off, nz - off ); off = nz + 1; + nz = bin.nextZero( data, off ); + bin.readUTF8( data, off, nz - off ); off = nz + 1; + var tl = len - ( off - offset ); + if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); + else { + + bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); + + } + + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'PLTE' ) { + + out.tabs[ type ] = bin.readBytes( data, offset, len ); + + } else if ( type == 'hIST' ) { + + var pl = out.tabs[ 'PLTE' ].length / 3; + out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); + + } else if ( type == 'tRNS' ) { + + if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); + else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); + else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + //else console.log("tRNS for unsupported color type",out.ctype, len); + + } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; + else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; + else if ( type == 'bKGD' ) { + + if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; + else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; + + } else if ( type == 'IEND' ) { + + break; + + } + + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len; + bin.readUint( data, offset ); offset += 4; + + } + + if ( foff != 0 ) { + + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); + + } + + out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); + + delete out.compress; delete out.interlace; delete out.filter; + return out; + + }; + + UPNG.decode._decompress = function ( out, dd, w, h ) { + + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); + if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); + else dd = UPNG.decode._inflate( dd, buff ); + + if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); + else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); + + return dd; + + }; + + UPNG.decode._inflate = function ( data, buff ) { + + var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; + + }; + + UPNG.inflateRaw = function () { + + var H = {}; H.H = {}; H.H.N = function ( N, W ) { + + var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; + if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; + if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { + + i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { + + if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); + var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; + w += q; continue + ; + + } + + if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { + + v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; + + } + + if ( m == 2 ) { + + J = A( N, d, 5 ) + 257; + h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { + + b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; + + } + + for ( var c = 0; + c < Q; c ++ ) { + + var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K + ; + + } + + d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; + d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); + I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + ; + + } + + while ( ! 0 ) { + + var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { + + W[ w ++ ] = p; + + } else if ( p == 256 ) { + + break; + + } else { + + var z = w + p - 254; + if ( p > 264 ) { + + var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; + + } + + var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); + d += Y & 15; while ( w < z ) { + + W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; + + } + + w = z + ; + + } + + } + + } + + return W.length == w ? W : W.slice( 0, w ) + ; + + }; + + H.H.W = function ( N, W ) { + + var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; + + }; + + H.H.R = function ( N, W, R, V, n, A ) { + + var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { + + var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; + if ( b <= 15 ) { + + A[ I ] = b; I ++; + + } else { + + var Z = 0, m = 0; if ( b == 16 ) { + + m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; + + } else if ( b == 17 ) { + + m = 3 + l( V, n, 3 ); + n += 3 + ; + + } else if ( b == 18 ) { + + m = 11 + l( V, n, 7 ); n += 7; + + } + + var J = I + m; while ( I < J ) { + + A[ I ] = Z; I ++; + + } + + } + + } + + return n + ; + + }; + + H.H.V = function ( N, W, R, V ) { + + var n = 0, A = 0, l = V.length >>> 1; + while ( A < R ) { + + var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; + + } + + while ( A < l ) { + + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + + } + + return n + ; + + }; + + H.H.n = function ( N, W ) { + + var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; + var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { + + n = n + e[ A - 1 ] << 1; b[ A ] = n; + + } + + for ( l = 0; l < V; l += 2 ) { + + I = N[ l + 1 ]; if ( I != 0 ) { + + N[ l ] = b[ I ]; + b[ I ] ++ + ; + + } + + } + + }; + + H.H.A = function ( N, W, R ) { + + var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { + + var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); + while ( Z != m ) { + + var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; + + } + + } + + }; + + H.H.l = function ( N, W ) { + + var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; + n += 2 ) { + + var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; + + } + + }; + + H.H.M = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + + }; + + H.H.I = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; + + }; + + H.H.e = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + H.H.b = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + H.H.Z = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + + }; + + H.H.i = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); + + }; + + H.H.m = function () { + + var N = Uint16Array, W = Uint32Array; + return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } + ; + + }(); + ( function () { + + var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { + + var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; + V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; + N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 + ; + + } + + function n( A, l, M ) { + + while ( l -- != 0 )A.push( 0, M ) + ; + + } + + for ( var R = 0; R < 32; R ++ ) { + + N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; + N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] + ; + + } + + n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); + H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); + n( N.D, 30, 0 ); n( N.v, 320, 0 ) + ; + + }() ); + + return H.H.N + ; + + }(); + + + UPNG.decode._readInterlace = function ( data, out ) { + + var w = out.width, h = out.height; + var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); + var img = new Uint8Array( h * bpl ); + var di = 0; + + var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; + var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; + var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; + var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; + + var pass = 0; + while ( pass < 7 ) { + + var ri = row_increment[ pass ], ci = col_increment[ pass ]; + var sw = 0, sh = 0; + var cr = starting_row[ pass ]; while ( cr < h ) { + + cr += ri; sh ++; + + } + + var cc = starting_col[ pass ]; while ( cc < w ) { + + cc += ci; sw ++; + + } + + var bpll = Math.ceil( sw * bpp / 8 ); + UPNG.decode._filterZero( data, out, di, sw, sh ); + + var y = 0, row = starting_row[ pass ]; + var val; + + while ( row < h ) { + + var col = starting_col[ pass ]; + var cdi = ( di + y * bpll ) << 3; + + while ( col < w ) { + + if ( bpp == 1 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; + img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); + + } + + if ( bpp == 2 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; + img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); + + } + + if ( bpp == 4 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; + img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); + + } + + if ( bpp >= 8 ) { + + var ii = row * bpl + col * cbpp; + for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; + + } + + cdi += bpp; col += ci; + + } + + y ++; row += ri; + + } + + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; + + } + + return img; + + }; + + UPNG.decode._getBPP = function ( out ) { + + var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; + return noc * out.depth; + + }; + + UPNG.decode._filterZero = function ( data, out, off, w, h ) { + + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; + bpp = Math.ceil( bpp / 8 ); + + var i, di, type = data[ off ], x = 0; + + if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; + if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; + + for ( var y = 0; y < h; y ++ ) { + + i = off + y * bpl; di = i + y + 1; + type = data[ di - 1 ]; x = 0; + + if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; + else if ( type == 1 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); + + } else if ( type == 2 ) { + + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); + + } else if ( type == 3 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); + + } else { + + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); + + } + + } + + return data; + + }; + + UPNG.decode._paeth = function ( a, b, c ) { + + var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); + if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; + else if ( pb * pb <= pc * pc ) return b; + return c; + + }; + + UPNG.decode._IHDR = function ( data, offset, out ) { + + var bin = UPNG._bin; + out.width = bin.readUint( data, offset ); offset += 4; + out.height = bin.readUint( data, offset ); offset += 4; + out.depth = data[ offset ]; offset ++; + out.ctype = data[ offset ]; offset ++; + out.compress = data[ offset ]; offset ++; + out.filter = data[ offset ]; offset ++; + out.interlace = data[ offset ]; offset ++; + + }; + + UPNG._bin = { + nextZero: function ( data, p ) { + + while ( data[ p ] != 0 ) p ++; return p; + + }, + readUshort: function ( buff, p ) { + + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + + }, + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + + }, + readUint: function ( buff, p ) { + + return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; + + }, + readASCII: function ( buff, p, l ) { + + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + + }, + writeASCII: function ( data, p, s ) { + + for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); + + }, + readBytes: function ( buff, p, l ) { + + var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; + + }, + pad: function ( n ) { + + return n.length < 2 ? '0' + n : n; + + }, + readUTF8: function ( buff, p, l ) { + + var s = '', ns; + for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); + try { + + ns = decodeURIComponent( s ); + + } catch ( e ) { + + return UPNG._bin.readASCII( buff, p, l ); + + } + + return ns; + + } + }; + UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { + + var w = Math.min( sw, tw ), h = Math.min( sh, th ); + var si = 0, ti = 0; + for ( var y = 0; y < h; y ++ ) + for ( var x = 0; x < w; x ++ ) { + + if ( xoff >= 0 && yoff >= 0 ) { + + si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; + + } else { + + si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; + + } + + if ( mode == 0 ) { + + tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; + + } else if ( mode == 1 ) { + + var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; + var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; + + var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); + tb[ ti + 3 ] = 255 * oa; + tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; + tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; + tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; + + } else if ( mode == 2 ) { // copy only differences, otherwise zero + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) { + + tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; + + } else { + + tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; + + } + + } else if ( mode == 3 ) { // check if can be blended + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; + //if(fa!=255 && ba!=0) return false; + if ( fa < 220 && ba > 20 ) return false; + + } + + } + + return true; + + }; + + return UPNG; + +} ); + +export default UPNG; +export { UPNG }; diff --git a/examples/jsm/loaders/LogLuvLoader.js b/examples/jsm/loaders/LogLuvLoader.js index 310cdd6bd58418..34654d76c62fdb 100644 --- a/examples/jsm/loaders/LogLuvLoader.js +++ b/examples/jsm/loaders/LogLuvLoader.js @@ -1,10 +1,9 @@ import { - DataUtils, DataTextureLoader, - FloatType, HalfFloatType, RGBAFormat } from 'three'; +import UTIF from '../libs/utif.module.js'; class LogLuvLoader extends DataTextureLoader { @@ -42,565 +41,4 @@ class LogLuvLoader extends DataTextureLoader { } -// from https://github.com/photopea/UTIF.js (MIT License) - -const UTIF = {}; - -UTIF.decode = function ( buff, prm ) { - - if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug - var data = new Uint8Array( buff ), offset = 0; - - var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; - var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; - bin.readUshort( data, offset ); offset += 2; - - var ifdo = bin.readUint( data, offset ); - var ifds = []; - while ( true ) { - - var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { - - console.log( 'error in TIFF' ); break; - - } - - - UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); - - ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); - if ( ifdo == 0 ) break; - - } - - return ifds; - -}; - -UTIF.decodeImage = function ( buff, img, ifds ) { - - if ( img.data ) return; - var data = new Uint8Array( buff ); - var id = UTIF._binBE.readASCII( data, 0, 2 ); - - if ( img[ 't256' ] == null ) return; // No width => probably not an image - img.isLE = id == 'II'; - img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; - img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; - - var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; - var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; - if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); - if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); - - var bipp; // bits per pixel - if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; - else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { - - bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); - - } - - var bipl = Math.ceil( img.width * bipp / 8 ) * 8; - var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; - var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; - - if ( img[ 't322' ] != null ) { - - var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; - var tx = Math.floor( ( img.width + tw - 1 ) / tw ); - var ty = Math.floor( ( img.height + th - 1 ) / th ); - var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); - for ( var y = 0; y < ty; y ++ ) - for ( var x = 0; x < tx; x ++ ) { - - var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); - // Might be required for 7 too. Need to check - if ( cmpr == 6 ) bytes = tbuff; - else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); - - } - - bilen = bytes.length * 8; - - } else { - - var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); - for ( var i = 0; i < soff.length; i ++ ) { - - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); - bilen += bipl * rps; - - } - - bilen = Math.min( bilen, bytes.length * 8 ); - - } - - img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); - -}; - -UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { - - //console.log("compression", cmpr); - //var time = Date.now(); - if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); - else console.log( 'Unsupported compression', cmpr ); - - //console.log(Date.now()-time); - - var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); - var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); - - // convert to Little Endian /* - if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG - for ( var y = 0; y < h; y ++ ) { - - //console.log("fixing endianity"); - var roff = toff + y * bpl; - for ( var x = 1; x < bpl; x += 2 ) { - - var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; - - } - - } //*/ - - if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { - - for ( var y = 0; y < h; y ++ ) { - - var ntoff = toff + y * bpl; - if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { - - var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); - tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; - - } - else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { - - tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; - tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; - tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; - - } - else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; - - } - - } - -}; - -UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { - - var w = img.width, qw = w * 4; - var io = 0, out = new Uint8Array( qw ); - - while ( io < len ) { - - var oo = 0; - while ( oo < qw ) { - - var c = data[ off + io ]; io ++; - if ( c < 128 ) { - - for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; - - } else { - - c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; - - } - - } - - for ( var x = 0; x < w; x ++ ) { - - tgt[ toff + 0 ] = out[ x ]; - tgt[ toff + 1 ] = out[ x + w ]; - tgt[ toff + 2 ] = out[ x + w * 2 ]; - tgt[ toff + 4 ] = out[ x + w * 3 ]; - toff += 6; - - } - - } - -}; - -UTIF.tags = {}; -//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; -// start at tag 250 -UTIF._types = function () { - - var main = new Array( 250 ); main.fill( 0 ); - main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); - var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; - return { - basic: { - main: main, - rest: rest - }, - gps: { - main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], - rest: { 18: 2, 29: 2 } - } - }; - -}(); - -UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { - - var cnt = bin.readUshort( data, offset ); offset += 2; - var ifd = {}; - - if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); - for ( var i = 0; i < cnt; i ++ ) { - - var tag = bin.readUshort( data, offset ); offset += 2; - var type = bin.readUshort( data, offset ); offset += 2; - var num = bin.readUint( data, offset ); offset += 4; - var voff = bin.readUint( data, offset ); offset += 4; - - var arr = []; - //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; - if ( type == 1 || type == 7 ) { - - arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); - - } - - if ( type == 2 ) { - - var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); - if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); - else arr = new Uint8Array( data.buffer, o0, len ); - - } - - if ( type == 3 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - - } - - if ( type == 4 - || type == 13 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - - } - - if ( type == 5 || type == 10 ) { - - var ri = type == 5 ? bin.readUint : bin.readInt; - for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); - - } - - if ( type == 8 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - - } - - if ( type == 9 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - - } - - if ( type == 11 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); - - } - - if ( type == 12 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); - - } - - if ( num != 0 && arr.length == 0 ) { - - console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; - - } - - if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); - - ifd[ 't' + tag ] = arr; - - if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { - - var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; - var subfd = []; - for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); - if ( tag == 330 ) ifd.subIFD = subfd; - if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; - if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } - if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; - if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; - - } - - if ( tag == 37500 && prm.parseMN ) { - - var mn = arr; - //console.log(bin.readASCII(mn,0,mn.length), mn); - if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; - else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { - - var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); - ifd.makerNote = subsub[ 0 ]; - - } - - } - - } - - ifds.push( ifd ); - if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); - return offset; - -}; - -UTIF.toRGBA = function ( out, type ) { - - const w = out.width, h = out.height, area = w * h, data = out.data; - - let img; - - switch ( type ) { - - case HalfFloatType: - - img = new Uint16Array( area * 4 ); - break; - - case FloatType: - - img = new Float32Array( area * 4 ); - break; - - default: - throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); - - } - - let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; - const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; - - if ( out[ 't262' ] == null && bps == 1 ) intp = 0; - - if ( intp == 32845 ) { - - for ( let y = 0; y < h; y ++ ) { - - for ( let x = 0; x < w; x ++ ) { - - const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; - let L = ( data[ si + 1 ] << 8 ) | data[ si ]; - - L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); - const u = ( data[ si + 3 ] + 0.5 ) / 410; - const v = ( data[ si + 5 ] + 0.5 ) / 410; - - // Luv to xyY - const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); - const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); - const bY = L; - - // xyY to XYZ - const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; - - // XYZ to linear RGB - const r = 2.690 * X - 1.276 * Y - 0.414 * Z; - const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; - const b = 0.061 * X - 0.224 * Y + 1.163 * Z; - - if ( type === HalfFloatType ) { - - img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); - img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); - img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); - img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); - - - } else { - - img[ qi ] = r; - img[ qi + 1 ] = g; - img[ qi + 2 ] = b; - img[ qi + 3 ] = 1; - - } - - } - - } - - } else { - - throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); - - } - - return img; - -}; - -UTIF._binBE = -{ - nextZero: function ( data, o ) { - - while ( data[ o ] != 0 ) o ++; return o; - - }, - readUshort: function ( buff, p ) { - - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - - }, - readShort: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; - - }, - readInt: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; - - }, - readUint: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; - - }, - readASCII: function ( buff, p, l ) { - - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - - }, - readFloat: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; - - }, - readDouble: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; - - }, - - writeUshort: function ( buff, p, n ) { - - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - - }, - writeInt: function ( buff, p, n ) { - - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; - - }, - writeUint: function ( buff, p, n ) { - - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; - - }, - writeASCII: function ( buff, p, s ) { - - for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); - - }, - writeDouble: function ( buff, p, n ) { - - UTIF._binBE.fl64[ 0 ] = n; - for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; - - } -}; -UTIF._binBE.ui8 = new Uint8Array( 8 ); -UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); - -UTIF._binLE = -{ - nextZero: UTIF._binBE.nextZero, - readUshort: function ( buff, p ) { - - return ( buff[ p + 1 ] << 8 ) | buff[ p ]; - - }, - readShort: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; - - }, - readInt: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; - - }, - readUint: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; - - }, - readASCII: UTIF._binBE.readASCII, - readFloat: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; - - }, - readDouble: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; - - }, - - writeUshort: function ( buff, p, n ) { - - buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; - - }, - writeInt: function ( buff, p, n ) { - - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; - - }, - writeUint: function ( buff, p, n ) { - - buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; - - }, - writeASCII: UTIF._binBE.writeASCII -}; -UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { - - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min( tw, w - xoff ); - var ylim = Math.min( th, h - yoff ); - for ( var y = 0; y < ylim; y ++ ) { - - var tof = ( yoff + y ) * w + xoff; - var sof = y * tw; - for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; - - } - -}; - export { LogLuvLoader }; diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index 44420c47869f37..d14d1eaebaf212 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -6,6 +6,7 @@ import { HalfFloatType, DataUtils } from 'three'; +import UPNG from '../libs/upng.module.js'; class RGBMLoader extends DataTextureLoader { @@ -125,941 +126,4 @@ class RGBMLoader extends DataTextureLoader { } -// from https://github.com/photopea/UPNG.js (MIT License) - -var UPNG = {}; - -UPNG.toRGBA8 = function ( out ) { - - var w = out.width, h = out.height; - if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; - - var frms = []; - if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; - - var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); - for ( var i = 0; i < out.frames.length; i ++ ) { - - var frm = out.frames[ i ]; - var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; - var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); - - if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; - - if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); - - frms.push( img.buffer.slice( 0 ) ); - - if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; - - } - - return frms; - -}; - -UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { - - var area = w * h, bpp = UPNG.decode._getBPP( out ); - var bpl = Math.ceil( w * bpp / 8 ); // bytes per line - - var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); - var ctype = out.ctype, depth = out.depth; - var rs = UPNG._bin.readUshort; - - if ( ctype == 6 ) { // RGB + alpha - - var qarea = area << 2; - if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { - - bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; - - } - - if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { - - bf[ i ] = data[ i << 1 ]; - - } - - } else if ( ctype == 2 ) { // RGB - - var ts = out.tabs[ 'tRNS' ]; - if ( ts == null ) { - - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - - } - - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - - var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - - } - - } else { - - var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; - - } - - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; - - } - - } - - } else if ( ctype == 3 ) { // palette - - var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; - //console.log(p, ap); - if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { - - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { - - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } - - if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { - - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { - - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } - - if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { - - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { - - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } - - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } else if ( ctype == 4 ) { // gray + alpha - - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; - - } - - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; - - } - - } else if ( ctype == 0 ) { // gray - - var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; - for ( var y = 0; y < h; y ++ ) { - - var off = y * bpl, to = y * w; - if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { - - var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { - - var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { - - var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { - - var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { - - var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - - } - - } - - //console.log(Date.now()-time); - return bf; - -}; - - - -UPNG.decode = function ( buff ) { - - var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; - var out = { tabs: {}, frames: [] }; - var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it - var fd, foff = 0; // frames - var text, keyw, bfr; - - var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; - for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); - - while ( offset < data.length ) { - - var len = bin.readUint( data, offset ); offset += 4; - var type = bin.readASCII( data, offset, 4 ); offset += 4; - //console.log(type,len); - - if ( type == 'IHDR' ) { - - UPNG.decode._IHDR( data, offset, out ); - - } else if ( type == 'CgBI' ) { - - out.tabs[ type ] = data.slice( offset, offset + 4 ); - - } else if ( type == 'IDAT' ) { - - for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; - doff += len; - - } else if ( type == 'acTL' ) { - - out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; - fd = new Uint8Array( data.length ); - - } else if ( type == 'fcTL' ) { - - if ( foff != 0 ) { - - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; - - } - - var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; - var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); - var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; - //console.log(frm); - out.frames.push( frm ); - - } else if ( type == 'fdAT' ) { - - for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; - foff += len - 4; - - } else if ( type == 'pHYs' ) { - - out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; - - } else if ( type == 'cHRM' ) { - - out.tabs[ type ] = []; - for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); - - } else if ( type == 'tEXt' || type == 'zTXt' ) { - - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = bin.nextZero( data, offset ); - keyw = bin.readASCII( data, offset, nz - offset ); - var tl = offset + len - nz - 1; - if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); - else { - - bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); - - } - - out.tabs[ type ][ keyw ] = text; - - } else if ( type == 'iTXt' ) { - - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = 0, off = offset; - nz = bin.nextZero( data, off ); - keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; - var cflag = data[ off ]; off += 2; - nz = bin.nextZero( data, off ); - bin.readASCII( data, off, nz - off ); off = nz + 1; - nz = bin.nextZero( data, off ); - bin.readUTF8( data, off, nz - off ); off = nz + 1; - var tl = len - ( off - offset ); - if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); - else { - - bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); - - } - - out.tabs[ type ][ keyw ] = text; - - } else if ( type == 'PLTE' ) { - - out.tabs[ type ] = bin.readBytes( data, offset, len ); - - } else if ( type == 'hIST' ) { - - var pl = out.tabs[ 'PLTE' ].length / 3; - out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); - - } else if ( type == 'tRNS' ) { - - if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); - else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); - else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - //else console.log("tRNS for unsupported color type",out.ctype, len); - - } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; - else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; - else if ( type == 'bKGD' ) { - - if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; - else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; - - } else if ( type == 'IEND' ) { - - break; - - } - - //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } - offset += len; - bin.readUint( data, offset ); offset += 4; - - } - - if ( foff != 0 ) { - - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); - - } - - out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); - - delete out.compress; delete out.interlace; delete out.filter; - return out; - -}; - -UPNG.decode._decompress = function ( out, dd, w, h ) { - - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); - if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); - else dd = UPNG.decode._inflate( dd, buff ); - - if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); - else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); - - return dd; - -}; - -UPNG.decode._inflate = function ( data, buff ) { - - var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; - -}; - -UPNG.inflateRaw = function () { - - var H = {}; H.H = {}; H.H.N = function ( N, W ) { - - var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; - if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; - if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { - - i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { - - if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); - var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; - w += q; continue - ; - - } - - if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { - - v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; - - } - - if ( m == 2 ) { - - J = A( N, d, 5 ) + 257; - h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { - - b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; - - } - - for ( var c = 0; - c < Q; c ++ ) { - - var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K - ; - - } - - d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; - d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); - I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) - ; - - } - - while ( ! 0 ) { - - var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { - - W[ w ++ ] = p; - - } else if ( p == 256 ) { - - break; - - } else { - - var z = w + p - 254; - if ( p > 264 ) { - - var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; - - } - - var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); - d += Y & 15; while ( w < z ) { - - W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; - - } - - w = z - ; - - } - - } - - } - - return W.length == w ? W : W.slice( 0, w ) - ; - - }; - - H.H.W = function ( N, W ) { - - var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; - - }; - - H.H.R = function ( N, W, R, V, n, A ) { - - var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { - - var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; - if ( b <= 15 ) { - - A[ I ] = b; I ++; - - } else { - - var Z = 0, m = 0; if ( b == 16 ) { - - m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; - - } else if ( b == 17 ) { - - m = 3 + l( V, n, 3 ); - n += 3 - ; - - } else if ( b == 18 ) { - - m = 11 + l( V, n, 7 ); n += 7; - - } - - var J = I + m; while ( I < J ) { - - A[ I ] = Z; I ++; - - } - - } - - } - - return n - ; - - }; - - H.H.V = function ( N, W, R, V ) { - - var n = 0, A = 0, l = V.length >>> 1; - while ( A < R ) { - - var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; - - } - - while ( A < l ) { - - V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; - - } - - return n - ; - - }; - - H.H.n = function ( N, W ) { - - var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; - var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { - - n = n + e[ A - 1 ] << 1; b[ A ] = n; - - } - - for ( l = 0; l < V; l += 2 ) { - - I = N[ l + 1 ]; if ( I != 0 ) { - - N[ l ] = b[ I ]; - b[ I ] ++ - ; - - } - - } - - }; - - H.H.A = function ( N, W, R ) { - - var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { - - var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); - while ( Z != m ) { - - var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; - - } - - } - - }; - - H.H.l = function ( N, W ) { - - var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; - n += 2 ) { - - var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; - - } - - }; - - H.H.M = function ( N, W, R ) { - - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; - - }; - - H.H.I = function ( N, W, R ) { - - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; - - }; - - H.H.e = function ( N, W, R ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - - }; - - H.H.b = function ( N, W, R ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - - }; - - H.H.Z = function ( N, W ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); - - }; - - H.H.i = function ( N, W ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); - - }; - - H.H.m = function () { - - var N = Uint16Array, W = Uint32Array; - return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } - ; - - }(); - ( function () { - - var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { - - var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; - V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; - N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 - ; - - } - - function n( A, l, M ) { - - while ( l -- != 0 )A.push( 0, M ) - ; - - } - - for ( var R = 0; R < 32; R ++ ) { - - N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; - N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] - ; - - } - - n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); - H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); - n( N.D, 30, 0 ); n( N.v, 320, 0 ) - ; - - }() ); - - return H.H.N - ; - -}(); - - -UPNG.decode._readInterlace = function ( data, out ) { - - var w = out.width, h = out.height; - var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); - var img = new Uint8Array( h * bpl ); - var di = 0; - - var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; - var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; - var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; - var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; - - var pass = 0; - while ( pass < 7 ) { - - var ri = row_increment[ pass ], ci = col_increment[ pass ]; - var sw = 0, sh = 0; - var cr = starting_row[ pass ]; while ( cr < h ) { - - cr += ri; sh ++; - - } - - var cc = starting_col[ pass ]; while ( cc < w ) { - - cc += ci; sw ++; - - } - - var bpll = Math.ceil( sw * bpp / 8 ); - UPNG.decode._filterZero( data, out, di, sw, sh ); - - var y = 0, row = starting_row[ pass ]; - var val; - - while ( row < h ) { - - var col = starting_col[ pass ]; - var cdi = ( di + y * bpll ) << 3; - - while ( col < w ) { - - if ( bpp == 1 ) { - - val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; - img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); - - } - - if ( bpp == 2 ) { - - val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; - img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); - - } - - if ( bpp == 4 ) { - - val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; - img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); - - } - - if ( bpp >= 8 ) { - - var ii = row * bpl + col * cbpp; - for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; - - } - - cdi += bpp; col += ci; - - } - - y ++; row += ri; - - } - - if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); - pass = pass + 1; - - } - - return img; - -}; - -UPNG.decode._getBPP = function ( out ) { - - var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; - return noc * out.depth; - -}; - -UPNG.decode._filterZero = function ( data, out, off, w, h ) { - - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; - bpp = Math.ceil( bpp / 8 ); - - var i, di, type = data[ off ], x = 0; - - if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; - if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; - - for ( var y = 0; y < h; y ++ ) { - - i = off + y * bpl; di = i + y + 1; - type = data[ di - 1 ]; x = 0; - - if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; - else if ( type == 1 ) { - - for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); - - } else if ( type == 2 ) { - - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); - - } else if ( type == 3 ) { - - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); - - } else { - - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); - - } - - } - - return data; - -}; - -UPNG.decode._paeth = function ( a, b, c ) { - - var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); - if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; - else if ( pb * pb <= pc * pc ) return b; - return c; - -}; - -UPNG.decode._IHDR = function ( data, offset, out ) { - - var bin = UPNG._bin; - out.width = bin.readUint( data, offset ); offset += 4; - out.height = bin.readUint( data, offset ); offset += 4; - out.depth = data[ offset ]; offset ++; - out.ctype = data[ offset ]; offset ++; - out.compress = data[ offset ]; offset ++; - out.filter = data[ offset ]; offset ++; - out.interlace = data[ offset ]; offset ++; - -}; - -UPNG._bin = { - nextZero: function ( data, p ) { - - while ( data[ p ] != 0 ) p ++; return p; - - }, - readUshort: function ( buff, p ) { - - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - - }, - writeUshort: function ( buff, p, n ) { - - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - - }, - readUint: function ( buff, p ) { - - return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); - - }, - writeUint: function ( buff, p, n ) { - - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; - - }, - readASCII: function ( buff, p, l ) { - - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - - }, - writeASCII: function ( data, p, s ) { - - for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); - - }, - readBytes: function ( buff, p, l ) { - - var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; - - }, - pad: function ( n ) { - - return n.length < 2 ? '0' + n : n; - - }, - readUTF8: function ( buff, p, l ) { - - var s = '', ns; - for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); - try { - - ns = decodeURIComponent( s ); - - } catch ( e ) { - - return UPNG._bin.readASCII( buff, p, l ); - - } - - return ns; - - } -}; -UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - - var w = Math.min( sw, tw ), h = Math.min( sh, th ); - var si = 0, ti = 0; - for ( var y = 0; y < h; y ++ ) - for ( var x = 0; x < w; x ++ ) { - - if ( xoff >= 0 && yoff >= 0 ) { - - si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; - - } else { - - si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; - - } - - if ( mode == 0 ) { - - tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; - - } else if ( mode == 1 ) { - - var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; - var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; - - var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); - tb[ ti + 3 ] = 255 * oa; - tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; - tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; - tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; - - } else if ( mode == 2 ) { // copy only differences, otherwise zero - - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) { - - tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; - - } else { - - tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; - - } - - } else if ( mode == 3 ) { // check if can be blended - - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; - //if(fa!=255 && ba!=0) return false; - if ( fa < 220 && ba > 20 ) return false; - - } - - } - - return true; - -}; - export { RGBMLoader }; From c67d55aeb7d07434ec838fd9811b42f6059aed40 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:06:38 -0500 Subject: [PATCH 15/45] chevrotain: tree-shake as IIFE --- examples/jsm/libs/chevrotain.module.min.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jsm/libs/chevrotain.module.min.js b/examples/jsm/libs/chevrotain.module.min.js index 424790cbae7ef3..eebcb5932ec4b3 100644 --- a/examples/jsm/libs/chevrotain.module.min.js +++ b/examples/jsm/libs/chevrotain.module.min.js @@ -1,5 +1,5 @@ /*! chevrotain - v9.0.1 */ -var R=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var Er=R(Pt=>{"use strict";Object.defineProperty(Pt,"__esModule",{value:!0});Pt.VERSION=void 0;Pt.VERSION="9.0.1"});var k=R((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(t,e){for(var r=0,n=e.length,i=t.length;r{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof St=="object"&&St.exports?St.exports=e():t.regexpToAst=e()})(typeof self!="undefined"?self:sn,function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(u){this.idx=u.idx,this.input=u.input,this.groupIdx=u.groupIdx},t.prototype.pattern=function(u){this.idx=0,this.input=u,this.groupIdx=0,this.consumeChar("/");var d=this.disjunction();this.consumeChar("/");for(var A={type:"Flags",loc:{begin:this.idx,end:u.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(A,"global");break;case"i":o(A,"ignoreCase");break;case"m":o(A,"multiLine");break;case"u":o(A,"unicode");break;case"y":o(A,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:A,value:d,loc:this.loc(0)}},t.prototype.disjunction=function(){var u=[],d=this.idx;for(u.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),u.push(this.alternative());return{type:"Disjunction",value:u,loc:this.loc(d)}},t.prototype.alternative=function(){for(var u=[],d=this.idx;this.isTerm();)u.push(this.term());return{type:"Alternative",value:u,loc:this.loc(d)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var u=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(u)};case"$":return{type:"EndAnchor",loc:this.loc(u)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(u)};case"B":return{type:"NonWordBoundary",loc:this.loc(u)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var d;switch(this.popChar()){case"=":d="Lookahead";break;case"!":d="NegativeLookahead";break}s(d);var A=this.disjunction();return this.consumeChar(")"),{type:d,value:A,loc:this.loc(u)}}c()},t.prototype.quantifier=function(u){var d,A=this.idx;switch(this.popChar()){case"*":d={atLeast:0,atMost:Infinity};break;case"+":d={atLeast:1,atMost:Infinity};break;case"?":d={atLeast:0,atMost:1};break;case"{":var _=this.integerIncludingZero();switch(this.popChar()){case"}":d={atLeast:_,atMost:_};break;case",":var g;this.isDigit()?(g=this.integerIncludingZero(),d={atLeast:_,atMost:g}):d={atLeast:_,atMost:Infinity},this.consumeChar("}");break}if(u===!0&&d===void 0)return;s(d);break}if(!(u===!0&&d===void 0))return s(d),this.peekChar(0)==="?"?(this.consumeChar("?"),d.greedy=!1):d.greedy=!0,d.type="Quantifier",d.loc=this.loc(A),d},t.prototype.atom=function(){var u,d=this.idx;switch(this.peekChar()){case".":u=this.dotAll();break;case"\\":u=this.atomEscape();break;case"[":u=this.characterClass();break;case"(":u=this.group();break}return u===void 0&&this.isPatternCharacter()&&(u=this.patternCharacter()),s(u),u.loc=this.loc(d),this.isQuantifier()&&(u.quantifier=this.quantifier()),u},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[i(` +const Fa=/*@__PURE__*/(() =>{var R=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var Er=R(Pt=>{"use strict";Object.defineProperty(Pt,"__esModule",{value:!0});Pt.VERSION=void 0;Pt.VERSION="9.0.1"});var k=R((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(t,e){for(var r=0,n=e.length,i=t.length;r{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof St=="object"&&St.exports?St.exports=e():t.regexpToAst=e()})(typeof self!="undefined"?self:sn,function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(u){this.idx=u.idx,this.input=u.input,this.groupIdx=u.groupIdx},t.prototype.pattern=function(u){this.idx=0,this.input=u,this.groupIdx=0,this.consumeChar("/");var d=this.disjunction();this.consumeChar("/");for(var A={type:"Flags",loc:{begin:this.idx,end:u.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(A,"global");break;case"i":o(A,"ignoreCase");break;case"m":o(A,"multiLine");break;case"u":o(A,"unicode");break;case"y":o(A,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:A,value:d,loc:this.loc(0)}},t.prototype.disjunction=function(){var u=[],d=this.idx;for(u.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),u.push(this.alternative());return{type:"Disjunction",value:u,loc:this.loc(d)}},t.prototype.alternative=function(){for(var u=[],d=this.idx;this.isTerm();)u.push(this.term());return{type:"Alternative",value:u,loc:this.loc(d)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var u=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(u)};case"$":return{type:"EndAnchor",loc:this.loc(u)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(u)};case"B":return{type:"NonWordBoundary",loc:this.loc(u)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var d;switch(this.popChar()){case"=":d="Lookahead";break;case"!":d="NegativeLookahead";break}s(d);var A=this.disjunction();return this.consumeChar(")"),{type:d,value:A,loc:this.loc(u)}}c()},t.prototype.quantifier=function(u){var d,A=this.idx;switch(this.popChar()){case"*":d={atLeast:0,atMost:Infinity};break;case"+":d={atLeast:1,atMost:Infinity};break;case"?":d={atLeast:0,atMost:1};break;case"{":var _=this.integerIncludingZero();switch(this.popChar()){case"}":d={atLeast:_,atMost:_};break;case",":var g;this.isDigit()?(g=this.integerIncludingZero(),d={atLeast:_,atMost:g}):d={atLeast:_,atMost:Infinity},this.consumeChar("}");break}if(u===!0&&d===void 0)return;s(d);break}if(!(u===!0&&d===void 0))return s(d),this.peekChar(0)==="?"?(this.consumeChar("?"),d.greedy=!1):d.greedy=!0,d.type="Quantifier",d.loc=this.loc(A),d},t.prototype.atom=function(){var u,d=this.idx;switch(this.peekChar()){case".":u=this.dotAll();break;case"\\":u=this.atomEscape();break;case"[":u=this.characterClass();break;case"(":u=this.group();break}return u===void 0&&this.isPatternCharacter()&&(u=this.patternCharacter()),s(u),u.loc=this.loc(d),this.isQuantifier()&&(u.quantifier=this.quantifier()),u},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[i(` `),i("\r"),i("\u2028"),i("\u2029")]}},t.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},t.prototype.decimalEscapeAtom=function(){var u=this.positiveInteger();return{type:"GroupBackReference",value:u}},t.prototype.characterClassEscape=function(){var u,d=!1;switch(this.popChar()){case"d":u=p;break;case"D":u=p,d=!0;break;case"s":u=m;break;case"S":u=m,d=!0;break;case"w":u=l;break;case"W":u=l,d=!0;break}return s(u),{type:"Set",value:u,complement:d}},t.prototype.controlEscapeAtom=function(){var u;switch(this.popChar()){case"f":u=i("\f");break;case"n":u=i(` `);break;case"r":u=i("\r");break;case"t":u=i(" ");break;case"v":u=i("\v");break}return s(u),{type:"Character",value:u}},t.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var u=this.popChar();if(/[a-zA-Z]/.test(u)===!1)throw Error("Invalid ");var d=u.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:d}},t.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:i("\0")}},t.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},t.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},t.prototype.identityEscapeAtom=function(){var u=this.popChar();return{type:"Character",value:i(u)}},t.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` `:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var u=this.popChar();return{type:"Character",value:i(u)}}},t.prototype.characterClass=function(){var u=[],d=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),d=!0);this.isClassAtom();){var A=this.classAtom(),_=A.type==="Character";if(_&&this.isRangeDash()){this.consumeChar("-");var g=this.classAtom(),y=g.type==="Character";if(y){if(g.value{"use strict";Object.defineProperty(E,"__esModule",{value:!0});E.Parser=E.createSyntaxDiagramsCode=E.clearCache=E.GAstVisitor=E.serializeProduction=E.serializeGrammar=E.Terminal=E.Rule=E.RepetitionWithSeparator=E.RepetitionMandatoryWithSeparator=E.RepetitionMandatory=E.Repetition=E.Option=E.NonTerminal=E.Alternative=E.Alternation=E.defaultLexerErrorProvider=E.NoViableAltException=E.NotAllInputParsedException=E.MismatchedTokenException=E.isRecognitionException=E.EarlyExitException=E.defaultParserErrorProvider=E.tokenName=E.tokenMatcher=E.tokenLabel=E.EOF=E.createTokenInstance=E.createToken=E.LexerDefinitionErrorType=E.Lexer=E.EMPTY_ALT=E.ParserDefinitionErrorType=E.EmbeddedActionsParser=E.CstParser=E.VERSION=void 0;var ku=Er();Object.defineProperty(E,"VERSION",{enumerable:!0,get:function(){return ku.VERSION}});var lr=ce();Object.defineProperty(E,"CstParser",{enumerable:!0,get:function(){return lr.CstParser}});Object.defineProperty(E,"EmbeddedActionsParser",{enumerable:!0,get:function(){return lr.EmbeddedActionsParser}});Object.defineProperty(E,"ParserDefinitionErrorType",{enumerable:!0,get:function(){return lr.ParserDefinitionErrorType}});Object.defineProperty(E,"EMPTY_ALT",{enumerable:!0,get:function(){return lr.EMPTY_ALT}});var Ma=ft();Object.defineProperty(E,"Lexer",{enumerable:!0,get:function(){return Ma.Lexer}});Object.defineProperty(E,"LexerDefinitionErrorType",{enumerable:!0,get:function(){return Ma.LexerDefinitionErrorType}});var nt=Ue();Object.defineProperty(E,"createToken",{enumerable:!0,get:function(){return nt.createToken}});Object.defineProperty(E,"createTokenInstance",{enumerable:!0,get:function(){return nt.createTokenInstance}});Object.defineProperty(E,"EOF",{enumerable:!0,get:function(){return nt.EOF}});Object.defineProperty(E,"tokenLabel",{enumerable:!0,get:function(){return nt.tokenLabel}});Object.defineProperty(E,"tokenMatcher",{enumerable:!0,get:function(){return nt.tokenMatcher}});Object.defineProperty(E,"tokenName",{enumerable:!0,get:function(){return nt.tokenName}});var Pu=mt();Object.defineProperty(E,"defaultParserErrorProvider",{enumerable:!0,get:function(){return Pu.defaultParserErrorProvider}});var Nt=et();Object.defineProperty(E,"EarlyExitException",{enumerable:!0,get:function(){return Nt.EarlyExitException}});Object.defineProperty(E,"isRecognitionException",{enumerable:!0,get:function(){return Nt.isRecognitionException}});Object.defineProperty(E,"MismatchedTokenException",{enumerable:!0,get:function(){return Nt.MismatchedTokenException}});Object.defineProperty(E,"NotAllInputParsedException",{enumerable:!0,get:function(){return Nt.NotAllInputParsedException}});Object.defineProperty(E,"NoViableAltException",{enumerable:!0,get:function(){return Nt.NoViableAltException}});var Su=kr();Object.defineProperty(E,"defaultLexerErrorProvider",{enumerable:!0,get:function(){return Su.defaultLexerErrorProvider}});var Se=ne();Object.defineProperty(E,"Alternation",{enumerable:!0,get:function(){return Se.Alternation}});Object.defineProperty(E,"Alternative",{enumerable:!0,get:function(){return Se.Alternative}});Object.defineProperty(E,"NonTerminal",{enumerable:!0,get:function(){return Se.NonTerminal}});Object.defineProperty(E,"Option",{enumerable:!0,get:function(){return Se.Option}});Object.defineProperty(E,"Repetition",{enumerable:!0,get:function(){return Se.Repetition}});Object.defineProperty(E,"RepetitionMandatory",{enumerable:!0,get:function(){return Se.RepetitionMandatory}});Object.defineProperty(E,"RepetitionMandatoryWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionMandatoryWithSeparator}});Object.defineProperty(E,"RepetitionWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionWithSeparator}});Object.defineProperty(E,"Rule",{enumerable:!0,get:function(){return Se.Rule}});Object.defineProperty(E,"Terminal",{enumerable:!0,get:function(){return Se.Terminal}});var ba=ne();Object.defineProperty(E,"serializeGrammar",{enumerable:!0,get:function(){return ba.serializeGrammar}});Object.defineProperty(E,"serializeProduction",{enumerable:!0,get:function(){return ba.serializeProduction}});var xu=$e();Object.defineProperty(E,"GAstVisitor",{enumerable:!0,get:function(){return xu.GAstVisitor}});function Cu(){console.warn(`The clearCache function was 'soft' removed from the Chevrotain API. It performs no action other than printing this message. Please avoid using it as it will be completely removed in the future`)}E.clearCache=Cu;var Lu=La();Object.defineProperty(E,"createSyntaxDiagramsCode",{enumerable:!0,get:function(){return Lu.createSyntaxDiagramsCode}});var Mu=function(){function t(){throw new Error(`The Parser class has been deprecated, use CstParser or EmbeddedActionsParser instead. -See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return t}();E.Parser=Mu});export default Fa(); +See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return t}();E.Parser=Mu});return Fa;})();export default/*@__PURE__*/Fa(); From 521a9f851d46117e3602786d0c1e8922e465c136 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:07:04 -0500 Subject: [PATCH 16/45] VRMLLoader: use factory method for tree-shaking --- examples/jsm/loaders/VRMLLoader.js | 280 ++++++++++++++--------------- 1 file changed, 136 insertions(+), 144 deletions(-) diff --git a/examples/jsm/loaders/VRMLLoader.js b/examples/jsm/loaders/VRMLLoader.js index e9df4fa36fad4b..5c2ab0684caae2 100644 --- a/examples/jsm/loaders/VRMLLoader.js +++ b/examples/jsm/loaders/VRMLLoader.js @@ -91,7 +91,7 @@ class VRMLLoader extends Loader { const tokenData = createTokens(); const lexer = new VRMLLexer( tokenData.tokens ); - const parser = new VRMLParser( tokenData.tokenVocabulary ); + const parser = createParser( tokenData.tokenVocabulary ); const visitor = createVisitor( parser.getBaseCstVisitorConstructor() ); // lexing @@ -3275,238 +3275,230 @@ class VRMLLexer { } -const CstParser = chevrotain.CstParser; +function createParser( tokenVocabulary ) { -class VRMLParser extends CstParser { + const $ = new chevrotain.CstParser( tokenVocabulary ); - constructor( tokenVocabulary ) { + const Version = tokenVocabulary[ 'Version' ]; + const LCurly = tokenVocabulary[ 'LCurly' ]; + const RCurly = tokenVocabulary[ 'RCurly' ]; + const LSquare = tokenVocabulary[ 'LSquare' ]; + const RSquare = tokenVocabulary[ 'RSquare' ]; + const Identifier = tokenVocabulary[ 'Identifier' ]; + const RouteIdentifier = tokenVocabulary[ 'RouteIdentifier' ]; + const StringLiteral = tokenVocabulary[ 'StringLiteral' ]; + const HexLiteral = tokenVocabulary[ 'HexLiteral' ]; + const NumberLiteral = tokenVocabulary[ 'NumberLiteral' ]; + const TrueLiteral = tokenVocabulary[ 'TrueLiteral' ]; + const FalseLiteral = tokenVocabulary[ 'FalseLiteral' ]; + const NullLiteral = tokenVocabulary[ 'NullLiteral' ]; + const DEF = tokenVocabulary[ 'DEF' ]; + const USE = tokenVocabulary[ 'USE' ]; + const ROUTE = tokenVocabulary[ 'ROUTE' ]; + const TO = tokenVocabulary[ 'TO' ]; + const NodeName = tokenVocabulary[ 'NodeName' ]; - super( tokenVocabulary ); + $.RULE( 'vrml', function () { - const $ = this; + $.SUBRULE( $.version ); + $.AT_LEAST_ONE( function () { - const Version = tokenVocabulary[ 'Version' ]; - const LCurly = tokenVocabulary[ 'LCurly' ]; - const RCurly = tokenVocabulary[ 'RCurly' ]; - const LSquare = tokenVocabulary[ 'LSquare' ]; - const RSquare = tokenVocabulary[ 'RSquare' ]; - const Identifier = tokenVocabulary[ 'Identifier' ]; - const RouteIdentifier = tokenVocabulary[ 'RouteIdentifier' ]; - const StringLiteral = tokenVocabulary[ 'StringLiteral' ]; - const HexLiteral = tokenVocabulary[ 'HexLiteral' ]; - const NumberLiteral = tokenVocabulary[ 'NumberLiteral' ]; - const TrueLiteral = tokenVocabulary[ 'TrueLiteral' ]; - const FalseLiteral = tokenVocabulary[ 'FalseLiteral' ]; - const NullLiteral = tokenVocabulary[ 'NullLiteral' ]; - const DEF = tokenVocabulary[ 'DEF' ]; - const USE = tokenVocabulary[ 'USE' ]; - const ROUTE = tokenVocabulary[ 'ROUTE' ]; - const TO = tokenVocabulary[ 'TO' ]; - const NodeName = tokenVocabulary[ 'NodeName' ]; + $.SUBRULE( $.node ); - $.RULE( 'vrml', function () { - - $.SUBRULE( $.version ); - $.AT_LEAST_ONE( function () { - - $.SUBRULE( $.node ); - - } ); - $.MANY( function () { - - $.SUBRULE( $.route ); + } ); + $.MANY( function () { - } ); + $.SUBRULE( $.route ); } ); - $.RULE( 'version', function () { + } ); - $.CONSUME( Version ); + $.RULE( 'version', function () { - } ); + $.CONSUME( Version ); - $.RULE( 'node', function () { + } ); - $.OPTION( function () { + $.RULE( 'node', function () { - $.SUBRULE( $.def ); + $.OPTION( function () { - } ); + $.SUBRULE( $.def ); - $.CONSUME( NodeName ); - $.CONSUME( LCurly ); - $.MANY( function () { + } ); - $.SUBRULE( $.field ); + $.CONSUME( NodeName ); + $.CONSUME( LCurly ); + $.MANY( function () { - } ); - $.CONSUME( RCurly ); + $.SUBRULE( $.field ); } ); + $.CONSUME( RCurly ); - $.RULE( 'field', function () { + } ); - $.CONSUME( Identifier ); + $.RULE( 'field', function () { - $.OR2( [ - { ALT: function () { + $.CONSUME( Identifier ); - $.SUBRULE( $.singleFieldValue ); + $.OR2( [ + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.singleFieldValue ); - $.SUBRULE( $.multiFieldValue ); + } }, + { ALT: function () { - } } - ] ); + $.SUBRULE( $.multiFieldValue ); - } ); + } } + ] ); - $.RULE( 'def', function () { + } ); - $.CONSUME( DEF ); - $.OR( [ - { ALT: function () { + $.RULE( 'def', function () { - $.CONSUME( Identifier ); + $.CONSUME( DEF ); + $.OR( [ + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( Identifier ); - $.CONSUME( NodeName ); + } }, + { ALT: function () { - } } - ] ); + $.CONSUME( NodeName ); - } ); + } } + ] ); - $.RULE( 'use', function () { + } ); - $.CONSUME( USE ); - $.OR( [ - { ALT: function () { + $.RULE( 'use', function () { - $.CONSUME( Identifier ); + $.CONSUME( USE ); + $.OR( [ + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( Identifier ); - $.CONSUME( NodeName ); + } }, + { ALT: function () { - } } - ] ); + $.CONSUME( NodeName ); - } ); + } } + ] ); - $.RULE( 'singleFieldValue', function () { + } ); - $.AT_LEAST_ONE( function () { + $.RULE( 'singleFieldValue', function () { - $.OR( [ - { ALT: function () { + $.AT_LEAST_ONE( function () { - $.SUBRULE( $.node ); + $.OR( [ + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.node ); - $.SUBRULE( $.use ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.use ); - $.CONSUME( StringLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( StringLiteral ); - $.CONSUME( HexLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( HexLiteral ); - $.CONSUME( NumberLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NumberLiteral ); - $.CONSUME( TrueLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( TrueLiteral ); - $.CONSUME( FalseLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( FalseLiteral ); - $.CONSUME( NullLiteral ); + } }, + { ALT: function () { - } } - ] ); + $.CONSUME( NullLiteral ); + } } + ] ); - } ); } ); - $.RULE( 'multiFieldValue', function () { + } ); - $.CONSUME( LSquare ); - $.MANY( function () { + $.RULE( 'multiFieldValue', function () { - $.OR( [ - { ALT: function () { + $.CONSUME( LSquare ); + $.MANY( function () { - $.SUBRULE( $.node ); + $.OR( [ + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.node ); - $.SUBRULE( $.use ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.use ); - $.CONSUME( StringLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( StringLiteral ); - $.CONSUME( HexLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( HexLiteral ); - $.CONSUME( NumberLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NumberLiteral ); - $.CONSUME( NullLiteral ); + } }, + { ALT: function () { - } } - ] ); + $.CONSUME( NullLiteral ); - } ); - $.CONSUME( RSquare ); + } } + ] ); } ); + $.CONSUME( RSquare ); - $.RULE( 'route', function () { + } ); - $.CONSUME( ROUTE ); - $.CONSUME( RouteIdentifier ); - $.CONSUME( TO ); - $.CONSUME2( RouteIdentifier ); + $.RULE( 'route', function () { - } ); + $.CONSUME( ROUTE ); + $.CONSUME( RouteIdentifier ); + $.CONSUME( TO ); + $.CONSUME2( RouteIdentifier ); - this.performSelfAnalysis(); + } ); - } + $.performSelfAnalysis(); } From ff8f72d9d08d23c1097ac3545c9a7e5bec289511 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:11:24 -0500 Subject: [PATCH 17/45] VRMLLoader: return parser instance in factory --- examples/jsm/loaders/VRMLLoader.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/jsm/loaders/VRMLLoader.js b/examples/jsm/loaders/VRMLLoader.js index 5c2ab0684caae2..0ca07f4cc772d4 100644 --- a/examples/jsm/loaders/VRMLLoader.js +++ b/examples/jsm/loaders/VRMLLoader.js @@ -3500,6 +3500,8 @@ function createParser( tokenVocabulary ) { $.performSelfAnalysis(); + return $; + } class Face { From 4b565aee21db7f6f087cf475dfedecb0344c09df Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:22:41 -0500 Subject: [PATCH 18/45] Keep loader libs inlined --- examples/jsm/libs/upng.module.js | 945 --------------------------- examples/jsm/loaders/LogLuvLoader.js | 570 +++++++++++++++- examples/jsm/loaders/RGBMLoader.js | 944 +++++++++++++++++++++++++- 3 files changed, 1512 insertions(+), 947 deletions(-) delete mode 100644 examples/jsm/libs/upng.module.js diff --git a/examples/jsm/libs/upng.module.js b/examples/jsm/libs/upng.module.js deleted file mode 100644 index 1c8d12607b7db1..00000000000000 --- a/examples/jsm/libs/upng.module.js +++ /dev/null @@ -1,945 +0,0 @@ -// from https://github.com/photopea/UPNG.js (MIT License) - -const UPNG = /* @__PURE__ */ ( () => { - - var UPNG = {}; - - UPNG.toRGBA8 = function ( out ) { - - var w = out.width, h = out.height; - if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; - - var frms = []; - if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; - - var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); - for ( var i = 0; i < out.frames.length; i ++ ) { - - var frm = out.frames[ i ]; - var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; - var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); - - if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; - - if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); - - frms.push( img.buffer.slice( 0 ) ); - - if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; - - } - - return frms; - - }; - - UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { - - var area = w * h, bpp = UPNG.decode._getBPP( out ); - var bpl = Math.ceil( w * bpp / 8 ); // bytes per line - - var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); - var ctype = out.ctype, depth = out.depth; - var rs = UPNG._bin.readUshort; - - if ( ctype == 6 ) { // RGB + alpha - - var qarea = area << 2; - if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { - - bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; - - } - - if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { - - bf[ i ] = data[ i << 1 ]; - - } - - } else if ( ctype == 2 ) { // RGB - - var ts = out.tabs[ 'tRNS' ]; - if ( ts == null ) { - - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - - } - - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - - var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - - } - - } else { - - var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; - - } - - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; - - } - - } - - } else if ( ctype == 3 ) { // palette - - var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; - //console.log(p, ap); - if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { - - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { - - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } - - if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { - - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { - - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } - - if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { - - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { - - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } - - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - - } - - } else if ( ctype == 4 ) { // gray + alpha - - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; - - } - - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - - var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; - - } - - } else if ( ctype == 0 ) { // gray - - var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; - for ( var y = 0; y < h; y ++ ) { - - var off = y * bpl, to = y * w; - if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { - - var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { - - var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { - - var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { - - var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { - - var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - - } - - } - - } - - //console.log(Date.now()-time); - return bf; - - }; - - - - UPNG.decode = function ( buff ) { - - var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; - var out = { tabs: {}, frames: [] }; - var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it - var fd, foff = 0; // frames - var text, keyw, bfr; - - var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; - for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); - - while ( offset < data.length ) { - - var len = bin.readUint( data, offset ); offset += 4; - var type = bin.readASCII( data, offset, 4 ); offset += 4; - //console.log(type,len); - - if ( type == 'IHDR' ) { - - UPNG.decode._IHDR( data, offset, out ); - - } else if ( type == 'CgBI' ) { - - out.tabs[ type ] = data.slice( offset, offset + 4 ); - - } else if ( type == 'IDAT' ) { - - for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; - doff += len; - - } else if ( type == 'acTL' ) { - - out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; - fd = new Uint8Array( data.length ); - - } else if ( type == 'fcTL' ) { - - if ( foff != 0 ) { - - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; - - } - - var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; - var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); - var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; - //console.log(frm); - out.frames.push( frm ); - - } else if ( type == 'fdAT' ) { - - for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; - foff += len - 4; - - } else if ( type == 'pHYs' ) { - - out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; - - } else if ( type == 'cHRM' ) { - - out.tabs[ type ] = []; - for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); - - } else if ( type == 'tEXt' || type == 'zTXt' ) { - - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = bin.nextZero( data, offset ); - keyw = bin.readASCII( data, offset, nz - offset ); - var tl = offset + len - nz - 1; - if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); - else { - - bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); - - } - - out.tabs[ type ][ keyw ] = text; - - } else if ( type == 'iTXt' ) { - - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = 0, off = offset; - nz = bin.nextZero( data, off ); - keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; - var cflag = data[ off ]; off += 2; - nz = bin.nextZero( data, off ); - bin.readASCII( data, off, nz - off ); off = nz + 1; - nz = bin.nextZero( data, off ); - bin.readUTF8( data, off, nz - off ); off = nz + 1; - var tl = len - ( off - offset ); - if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); - else { - - bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); - - } - - out.tabs[ type ][ keyw ] = text; - - } else if ( type == 'PLTE' ) { - - out.tabs[ type ] = bin.readBytes( data, offset, len ); - - } else if ( type == 'hIST' ) { - - var pl = out.tabs[ 'PLTE' ].length / 3; - out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); - - } else if ( type == 'tRNS' ) { - - if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); - else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); - else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - //else console.log("tRNS for unsupported color type",out.ctype, len); - - } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; - else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; - else if ( type == 'bKGD' ) { - - if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; - else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; - - } else if ( type == 'IEND' ) { - - break; - - } - - //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } - offset += len; - bin.readUint( data, offset ); offset += 4; - - } - - if ( foff != 0 ) { - - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); - - } - - out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); - - delete out.compress; delete out.interlace; delete out.filter; - return out; - - }; - - UPNG.decode._decompress = function ( out, dd, w, h ) { - - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); - if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); - else dd = UPNG.decode._inflate( dd, buff ); - - if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); - else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); - - return dd; - - }; - - UPNG.decode._inflate = function ( data, buff ) { - - var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; - - }; - - UPNG.inflateRaw = function () { - - var H = {}; H.H = {}; H.H.N = function ( N, W ) { - - var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; - if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; - if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { - - i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { - - if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); - var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; - w += q; continue - ; - - } - - if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { - - v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; - - } - - if ( m == 2 ) { - - J = A( N, d, 5 ) + 257; - h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { - - b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; - - } - - for ( var c = 0; - c < Q; c ++ ) { - - var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K - ; - - } - - d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; - d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); - I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) - ; - - } - - while ( ! 0 ) { - - var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { - - W[ w ++ ] = p; - - } else if ( p == 256 ) { - - break; - - } else { - - var z = w + p - 254; - if ( p > 264 ) { - - var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; - - } - - var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); - d += Y & 15; while ( w < z ) { - - W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; - - } - - w = z - ; - - } - - } - - } - - return W.length == w ? W : W.slice( 0, w ) - ; - - }; - - H.H.W = function ( N, W ) { - - var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; - - }; - - H.H.R = function ( N, W, R, V, n, A ) { - - var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { - - var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; - if ( b <= 15 ) { - - A[ I ] = b; I ++; - - } else { - - var Z = 0, m = 0; if ( b == 16 ) { - - m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; - - } else if ( b == 17 ) { - - m = 3 + l( V, n, 3 ); - n += 3 - ; - - } else if ( b == 18 ) { - - m = 11 + l( V, n, 7 ); n += 7; - - } - - var J = I + m; while ( I < J ) { - - A[ I ] = Z; I ++; - - } - - } - - } - - return n - ; - - }; - - H.H.V = function ( N, W, R, V ) { - - var n = 0, A = 0, l = V.length >>> 1; - while ( A < R ) { - - var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; - - } - - while ( A < l ) { - - V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; - - } - - return n - ; - - }; - - H.H.n = function ( N, W ) { - - var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; - var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { - - n = n + e[ A - 1 ] << 1; b[ A ] = n; - - } - - for ( l = 0; l < V; l += 2 ) { - - I = N[ l + 1 ]; if ( I != 0 ) { - - N[ l ] = b[ I ]; - b[ I ] ++ - ; - - } - - } - - }; - - H.H.A = function ( N, W, R ) { - - var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { - - var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); - while ( Z != m ) { - - var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; - - } - - } - - }; - - H.H.l = function ( N, W ) { - - var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; - n += 2 ) { - - var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; - - } - - }; - - H.H.M = function ( N, W, R ) { - - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; - - }; - - H.H.I = function ( N, W, R ) { - - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; - - }; - - H.H.e = function ( N, W, R ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - - }; - - H.H.b = function ( N, W, R ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - - }; - - H.H.Z = function ( N, W ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); - - }; - - H.H.i = function ( N, W ) { - - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); - - }; - - H.H.m = function () { - - var N = Uint16Array, W = Uint32Array; - return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } - ; - - }(); - ( function () { - - var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { - - var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; - V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; - N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 - ; - - } - - function n( A, l, M ) { - - while ( l -- != 0 )A.push( 0, M ) - ; - - } - - for ( var R = 0; R < 32; R ++ ) { - - N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; - N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] - ; - - } - - n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); - H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); - n( N.D, 30, 0 ); n( N.v, 320, 0 ) - ; - - }() ); - - return H.H.N - ; - - }(); - - - UPNG.decode._readInterlace = function ( data, out ) { - - var w = out.width, h = out.height; - var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); - var img = new Uint8Array( h * bpl ); - var di = 0; - - var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; - var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; - var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; - var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; - - var pass = 0; - while ( pass < 7 ) { - - var ri = row_increment[ pass ], ci = col_increment[ pass ]; - var sw = 0, sh = 0; - var cr = starting_row[ pass ]; while ( cr < h ) { - - cr += ri; sh ++; - - } - - var cc = starting_col[ pass ]; while ( cc < w ) { - - cc += ci; sw ++; - - } - - var bpll = Math.ceil( sw * bpp / 8 ); - UPNG.decode._filterZero( data, out, di, sw, sh ); - - var y = 0, row = starting_row[ pass ]; - var val; - - while ( row < h ) { - - var col = starting_col[ pass ]; - var cdi = ( di + y * bpll ) << 3; - - while ( col < w ) { - - if ( bpp == 1 ) { - - val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; - img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); - - } - - if ( bpp == 2 ) { - - val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; - img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); - - } - - if ( bpp == 4 ) { - - val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; - img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); - - } - - if ( bpp >= 8 ) { - - var ii = row * bpl + col * cbpp; - for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; - - } - - cdi += bpp; col += ci; - - } - - y ++; row += ri; - - } - - if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); - pass = pass + 1; - - } - - return img; - - }; - - UPNG.decode._getBPP = function ( out ) { - - var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; - return noc * out.depth; - - }; - - UPNG.decode._filterZero = function ( data, out, off, w, h ) { - - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; - bpp = Math.ceil( bpp / 8 ); - - var i, di, type = data[ off ], x = 0; - - if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; - if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; - - for ( var y = 0; y < h; y ++ ) { - - i = off + y * bpl; di = i + y + 1; - type = data[ di - 1 ]; x = 0; - - if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; - else if ( type == 1 ) { - - for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); - - } else if ( type == 2 ) { - - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); - - } else if ( type == 3 ) { - - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); - - } else { - - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); - - } - - } - - return data; - - }; - - UPNG.decode._paeth = function ( a, b, c ) { - - var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); - if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; - else if ( pb * pb <= pc * pc ) return b; - return c; - - }; - - UPNG.decode._IHDR = function ( data, offset, out ) { - - var bin = UPNG._bin; - out.width = bin.readUint( data, offset ); offset += 4; - out.height = bin.readUint( data, offset ); offset += 4; - out.depth = data[ offset ]; offset ++; - out.ctype = data[ offset ]; offset ++; - out.compress = data[ offset ]; offset ++; - out.filter = data[ offset ]; offset ++; - out.interlace = data[ offset ]; offset ++; - - }; - - UPNG._bin = { - nextZero: function ( data, p ) { - - while ( data[ p ] != 0 ) p ++; return p; - - }, - readUshort: function ( buff, p ) { - - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - - }, - writeUshort: function ( buff, p, n ) { - - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - - }, - readUint: function ( buff, p ) { - - return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); - - }, - writeUint: function ( buff, p, n ) { - - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; - - }, - readASCII: function ( buff, p, l ) { - - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - - }, - writeASCII: function ( data, p, s ) { - - for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); - - }, - readBytes: function ( buff, p, l ) { - - var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; - - }, - pad: function ( n ) { - - return n.length < 2 ? '0' + n : n; - - }, - readUTF8: function ( buff, p, l ) { - - var s = '', ns; - for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); - try { - - ns = decodeURIComponent( s ); - - } catch ( e ) { - - return UPNG._bin.readASCII( buff, p, l ); - - } - - return ns; - - } - }; - UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - - var w = Math.min( sw, tw ), h = Math.min( sh, th ); - var si = 0, ti = 0; - for ( var y = 0; y < h; y ++ ) - for ( var x = 0; x < w; x ++ ) { - - if ( xoff >= 0 && yoff >= 0 ) { - - si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; - - } else { - - si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; - - } - - if ( mode == 0 ) { - - tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; - - } else if ( mode == 1 ) { - - var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; - var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; - - var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); - tb[ ti + 3 ] = 255 * oa; - tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; - tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; - tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; - - } else if ( mode == 2 ) { // copy only differences, otherwise zero - - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) { - - tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; - - } else { - - tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; - - } - - } else if ( mode == 3 ) { // check if can be blended - - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; - //if(fa!=255 && ba!=0) return false; - if ( fa < 220 && ba > 20 ) return false; - - } - - } - - return true; - - }; - - return UPNG; - -} ); - -export default UPNG; -export { UPNG }; diff --git a/examples/jsm/loaders/LogLuvLoader.js b/examples/jsm/loaders/LogLuvLoader.js index 34654d76c62fdb..ddf6d3d737669f 100644 --- a/examples/jsm/loaders/LogLuvLoader.js +++ b/examples/jsm/loaders/LogLuvLoader.js @@ -1,9 +1,10 @@ import { + DataUtils, DataTextureLoader, + FloatType, HalfFloatType, RGBAFormat } from 'three'; -import UTIF from '../libs/utif.module.js'; class LogLuvLoader extends DataTextureLoader { @@ -41,4 +42,571 @@ class LogLuvLoader extends DataTextureLoader { } +// from https://github.com/photopea/UTIF.js (MIT License) + +const UTIF = /* @__PURE__ */ ( () => { + + const UTIF = {}; + + UTIF.decode = function ( buff, prm ) { + + if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug + var data = new Uint8Array( buff ), offset = 0; + + var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; + var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; + bin.readUshort( data, offset ); offset += 2; + + var ifdo = bin.readUint( data, offset ); + var ifds = []; + while ( true ) { + + var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { + + console.log( 'error in TIFF' ); break; + + } + + + UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); + + ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); + if ( ifdo == 0 ) break; + + } + + return ifds; + + }; + + UTIF.decodeImage = function ( buff, img, ifds ) { + + if ( img.data ) return; + var data = new Uint8Array( buff ); + var id = UTIF._binBE.readASCII( data, 0, 2 ); + + if ( img[ 't256' ] == null ) return; // No width => probably not an image + img.isLE = id == 'II'; + img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; + img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; + + var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; + var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; + if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); + if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); + + var bipp; // bits per pixel + if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; + else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { + + bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); + + } + + var bipl = Math.ceil( img.width * bipp / 8 ) * 8; + var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; + var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; + + if ( img[ 't322' ] != null ) { + + var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; + var tx = Math.floor( ( img.width + tw - 1 ) / tw ); + var ty = Math.floor( ( img.height + th - 1 ) / th ); + var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); + for ( var y = 0; y < ty; y ++ ) + for ( var x = 0; x < tx; x ++ ) { + + var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); + // Might be required for 7 too. Need to check + if ( cmpr == 6 ) bytes = tbuff; + else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); + + } + + bilen = bytes.length * 8; + + } else { + + var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); + for ( var i = 0; i < soff.length; i ++ ) { + + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); + bilen += bipl * rps; + + } + + bilen = Math.min( bilen, bytes.length * 8 ); + + } + + img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); + + }; + + UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { + + //console.log("compression", cmpr); + //var time = Date.now(); + if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); + else console.log( 'Unsupported compression', cmpr ); + + //console.log(Date.now()-time); + + var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); + var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); + + // convert to Little Endian /* + if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG + for ( var y = 0; y < h; y ++ ) { + + //console.log("fixing endianity"); + var roff = toff + y * bpl; + for ( var x = 1; x < bpl; x += 2 ) { + + var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; + + } + + } //*/ + + if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { + + for ( var y = 0; y < h; y ++ ) { + + var ntoff = toff + y * bpl; + if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { + + var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); + tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; + + } + else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + + tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; + tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; + tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + + } + else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; + + } + + } + + }; + + UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { + + var w = img.width, qw = w * 4; + var io = 0, out = new Uint8Array( qw ); + + while ( io < len ) { + + var oo = 0; + while ( oo < qw ) { + + var c = data[ off + io ]; io ++; + if ( c < 128 ) { + + for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; + + } else { + + c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; + + } + + } + + for ( var x = 0; x < w; x ++ ) { + + tgt[ toff + 0 ] = out[ x ]; + tgt[ toff + 1 ] = out[ x + w ]; + tgt[ toff + 2 ] = out[ x + w * 2 ]; + tgt[ toff + 4 ] = out[ x + w * 3 ]; + toff += 6; + + } + + } + + }; + + UTIF.tags = {}; + //UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; + // start at tag 250 + UTIF._types = function () { + + var main = new Array( 250 ); main.fill( 0 ); + main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); + var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; + return { + basic: { + main: main, + rest: rest + }, + gps: { + main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], + rest: { 18: 2, 29: 2 } + } + }; + + }(); + + UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { + + var cnt = bin.readUshort( data, offset ); offset += 2; + var ifd = {}; + + if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); + for ( var i = 0; i < cnt; i ++ ) { + + var tag = bin.readUshort( data, offset ); offset += 2; + var type = bin.readUshort( data, offset ); offset += 2; + var num = bin.readUint( data, offset ); offset += 4; + var voff = bin.readUint( data, offset ); offset += 4; + + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if ( type == 1 || type == 7 ) { + + arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); + + } + + if ( type == 2 ) { + + var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); + if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); + else arr = new Uint8Array( data.buffer, o0, len ); + + } + + if ( type == 3 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + + } + + if ( type == 4 + || type == 13 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + + } + + if ( type == 5 || type == 10 ) { + + var ri = type == 5 ? bin.readUint : bin.readInt; + for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); + + } + + if ( type == 8 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + + } + + if ( type == 9 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + + } + + if ( type == 11 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); + + } + + if ( type == 12 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); + + } + + if ( num != 0 && arr.length == 0 ) { + + console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; + + } + + if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); + + ifd[ 't' + tag ] = arr; + + if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { + + var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; + var subfd = []; + for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); + if ( tag == 330 ) ifd.subIFD = subfd; + if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; + if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } + if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; + if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; + + } + + if ( tag == 37500 && prm.parseMN ) { + + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; + else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { + + var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); + ifd.makerNote = subsub[ 0 ]; + + } + + } + + } + + ifds.push( ifd ); + if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); + return offset; + + }; + + UTIF.toRGBA = function ( out, type ) { + + const w = out.width, h = out.height, area = w * h, data = out.data; + + let img; + + switch ( type ) { + + case HalfFloatType: + + img = new Uint16Array( area * 4 ); + break; + + case FloatType: + + img = new Float32Array( area * 4 ); + break; + + default: + throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); + + } + + let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; + const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; + + if ( out[ 't262' ] == null && bps == 1 ) intp = 0; + + if ( intp == 32845 ) { + + for ( let y = 0; y < h; y ++ ) { + + for ( let x = 0; x < w; x ++ ) { + + const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; + let L = ( data[ si + 1 ] << 8 ) | data[ si ]; + + L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); + const u = ( data[ si + 3 ] + 0.5 ) / 410; + const v = ( data[ si + 5 ] + 0.5 ) / 410; + + // Luv to xyY + const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); + const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); + const bY = L; + + // xyY to XYZ + const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; + + // XYZ to linear RGB + const r = 2.690 * X - 1.276 * Y - 0.414 * Z; + const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; + const b = 0.061 * X - 0.224 * Y + 1.163 * Z; + + if ( type === HalfFloatType ) { + + img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); + img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); + img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); + img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); + + + } else { + + img[ qi ] = r; + img[ qi + 1 ] = g; + img[ qi + 2 ] = b; + img[ qi + 3 ] = 1; + + } + + } + + } + + } else { + + throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); + + } + + return img; + + }; + + UTIF._binBE = + { + nextZero: function ( data, o ) { + + while ( data[ o ] != 0 ) o ++; return o; + + }, + readUshort: function ( buff, p ) { + + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + + }, + readShort: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; + + }, + readInt: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; + + }, + readUint: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; + + }, + readASCII: function ( buff, p, l ) { + + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + + }, + readFloat: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; + + }, + readDouble: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; + + }, + + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + + }, + writeInt: function ( buff, p, n ) { + + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; + + }, + writeASCII: function ( buff, p, s ) { + + for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); + + }, + writeDouble: function ( buff, p, n ) { + + UTIF._binBE.fl64[ 0 ] = n; + for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; + + } + }; + UTIF._binBE.ui8 = new Uint8Array( 8 ); + UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); + + UTIF._binLE = + { + nextZero: UTIF._binBE.nextZero, + readUshort: function ( buff, p ) { + + return ( buff[ p + 1 ] << 8 ) | buff[ p ]; + + }, + readShort: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; + + }, + readInt: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; + + }, + readUint: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; + + }, + readASCII: UTIF._binBE.readASCII, + readFloat: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; + + }, + readDouble: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; + + }, + + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; + + }, + writeInt: function ( buff, p, n ) { + + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; + + }, + writeASCII: UTIF._binBE.writeASCII + }; + UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { + + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min( tw, w - xoff ); + var ylim = Math.min( th, h - yoff ); + for ( var y = 0; y < ylim; y ++ ) { + + var tof = ( yoff + y ) * w + xoff; + var sof = y * tw; + for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; + + } + + }; + + return UTIF; + +} )(); + export { LogLuvLoader }; diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index d14d1eaebaf212..771c285199eb1c 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -6,7 +6,6 @@ import { HalfFloatType, DataUtils } from 'three'; -import UPNG from '../libs/upng.module.js'; class RGBMLoader extends DataTextureLoader { @@ -126,4 +125,947 @@ class RGBMLoader extends DataTextureLoader { } +// from https://github.com/photopea/UPNG.js (MIT License) + +const UPNG = /* @__PURE__ */ ( () => { + + const UPNG = {}; + + UPNG.toRGBA8 = function ( out ) { + + var w = out.width, h = out.height; + if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; + + var frms = []; + if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; + + var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); + for ( var i = 0; i < out.frames.length; i ++ ) { + + var frm = out.frames[ i ]; + var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; + var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); + + if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; + + if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); + + frms.push( img.buffer.slice( 0 ) ); + + if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; + + } + + return frms; + + }; + + UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { + + var area = w * h, bpp = UPNG.decode._getBPP( out ); + var bpl = Math.ceil( w * bpp / 8 ); // bytes per line + + var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); + var ctype = out.ctype, depth = out.depth; + var rs = UPNG._bin.readUshort; + + if ( ctype == 6 ) { // RGB + alpha + + var qarea = area << 2; + if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { + + bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { + + bf[ i ] = data[ i << 1 ]; + + } + + } else if ( ctype == 2 ) { // RGB + + var ts = out.tabs[ 'tRNS' ]; + if ( ts == null ) { + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + + } + + } else { + + var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + + } + + } + + } else if ( ctype == 3 ) { // palette + + var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; + //console.log(p, ap); + if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } else if ( ctype == 4 ) { // gray + alpha + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; + + } + + } else if ( ctype == 0 ) { // gray + + var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; + for ( var y = 0; y < h; y ++ ) { + + var off = y * bpl, to = y * w; + if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { + + var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { + + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + + } + + } + + //console.log(Date.now()-time); + return bf; + + }; + + + + UPNG.decode = function ( buff ) { + + var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; + var out = { tabs: {}, frames: [] }; + var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it + var fd, foff = 0; // frames + var text, keyw, bfr; + + var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; + for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); + + while ( offset < data.length ) { + + var len = bin.readUint( data, offset ); offset += 4; + var type = bin.readASCII( data, offset, 4 ); offset += 4; + //console.log(type,len); + + if ( type == 'IHDR' ) { + + UPNG.decode._IHDR( data, offset, out ); + + } else if ( type == 'CgBI' ) { + + out.tabs[ type ] = data.slice( offset, offset + 4 ); + + } else if ( type == 'IDAT' ) { + + for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; + doff += len; + + } else if ( type == 'acTL' ) { + + out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; + fd = new Uint8Array( data.length ); + + } else if ( type == 'fcTL' ) { + + if ( foff != 0 ) { + + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + + } + + var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; + var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); + var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; + //console.log(frm); + out.frames.push( frm ); + + } else if ( type == 'fdAT' ) { + + for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; + foff += len - 4; + + } else if ( type == 'pHYs' ) { + + out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; + + } else if ( type == 'cHRM' ) { + + out.tabs[ type ] = []; + for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); + + } else if ( type == 'tEXt' || type == 'zTXt' ) { + + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = bin.nextZero( data, offset ); + keyw = bin.readASCII( data, offset, nz - offset ); + var tl = offset + len - nz - 1; + if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); + else { + + bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); + + } + + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'iTXt' ) { + + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = 0, off = offset; + nz = bin.nextZero( data, off ); + keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; + var cflag = data[ off ]; off += 2; + nz = bin.nextZero( data, off ); + bin.readASCII( data, off, nz - off ); off = nz + 1; + nz = bin.nextZero( data, off ); + bin.readUTF8( data, off, nz - off ); off = nz + 1; + var tl = len - ( off - offset ); + if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); + else { + + bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); + + } + + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'PLTE' ) { + + out.tabs[ type ] = bin.readBytes( data, offset, len ); + + } else if ( type == 'hIST' ) { + + var pl = out.tabs[ 'PLTE' ].length / 3; + out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); + + } else if ( type == 'tRNS' ) { + + if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); + else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); + else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + //else console.log("tRNS for unsupported color type",out.ctype, len); + + } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; + else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; + else if ( type == 'bKGD' ) { + + if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; + else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; + + } else if ( type == 'IEND' ) { + + break; + + } + + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len; + bin.readUint( data, offset ); offset += 4; + + } + + if ( foff != 0 ) { + + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); + + } + + out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); + + delete out.compress; delete out.interlace; delete out.filter; + return out; + + }; + + UPNG.decode._decompress = function ( out, dd, w, h ) { + + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); + if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); + else dd = UPNG.decode._inflate( dd, buff ); + + if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); + else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); + + return dd; + + }; + + UPNG.decode._inflate = function ( data, buff ) { + + var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; + + }; + + UPNG.inflateRaw = function () { + + var H = {}; H.H = {}; H.H.N = function ( N, W ) { + + var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; + if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; + if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { + + i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { + + if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); + var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; + w += q; continue + ; + + } + + if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { + + v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; + + } + + if ( m == 2 ) { + + J = A( N, d, 5 ) + 257; + h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { + + b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; + + } + + for ( var c = 0; + c < Q; c ++ ) { + + var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K + ; + + } + + d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; + d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); + I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + ; + + } + + while ( ! 0 ) { + + var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { + + W[ w ++ ] = p; + + } else if ( p == 256 ) { + + break; + + } else { + + var z = w + p - 254; + if ( p > 264 ) { + + var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; + + } + + var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); + d += Y & 15; while ( w < z ) { + + W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; + + } + + w = z + ; + + } + + } + + } + + return W.length == w ? W : W.slice( 0, w ) + ; + + }; + + H.H.W = function ( N, W ) { + + var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; + + }; + + H.H.R = function ( N, W, R, V, n, A ) { + + var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { + + var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; + if ( b <= 15 ) { + + A[ I ] = b; I ++; + + } else { + + var Z = 0, m = 0; if ( b == 16 ) { + + m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; + + } else if ( b == 17 ) { + + m = 3 + l( V, n, 3 ); + n += 3 + ; + + } else if ( b == 18 ) { + + m = 11 + l( V, n, 7 ); n += 7; + + } + + var J = I + m; while ( I < J ) { + + A[ I ] = Z; I ++; + + } + + } + + } + + return n + ; + + }; + + H.H.V = function ( N, W, R, V ) { + + var n = 0, A = 0, l = V.length >>> 1; + while ( A < R ) { + + var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; + + } + + while ( A < l ) { + + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + + } + + return n + ; + + }; + + H.H.n = function ( N, W ) { + + var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; + var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { + + n = n + e[ A - 1 ] << 1; b[ A ] = n; + + } + + for ( l = 0; l < V; l += 2 ) { + + I = N[ l + 1 ]; if ( I != 0 ) { + + N[ l ] = b[ I ]; + b[ I ] ++ + ; + + } + + } + + }; + + H.H.A = function ( N, W, R ) { + + var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { + + var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); + while ( Z != m ) { + + var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; + + } + + } + + }; + + H.H.l = function ( N, W ) { + + var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; + n += 2 ) { + + var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; + + } + + }; + + H.H.M = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + + }; + + H.H.I = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; + + }; + + H.H.e = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + H.H.b = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + H.H.Z = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + + }; + + H.H.i = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); + + }; + + H.H.m = function () { + + var N = Uint16Array, W = Uint32Array; + return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } + ; + + }(); + ( function () { + + var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { + + var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; + V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; + N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 + ; + + } + + function n( A, l, M ) { + + while ( l -- != 0 )A.push( 0, M ) + ; + + } + + for ( var R = 0; R < 32; R ++ ) { + + N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; + N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] + ; + + } + + n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); + H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); + n( N.D, 30, 0 ); n( N.v, 320, 0 ) + ; + + }() ); + + return H.H.N + ; + + }(); + + + UPNG.decode._readInterlace = function ( data, out ) { + + var w = out.width, h = out.height; + var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); + var img = new Uint8Array( h * bpl ); + var di = 0; + + var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; + var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; + var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; + var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; + + var pass = 0; + while ( pass < 7 ) { + + var ri = row_increment[ pass ], ci = col_increment[ pass ]; + var sw = 0, sh = 0; + var cr = starting_row[ pass ]; while ( cr < h ) { + + cr += ri; sh ++; + + } + + var cc = starting_col[ pass ]; while ( cc < w ) { + + cc += ci; sw ++; + + } + + var bpll = Math.ceil( sw * bpp / 8 ); + UPNG.decode._filterZero( data, out, di, sw, sh ); + + var y = 0, row = starting_row[ pass ]; + var val; + + while ( row < h ) { + + var col = starting_col[ pass ]; + var cdi = ( di + y * bpll ) << 3; + + while ( col < w ) { + + if ( bpp == 1 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; + img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); + + } + + if ( bpp == 2 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; + img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); + + } + + if ( bpp == 4 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; + img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); + + } + + if ( bpp >= 8 ) { + + var ii = row * bpl + col * cbpp; + for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; + + } + + cdi += bpp; col += ci; + + } + + y ++; row += ri; + + } + + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; + + } + + return img; + + }; + + UPNG.decode._getBPP = function ( out ) { + + var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; + return noc * out.depth; + + }; + + UPNG.decode._filterZero = function ( data, out, off, w, h ) { + + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; + bpp = Math.ceil( bpp / 8 ); + + var i, di, type = data[ off ], x = 0; + + if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; + if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; + + for ( var y = 0; y < h; y ++ ) { + + i = off + y * bpl; di = i + y + 1; + type = data[ di - 1 ]; x = 0; + + if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; + else if ( type == 1 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); + + } else if ( type == 2 ) { + + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); + + } else if ( type == 3 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); + + } else { + + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); + + } + + } + + return data; + + }; + + UPNG.decode._paeth = function ( a, b, c ) { + + var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); + if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; + else if ( pb * pb <= pc * pc ) return b; + return c; + + }; + + UPNG.decode._IHDR = function ( data, offset, out ) { + + var bin = UPNG._bin; + out.width = bin.readUint( data, offset ); offset += 4; + out.height = bin.readUint( data, offset ); offset += 4; + out.depth = data[ offset ]; offset ++; + out.ctype = data[ offset ]; offset ++; + out.compress = data[ offset ]; offset ++; + out.filter = data[ offset ]; offset ++; + out.interlace = data[ offset ]; offset ++; + + }; + + UPNG._bin = { + nextZero: function ( data, p ) { + + while ( data[ p ] != 0 ) p ++; return p; + + }, + readUshort: function ( buff, p ) { + + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + + }, + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + + }, + readUint: function ( buff, p ) { + + return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; + + }, + readASCII: function ( buff, p, l ) { + + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + + }, + writeASCII: function ( data, p, s ) { + + for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); + + }, + readBytes: function ( buff, p, l ) { + + var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; + + }, + pad: function ( n ) { + + return n.length < 2 ? '0' + n : n; + + }, + readUTF8: function ( buff, p, l ) { + + var s = '', ns; + for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); + try { + + ns = decodeURIComponent( s ); + + } catch ( e ) { + + return UPNG._bin.readASCII( buff, p, l ); + + } + + return ns; + + } + }; + UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { + + var w = Math.min( sw, tw ), h = Math.min( sh, th ); + var si = 0, ti = 0; + for ( var y = 0; y < h; y ++ ) + for ( var x = 0; x < w; x ++ ) { + + if ( xoff >= 0 && yoff >= 0 ) { + + si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; + + } else { + + si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; + + } + + if ( mode == 0 ) { + + tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; + + } else if ( mode == 1 ) { + + var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; + var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; + + var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); + tb[ ti + 3 ] = 255 * oa; + tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; + tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; + tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; + + } else if ( mode == 2 ) { // copy only differences, otherwise zero + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) { + + tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; + + } else { + + tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; + + } + + } else if ( mode == 3 ) { // check if can be blended + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; + //if(fa!=255 && ba!=0) return false; + if ( fa < 220 && ba > 20 ) return false; + + } + + } + + return true; + + }; + + return UPNG; + +} )(); + export { RGBMLoader }; From 501ac632c6740a9a8b0ac4e79405d343837c24d9 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:30:45 -0500 Subject: [PATCH 19/45] CSMShader: don't self-reference with include --- examples/jsm/csm/CSMShader.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/jsm/csm/CSMShader.js b/examples/jsm/csm/CSMShader.js index b6b279313f5824..c4d0e73905d265 100644 --- a/examples/jsm/csm/CSMShader.js +++ b/examples/jsm/csm/CSMShader.js @@ -1,4 +1,6 @@ -const CSMShader = { +import { ShaderChunk } from 'three'; + +const CSMShader = /* @__PURE__ */ ( () => ( { lights_fragment_begin: /* glsl */` vec3 geometryPosition = - vViewPosition; vec3 geometryNormal = normal; @@ -283,8 +285,7 @@ uniform float cameraNear; uniform float shadowFar; #endif -#include - ` -}; + ` + ShaderChunk.lights_pars_begin +} ) )(); export { CSMShader }; From f1e8ab61604a2ccf140b0d0f8178d84844e7f9c3 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:31:23 -0500 Subject: [PATCH 20/45] CSMShader: cleanup --- examples/jsm/csm/CSMShader.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/jsm/csm/CSMShader.js b/examples/jsm/csm/CSMShader.js index c4d0e73905d265..e7a4b44a75468a 100644 --- a/examples/jsm/csm/CSMShader.js +++ b/examples/jsm/csm/CSMShader.js @@ -284,7 +284,6 @@ uniform vec2 CSM_cascades[CSM_CASCADES]; uniform float cameraNear; uniform float shadowFar; #endif - ` + ShaderChunk.lights_pars_begin } ) )(); From 9446f8837425f2ecb2144627c7e53bd5d535764e Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:41:37 -0500 Subject: [PATCH 21/45] utif: hoist namespace --- examples/jsm/libs/utif.module.js | 3026 +++++++++++++++--------------- 1 file changed, 1512 insertions(+), 1514 deletions(-) diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index aa5c2c61cdd440..924160f68ce190 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -1,1033 +1,1114 @@ -const UTIF = /* @__PURE */ ( () => { - - var UTIF = {}; - - // Following lines add a JPEG decoder to UTIF.JpegDecoder - (function(){"use strict";var W=function a1(){function W(p){this.message="JPEG error: "+p}W.prototype=new Error;W.prototype.name="JpegError";W.constructor=W;return W}(),ak=function ag(){var p=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),t=4017,ac=799,ah=3406,ao=2276,ar=1567,ai=3784,s=5793,ad=2896;function ak(Q){if(Q==null)Q={};if(Q.w==null)Q.w=-1;this.V=Q.n;this.N=Q.w}function a5(Q,h){var f=0,G=[],n,E,a=16,F;while(a>0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n0){C=G.pop()}C.index++;G.push(C);while(G.length<=n){G.push(F={children:[],index:0});C.children[C.index]=F.children;C=F}f++}if(n+10){V--;return J>>V&1}J=Q[h++];if(J===255){var I=Q[h++];if(I){if(I===220&&d){h+=2;var l=Z(Q,h);h+=2;if(l>0&&l!==f.s){throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",l)}}else if(I===217){if(d){var M=q*8; - if(M>0&&M>>7}function u(I){var l=I;while(!0){l=l[Y()];switch(typeof l){case"number":return l;case"object":continue}throw new W("invalid huffman sequence")}}function m(I){var e=0;while(I>0){e=e<<1|Y();I--}return e}function j(I){if(I===1){return Y()===1?1:-1}var e=m(I);if(e>=1<>4;if(i===0){if(A<15){break}N+=16;continue}N+=A;var o=p[N];X.D[I+o]=j(i);N++}}function $(X,I){var l=u(X.J),M=l===0?0:j(l)<0){r--;return}var N=E,l=a;while(N<=l){var M=u(X.i),S=M&15,i=M>>4;if(S===0){if(i<15){r=m(i)+(1<>4;if(S===0){if(M<15){r=m(M)+(1<0){for(O=0;O0?"unexpected":"excessive";h=k.offset}if(k.M>=65488&&k.M<=65495){h+=2}else{break}}return h-z}function al(Q,h,f){var G=Q.$,n=Q.D,E,a,C,F,d,T,U,z,J,V,Y,u,m,j,v,$,b;if(!G){throw new W("missing required Quantization Table.")}for(var r=0;r<64;r+=8){J=n[h+r];V=n[h+r+1];Y=n[h+r+2];u=n[h+r+3];m=n[h+r+4];j=n[h+r+5];v=n[h+r+6];$=n[h+r+7];J*=G[r];if((V|Y|u|m|j|v|$)===0){b=s*J+512>>10;f[r]=b;f[r+1]=b;f[r+2]=b;f[r+3]=b;f[r+4]=b;f[r+5]=b;f[r+6]=b;f[r+7]=b;continue}V*=G[r+1];Y*=G[r+2];u*=G[r+3];m*=G[r+4];j*=G[r+5];v*=G[r+6];$*=G[r+7];E=s*J+128>>8;a=s*m+128>>8;C=Y;F=v;d=ad*(V-$)+128>>8;z=ad*(V+$)+128>>8; - T=u<<4;U=j<<4;E=E+a+1>>1;a=E-a;b=C*ai+F*ar+128>>8;C=C*ar-F*ai+128>>8;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b;b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;f[r]=E+z;f[r+7]=E-z;f[r+1]=a+U;f[r+6]=a-U;f[r+2]=C+T;f[r+5]=C-T;f[r+3]=F+d;f[r+4]=F-d}for(var P=0;P<8;++P){J=f[P];V=f[P+8];Y=f[P+16];u=f[P+24];m=f[P+32];j=f[P+40];v=f[P+48];$=f[P+56];if((V|Y|u|m|j|v|$)===0){b=s*J+8192>>14;if(b<-2040){b=0}else if(b>=2024){b=255}else{b=b+2056>>4}n[h+P]=b;n[h+P+8]=b;n[h+P+16]=b;n[h+P+24]=b;n[h+P+32]=b;n[h+P+40]=b;n[h+P+48]=b;n[h+P+56]=b;continue}E=s*J+2048>>12;a=s*m+2048>>12;C=Y;F=v;d=ad*(V-$)+2048>>12;z=ad*(V+$)+2048>>12;T=u;U=j;E=(E+a+1>>1)+4112;a=E-a;b=C*ai+F*ar+2048>>12;C=C*ar-F*ai+2048>>12;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b; - b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;J=E+z;$=E-z;V=a+U;v=a-U;Y=C+T;j=C-T;u=F+d;m=F-d;if(J<16){J=0}else if(J>=4080){J=255}else{J>>=4}if(V<16){V=0}else if(V>=4080){V=255}else{V>>=4}if(Y<16){Y=0}else if(Y>=4080){Y=255}else{Y>>=4}if(u<16){u=0}else if(u>=4080){u=255}else{u>>=4}if(m<16){m=0}else if(m>=4080){m=255}else{m>>=4}if(j<16){j=0}else if(j>=4080){j=255}else{j>>=4}if(v<16){v=0}else if(v>=4080){v=255}else{v>>=4}if($<16){$=0}else if($>=4080){$=255}else{$>>=4}n[h+P]=J; - n[h+P+8]=V;n[h+P+16]=Y;n[h+P+24]=u;n[h+P+32]=m;n[h+P+40]=j;n[h+P+48]=v;n[h+P+56]=$}}function a0(Q,h){var f=h.P,G=h.c,n=new Int16Array(64);for(var E=0;E=G){return null}var E=Z(Q,h);if(E>=65472&&E<=65534){return{u:null,M:E,offset:h}}var a=Z(Q,n);while(!(a>=65472&&a<=65534)){if(++n>=G){return null}a=Z(Q,n)}return{u:E.toString(16),M:a,offset:n}}ak.prototype={parse(Q,h){if(h==null)h={}; - var f=h.F,E=0,a=null,C=null,F,d,T=0;function G(){var o=Z(Q,E);E+=2;var B=E+o-2,V=an(Q,B,E);if(V&&V.u){B=V.offset}var ab=Q.subarray(E,B);E+=ab.length;return ab}function n(F){var o=Math.ceil(F.o/8/F.X),B=Math.ceil(F.s/8/F.B);for(var Y=0;Y>4===0){for(u=0;u<64;u++){b=p[u];P[b]=Q[E++]}}else if(r>>4===1){for(u=0;u<64;u++){b=p[u];P[b]=Z(Q,E);E+=2}}else{throw new W("DQT - invalid table spec")}U[r&15]=P}break;case 65472:case 65473:case 65474:if(F){throw new W("Only single frame JPEGs supported")}E+=2;F={};F.G=V===65473;F.Z=V===65474;F.precision=Q[E++];var D=Z(Q,E),a4,q=0,H=0;E+=2;F.s=f||D;F.o=Z(Q,E);E+=2;F.W=[];F._={};var a8=Q[E++];for(Y=0;Y>4,y=Q[E+1]&15;if(q>4===0?J:z)[_&15]=a5(N,K)}break;case 65501:E+=2;d=Z(Q,E);E+=2;break;case 65498:var x=++T===1&&!f,R;E+=2;var k=Q[E++],g=[];for(Y=0;Y>4];R.i=z[a6&15];g.push(R)}var I=Q[E++],l=Q[E++],M=Q[E++];try{var S=a7(Q,E,F,g,d,I,l,M>>4,M&15,x);E+=S}catch(ex){if(ex instanceof DNLMarkerError){return this.parse(Q,{F:ex.s})}else if(ex instanceof EOIMarkerError){break markerLoop}throw ex}break;case 65500:E+=4;break;case 65535:if(Q[E]!==255){E--}break;default:var i=an(Q,E-2,E-3);if(i&&i.u){E=i.offset;break}if(E>=Q.length-1){break markerLoop}throw new W("JpegImage.parse - unknown marker: "+V.toString(16))}V=Z(Q,E);E+=2}this.width=F.o;this.height=F.s;this.g=a;this.b=C;this.W=[];for(Y=0;Y>8)+P[J+1]}}}return v},get f(){if(this.b){return!!this.b.a}if(this.p===3){if(this.N===0){return!1}else if(this.W[0].index===82&&this.W[1].index===71&&this.W[2].index===66){return!1}return!0}if(this.N===1){return!0}return!1},z:function aj(Q){var h,f,G; - for(var n=0,E=Q.length;n4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p===1&&G){var a=E.length,C=new Uint8ClampedArray(a*3),F=0;for(var d=0;d>24}function Z(p,t){return p[t]<<8|p[t+1]}function am(p,t){return(p[t]<<24|p[t+1]<<16|p[t+2]<<8|p[t+3])>>>0}UTIF.JpegDecoder=ak}()); - - //UTIF.JpegDecoder = PDFJS.JpegImage; - - - UTIF.encodeImage = function(rgba, w, h, metadata) +const UTIF = {}; + +/* @__PURE__ */ ( () => { + +// Following lines add a JPEG decoder to UTIF.JpegDecoder +(function(){"use strict";var W=function a1(){function W(p){this.message="JPEG error: "+p}W.prototype=new Error;W.prototype.name="JpegError";W.constructor=W;return W}(),ak=function ag(){var p=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),t=4017,ac=799,ah=3406,ao=2276,ar=1567,ai=3784,s=5793,ad=2896;function ak(Q){if(Q==null)Q={};if(Q.w==null)Q.w=-1;this.V=Q.n;this.N=Q.w}function a5(Q,h){var f=0,G=[],n,E,a=16,F;while(a>0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n0){C=G.pop()}C.index++;G.push(C);while(G.length<=n){G.push(F={children:[],index:0});C.children[C.index]=F.children;C=F}f++}if(n+10){V--;return J>>V&1}J=Q[h++];if(J===255){var I=Q[h++];if(I){if(I===220&&d){h+=2;var l=Z(Q,h);h+=2;if(l>0&&l!==f.s){throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",l)}}else if(I===217){if(d){var M=q*8; +if(M>0&&M>>7}function u(I){var l=I;while(!0){l=l[Y()];switch(typeof l){case"number":return l;case"object":continue}throw new W("invalid huffman sequence")}}function m(I){var e=0;while(I>0){e=e<<1|Y();I--}return e}function j(I){if(I===1){return Y()===1?1:-1}var e=m(I);if(e>=1<>4;if(i===0){if(A<15){break}N+=16;continue}N+=A;var o=p[N];X.D[I+o]=j(i);N++}}function $(X,I){var l=u(X.J),M=l===0?0:j(l)<0){r--;return}var N=E,l=a;while(N<=l){var M=u(X.i),S=M&15,i=M>>4;if(S===0){if(i<15){r=m(i)+(1<>4;if(S===0){if(M<15){r=m(M)+(1<0){for(O=0;O0?"unexpected":"excessive";h=k.offset}if(k.M>=65488&&k.M<=65495){h+=2}else{break}}return h-z}function al(Q,h,f){var G=Q.$,n=Q.D,E,a,C,F,d,T,U,z,J,V,Y,u,m,j,v,$,b;if(!G){throw new W("missing required Quantization Table.")}for(var r=0;r<64;r+=8){J=n[h+r];V=n[h+r+1];Y=n[h+r+2];u=n[h+r+3];m=n[h+r+4];j=n[h+r+5];v=n[h+r+6];$=n[h+r+7];J*=G[r];if((V|Y|u|m|j|v|$)===0){b=s*J+512>>10;f[r]=b;f[r+1]=b;f[r+2]=b;f[r+3]=b;f[r+4]=b;f[r+5]=b;f[r+6]=b;f[r+7]=b;continue}V*=G[r+1];Y*=G[r+2];u*=G[r+3];m*=G[r+4];j*=G[r+5];v*=G[r+6];$*=G[r+7];E=s*J+128>>8;a=s*m+128>>8;C=Y;F=v;d=ad*(V-$)+128>>8;z=ad*(V+$)+128>>8; +T=u<<4;U=j<<4;E=E+a+1>>1;a=E-a;b=C*ai+F*ar+128>>8;C=C*ar-F*ai+128>>8;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b;b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;f[r]=E+z;f[r+7]=E-z;f[r+1]=a+U;f[r+6]=a-U;f[r+2]=C+T;f[r+5]=C-T;f[r+3]=F+d;f[r+4]=F-d}for(var P=0;P<8;++P){J=f[P];V=f[P+8];Y=f[P+16];u=f[P+24];m=f[P+32];j=f[P+40];v=f[P+48];$=f[P+56];if((V|Y|u|m|j|v|$)===0){b=s*J+8192>>14;if(b<-2040){b=0}else if(b>=2024){b=255}else{b=b+2056>>4}n[h+P]=b;n[h+P+8]=b;n[h+P+16]=b;n[h+P+24]=b;n[h+P+32]=b;n[h+P+40]=b;n[h+P+48]=b;n[h+P+56]=b;continue}E=s*J+2048>>12;a=s*m+2048>>12;C=Y;F=v;d=ad*(V-$)+2048>>12;z=ad*(V+$)+2048>>12;T=u;U=j;E=(E+a+1>>1)+4112;a=E-a;b=C*ai+F*ar+2048>>12;C=C*ar-F*ai+2048>>12;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b; +b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;J=E+z;$=E-z;V=a+U;v=a-U;Y=C+T;j=C-T;u=F+d;m=F-d;if(J<16){J=0}else if(J>=4080){J=255}else{J>>=4}if(V<16){V=0}else if(V>=4080){V=255}else{V>>=4}if(Y<16){Y=0}else if(Y>=4080){Y=255}else{Y>>=4}if(u<16){u=0}else if(u>=4080){u=255}else{u>>=4}if(m<16){m=0}else if(m>=4080){m=255}else{m>>=4}if(j<16){j=0}else if(j>=4080){j=255}else{j>>=4}if(v<16){v=0}else if(v>=4080){v=255}else{v>>=4}if($<16){$=0}else if($>=4080){$=255}else{$>>=4}n[h+P]=J; +n[h+P+8]=V;n[h+P+16]=Y;n[h+P+24]=u;n[h+P+32]=m;n[h+P+40]=j;n[h+P+48]=v;n[h+P+56]=$}}function a0(Q,h){var f=h.P,G=h.c,n=new Int16Array(64);for(var E=0;E=G){return null}var E=Z(Q,h);if(E>=65472&&E<=65534){return{u:null,M:E,offset:h}}var a=Z(Q,n);while(!(a>=65472&&a<=65534)){if(++n>=G){return null}a=Z(Q,n)}return{u:E.toString(16),M:a,offset:n}}ak.prototype={parse(Q,h){if(h==null)h={}; +var f=h.F,E=0,a=null,C=null,F,d,T=0;function G(){var o=Z(Q,E);E+=2;var B=E+o-2,V=an(Q,B,E);if(V&&V.u){B=V.offset}var ab=Q.subarray(E,B);E+=ab.length;return ab}function n(F){var o=Math.ceil(F.o/8/F.X),B=Math.ceil(F.s/8/F.B);for(var Y=0;Y>4===0){for(u=0;u<64;u++){b=p[u];P[b]=Q[E++]}}else if(r>>4===1){for(u=0;u<64;u++){b=p[u];P[b]=Z(Q,E);E+=2}}else{throw new W("DQT - invalid table spec")}U[r&15]=P}break;case 65472:case 65473:case 65474:if(F){throw new W("Only single frame JPEGs supported")}E+=2;F={};F.G=V===65473;F.Z=V===65474;F.precision=Q[E++];var D=Z(Q,E),a4,q=0,H=0;E+=2;F.s=f||D;F.o=Z(Q,E);E+=2;F.W=[];F._={};var a8=Q[E++];for(Y=0;Y>4,y=Q[E+1]&15;if(q>4===0?J:z)[_&15]=a5(N,K)}break;case 65501:E+=2;d=Z(Q,E);E+=2;break;case 65498:var x=++T===1&&!f,R;E+=2;var k=Q[E++],g=[];for(Y=0;Y>4];R.i=z[a6&15];g.push(R)}var I=Q[E++],l=Q[E++],M=Q[E++];try{var S=a7(Q,E,F,g,d,I,l,M>>4,M&15,x);E+=S}catch(ex){if(ex instanceof DNLMarkerError){return this.parse(Q,{F:ex.s})}else if(ex instanceof EOIMarkerError){break markerLoop}throw ex}break;case 65500:E+=4;break;case 65535:if(Q[E]!==255){E--}break;default:var i=an(Q,E-2,E-3);if(i&&i.u){E=i.offset;break}if(E>=Q.length-1){break markerLoop}throw new W("JpegImage.parse - unknown marker: "+V.toString(16))}V=Z(Q,E);E+=2}this.width=F.o;this.height=F.s;this.g=a;this.b=C;this.W=[];for(Y=0;Y>8)+P[J+1]}}}return v},get f(){if(this.b){return!!this.b.a}if(this.p===3){if(this.N===0){return!1}else if(this.W[0].index===82&&this.W[1].index===71&&this.W[2].index===66){return!1}return!0}if(this.N===1){return!0}return!1},z:function aj(Q){var h,f,G; +for(var n=0,E=Q.length;n4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p===1&&G){var a=E.length,C=new Uint8ClampedArray(a*3),F=0;for(var d=0;d>24}function Z(p,t){return p[t]<<8|p[t+1]}function am(p,t){return(p[t]<<24|p[t+1]<<16|p[t+2]<<8|p[t+3])>>>0}UTIF.JpegDecoder=ak}()); + +//UTIF.JpegDecoder = PDFJS.JpegImage; + + +UTIF.encodeImage = function(rgba, w, h, metadata) +{ + var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset + "t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts + "t282":[[72,1]], "t283":[[72,1]], "t284":[1], "t286":[[0,1]], "t287":[[0,1]], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1] + }; + if (metadata) for (var i in metadata) idf[i] = metadata[i]; + + var prfx = new Uint8Array(UTIF.encode([idf])); + var img = new Uint8Array(rgba); + var data = new Uint8Array(1000+w*h*4); + for(var i=0; i probably not an image + img.isLE = id=="II"; + img.width = img["t256"][0]; //delete img["t256"]; + img.height = img["t257"][0]; //delete img["t257"]; + + var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; + var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; + if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); + if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); + + var spp = img["t277"]?img["t277"][0]:1; + var bps = img["t258"]?img["t258"][0]:1; + var bipp = bps*spp; // bits per pixel + /* + var bipp; // bits per pixel + if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; + else bipp = (img["t277"]?img["t277"][0]:1); + */ + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { + bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); } - - UTIF.decode = function(buff, prm) + if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 + var bipl = Math.ceil(img.width*bipp/8)*8; + var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; + var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; + + if(img["t322"]!=null) // tiled { - if(prm==null) prm = {parseMN:true, debug:false}; // read MakerNote, debug - var data = new Uint8Array(buff), offset = 0; - - var id = UTIF._binBE.readASCII(data, offset, 2); offset+=2; - var bin = id=="II" ? UTIF._binLE : UTIF._binBE; - var num = bin.readUshort(data, offset); offset+=2; - - var ifdo = bin.readUint(data, offset); offset+=4; - var ifds = []; - while(true) { - var cnt = bin.readUshort(data,ifdo), typ = bin.readUshort(data,ifdo+4); if(cnt!=0) if(typ<1 || 13 probably not an image - img.isLE = id=="II"; - img.width = img["t256"][0]; //delete img["t256"]; - img.height = img["t257"][0]; //delete img["t257"]; - - var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; - var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; - if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); - if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); - - var spp = img["t277"]?img["t277"][0]:1; - var bps = img["t258"]?img["t258"][0]:1; - var bipp = bps*spp; // bits per pixel - /* - var bipp; // bits per pixel - if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; - else bipp = (img["t277"]?img["t277"][0]:1); - */ - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { - bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); - } - if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 - var bipl = Math.ceil(img.width*bipp/8)*8; - var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; - var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; - - if(img["t322"]!=null) // tiled - { - var tw = img["t322"][0], th = img["t323"][0]; - var tx = Math.floor((img.width + tw - 1) / tw); - var ty = Math.floor((img.height + th - 1) / th); - var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); - console.log("====", tx,ty); - for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); + + // convert to Little Endian /* + if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG + for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); - - // convert to Little Endian /* - if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG - for(var y=0; y>>8)&255; + } + else if(noc==3) for(var j= 3; j>>8)&255; - } - else if(noc==3) for(var j= 3; j> 3 ^ 0x3ff0; - return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); - } - } - // Raw Format 6 - function getBufferDataRW6(i) { - return buffer[vpos + 15 - i]; - } - function readPageRW6() { - bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit - bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; - bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); - bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); - bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); - bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; - bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); - bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); - bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; - bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); - bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; - bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; - vpos += 16; - byte = 0; - } - function readPageRw6_bps12() { - bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); - bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; - bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); - bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); - bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); - bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; - bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); - bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); - bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); - bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); - bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); - bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); - bytes[14] = getBufferDataRW6(12) & 0x3; - bytes[15] = getBufferDataRW6(13); - bytes[16] = getBufferDataRW6(14); - bytes[17] = getBufferDataRW6(15); - - vpos += 16; - byte = 0; - } - // Main loop - function resetPredNonzeros(){ - pred[0]=0; pred[1]=0; - nonz[0]=0; nonz[1]=0; - } - if (RW2_Format == 7) { - throw RW2_Format; - - // Skatch of version 7 - /* - var pixels_per_block = bitsPerSample == 14 ? 9 : 10; - rowbytes = 0|(rawWidth / pixels_per_block * 16); - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - i = 0; - for (crow = 0; crow < rowstoread; crow++) { - idx = (row + crow) * rawWidth; - for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { - for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; - if (bitsPerSample == 12) { - result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - } - */ - } - else if(RW2_Format == 6) { - var is12bit = bitsPerSample == 12, - readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, - pixelsPerBlock = is12bit ? 14 : 11, - pixelbase0 = is12bit ? 0x80 : 0x200, - pixelbase_compare = is12bit ? 0x800 : 0x2000, - spix_compare = is12bit ? 0x3fff : 0xffff, - pixel_mask = is12bit ? 0xfff : 0x3fff, - blocksperrow = rawWidth / pixelsPerBlock, - rowbytes = blocksperrow * 16, - bufferSize = is12bit ? 18 : 14; - - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { - idx = (row + crow) * rawWidth; - for (var rblock = 0; rblock < blocksperrow; rblock++) { - readPageRw6Fn(); - resetPredNonzeros(); - sh=0; pixel_base=0; - for (i = 0; i < pixelsPerBlock; i++){ - isOdd = i & 1; - if (i % 3 == 2) { - var base = byte < bufferSize ? bytes[byte++] : 0; - if (base == 3) base = 4; - pixel_base = pixelbase0 << base; - sh = 1 << base; - } - var epixel = byte < bufferSize ? bytes[byte++] : 0; - if (pred[isOdd]) { - epixel *= sh; - if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) - epixel += nonz[isOdd] - pixel_base; - nonz[isOdd] = epixel; - } else { - pred[isOdd] = epixel; - if (epixel) - nonz[isOdd] = epixel; - else - epixel = nonz[isOdd]; - } - result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; - } - } - } - } - } - else if (RW2_Format == 5) { - var blockSize = bitsPerSample == 12 ? 10 : 9; - for (row = 0; row < rawHeight; row++) { - for (col = 0; col < rawWidth; col+=blockSize) { - getDataRaw(0); - // Tuhle podminku pouziva i RW2_Format 7 - if (bitsPerSample == 12) { - result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - //console.log(result[1000000 - 1]) - } else if(RW2_Format == 4) { - for (row = 0; row < rawHeight; row++){ - for(col = 0; col < rawWidth; col++){ - i = col % 14; - isOdd = i & 1; - if (i==0) resetPredNonzeros(); - if (i%3 == 2) - sh = 4 >> (3 - getDataRaw(2)); - if (nonz[isOdd]) { - j = getDataRaw(8); - if(j != 0){ - pred[isOdd] -= 0x80 << sh; - if (pred[isOdd] < 0 || sh == 4) - pred[isOdd] &= ~((-1) << sh); - pred[isOdd] += j << sh; - } - } else { - nonz[isOdd] = getDataRaw(8); - if(nonz[isOdd] || i > 11) - pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); - } - result[idx++] = pred[col & 1]; - } - } - } - else throw RW2_Format; - } - - - UTIF.decode._decodeVC5 = function(){var x=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],o,C,k,P=[3,3,3,3,2,2,2,1,1,1],V=24576,ar=16384,H=8192,az=ar|H; - function d(t){var E=t[1],h=t[0][E>>>3]>>>7-(E&7)&1;t[1]++;return h}function ag(t,E){if(o==null){o={}; - for(var h=0;h>>1}return t}function A(t,E){return t>>E}function O(t,E,h,L,g,n){E[h]=A(A(11*t[g]-4*t[g+n]+t[g+n+n]+4,3)+t[L],1); - E[h+n]=A(A(5*t[g]+4*t[g+n]-t[g+n+n]+4,3)-t[L],1)}function J(t,E,h,L,g,n){var W=t[g-n]-t[g+n],j=t[g],$=t[L]; - E[h]=A(A(W+4,3)+j+$,1);E[h+n]=A(A(-W+4,3)+j-$,1)}function y(t,E,h,L,g,n){E[h]=A(A(5*t[g]+4*t[g-n]-t[g-n-n]+4,3)+t[L],1); - E[h+n]=A(A(11*t[g]-4*t[g-n]+t[g-n-n]+4,3)-t[L],1)}function q(t){t=t<0?0:t>4095?4095:t;t=k[t]>>>2;return t}function av(t,E,h,L,g,n){L=new Uint16Array(L.buffer); - var W=Date.now(),j=UTIF._binBE,$=E+h,r,u,X,I,ax,a3,R,ai,aa,ap,ah,ae,aD,al,i,aE,T,B;E+=4;var a5=n[0]==1; - while(E<$){var S=j.readShort(t,E),s=j.readUshort(t,E+2);E+=4;if(S==12)r=s;else if(S==20)u=s;else if(S==21)X=s; - else if(S==48)I=s;else if(S==53)ax=s;else if(S==35)a3=s;else if(S==62)R=s;else if(S==101)ai=s;else if(S==109)aa=s; - else if(S==84)ap=s;else if(S==106)ah=s;else if(S==107)ae=s;else if(S==108)aD=s;else if(S==102)al=s;else if(S==104)i=s; - else if(S==105)aE=s;else{var F=S<0?-S:S,D=F&65280,_=0;if(F&az){if(F&H){_=s&65535;_+=(F&255)<<16}else{_=s&65535}}if((F&V)==V){if(T==null){T=[]; - for(var M=0;M<4;M++)T[M]=new Int16Array((u>>>1)*(X>>>1));B=new Int16Array((u>>>1)*(X>>>1));C=new Int16Array(1024); - for(var M=0;M<1024;M++){var aG=M-512,p=Math.abs(aG),r=Math.floor(768*p*p*p/(255*255*255))+p;C[M]=Math.sign(aG)*r}k=new Uint16Array(4096); - var aA=(1<<16)-1;for(var M=0;M<4096;M++){var at=M,a1=aA*(Math.pow(113,at/4095)-1)/112;k[M]=Math.min(a1,aA)}}var w=T[R],v=m(u,1+P[I]),N=m(X,1+P[I]); - if(I==0){for(var b=0;b>>1)+G]=t[c]<<8|t[c+1]}}else{var a7=[t,E*8],a4=[],ay=0,aw=v*N,f=[0,0],Q=0,s=0; - while(ay0){a4[ay++]=s;Q--}}var l=(I-1)%3,aF=l!=1?v:0,a2=l!=0?N:0; - for(var b=0;b>>1)+aF,au=b*v;for(var G=0;G>>1,an=v*2,a9=N*2; - for(var b=0;b>14-K*2&3;var a6=aC[aB];if(a6!=0)for(var b=0;b>>1)*(u>>>1)+(G>>>1),z=a8[c],ao=ab[c]-2048,ak=aq[c]-2048,ad=as[c]-2048,aj=(ao<<1)+z,a0=(ak<<1)+z,aH=z+ad,am=z-ad; - if(a5){L[U]=q(aH);L[U+1]=q(a0);L[U+u]=q(aj);L[U+u+1]=q(am)}else{L[U]=q(aj);L[U+1]=q(aH);L[U+u]=q(am); - L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==9216){}else throw F.toString(16)}}console.log(Date.now()-W)}return av}() - - - - UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { - var w = img.width, qw=w*4; - var io = 0, out = new Uint8Array(qw); +} + + UTIF.decode._decodePanasonic = function(img, data, off, len, tgt, toff) { + + var img_buffer = data.buffer; + + var rawWidth = img["t2"][0]; + var rawHeight = img["t3"][0]; + var bitsPerSample = img["t10"][0]; + var RW2_Format = img["t45"][0]; + + var bidx = 0; + var imageIndex = 0; + var vpos = 0; + var byte = 0; + var arr_a, arr_b; + var bytes = (RW2_Format == 6 ? new Uint32Array(18) : new Uint8Array(16)); + var i, j, sh, pred=[0,0], nonz=[0,0], isOdd, idx = 0, pixel_base; + var row, col, crow; + var buffer = new Uint8Array(0x4000); + var result = new Uint16Array(tgt.buffer); + + function getDataRaw(bits){ + if (vpos == 0) { + var arr_a = new Uint8Array(img_buffer, off+imageIndex + 0x1ff8, 0x4000-0x1ff8); + var arr_b = new Uint8Array(img_buffer, off+imageIndex, 0x1ff8); + buffer.set(arr_a); + buffer.set(arr_b, arr_a.length); + imageIndex += 0x4000; + } + if(RW2_Format == 5) { + for (i = 0; i < 16; i++){ + bytes[i] = buffer[vpos++]; + vpos &= 0x3FFF; + } + } else { + vpos = (vpos - bits) & 0x1ffff; + byte = vpos >> 3 ^ 0x3ff0; + return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); + } + } + // Raw Format 6 + function getBufferDataRW6(i) { + return buffer[vpos + 15 - i]; + } + function readPageRW6() { + bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit + bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; + bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); + bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); + bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); + bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; + bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); + bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); + bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; + bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); + bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; + bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; + vpos += 16; + byte = 0; + } + function readPageRw6_bps12() { + bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); + bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; + bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); + bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); + bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); + bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; + bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); + bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); + bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); + bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); + bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); + bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); + bytes[14] = getBufferDataRW6(12) & 0x3; + bytes[15] = getBufferDataRW6(13); + bytes[16] = getBufferDataRW6(14); + bytes[17] = getBufferDataRW6(15); + + vpos += 16; + byte = 0; + } + // Main loop + function resetPredNonzeros(){ + pred[0]=0; pred[1]=0; + nonz[0]=0; nonz[1]=0; + } + if (RW2_Format == 7) { + throw RW2_Format; + + // Skatch of version 7 + /* + var pixels_per_block = bitsPerSample == 14 ? 9 : 10; + rowbytes = 0|(rawWidth / pixels_per_block * 16); + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + i = 0; + for (crow = 0; crow < rowstoread; crow++) { + idx = (row + crow) * rawWidth; + for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { + for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; + if (bitsPerSample == 12) { + result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + } + */ + } + else if(RW2_Format == 6) { + var is12bit = bitsPerSample == 12, + readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, + pixelsPerBlock = is12bit ? 14 : 11, + pixelbase0 = is12bit ? 0x80 : 0x200, + pixelbase_compare = is12bit ? 0x800 : 0x2000, + spix_compare = is12bit ? 0x3fff : 0xffff, + pixel_mask = is12bit ? 0xfff : 0x3fff, + blocksperrow = rawWidth / pixelsPerBlock, + rowbytes = blocksperrow * 16, + bufferSize = is12bit ? 18 : 14; + + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { + idx = (row + crow) * rawWidth; + for (var rblock = 0; rblock < blocksperrow; rblock++) { + readPageRw6Fn(); + resetPredNonzeros(); + sh=0; pixel_base=0; + for (i = 0; i < pixelsPerBlock; i++){ + isOdd = i & 1; + if (i % 3 == 2) { + var base = byte < bufferSize ? bytes[byte++] : 0; + if (base == 3) base = 4; + pixel_base = pixelbase0 << base; + sh = 1 << base; + } + var epixel = byte < bufferSize ? bytes[byte++] : 0; + if (pred[isOdd]) { + epixel *= sh; + if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) + epixel += nonz[isOdd] - pixel_base; + nonz[isOdd] = epixel; + } else { + pred[isOdd] = epixel; + if (epixel) + nonz[isOdd] = epixel; + else + epixel = nonz[isOdd]; + } + result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; + } + } + } + } + } + else if (RW2_Format == 5) { + var blockSize = bitsPerSample == 12 ? 10 : 9; + for (row = 0; row < rawHeight; row++) { + for (col = 0; col < rawWidth; col+=blockSize) { + getDataRaw(0); + // Tuhle podminku pouziva i RW2_Format 7 + if (bitsPerSample == 12) { + result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + //console.log(result[1000000 - 1]) + } else if(RW2_Format == 4) { + for (row = 0; row < rawHeight; row++){ + for(col = 0; col < rawWidth; col++){ + i = col % 14; + isOdd = i & 1; + if (i==0) resetPredNonzeros(); + if (i%3 == 2) + sh = 4 >> (3 - getDataRaw(2)); + if (nonz[isOdd]) { + j = getDataRaw(8); + if(j != 0){ + pred[isOdd] -= 0x80 << sh; + if (pred[isOdd] < 0 || sh == 4) + pred[isOdd] &= ~((-1) << sh); + pred[isOdd] += j << sh; + } + } else { + nonz[isOdd] = getDataRaw(8); + if(nonz[isOdd] || i > 11) + pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); + } + result[idx++] = pred[col & 1]; + } + } + } + else throw RW2_Format; + } + + +UTIF.decode._decodeVC5 = function(){var x=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],o,C,k,P=[3,3,3,3,2,2,2,1,1,1],V=24576,ar=16384,H=8192,az=ar|H; +function d(t){var E=t[1],h=t[0][E>>>3]>>>7-(E&7)&1;t[1]++;return h}function ag(t,E){if(o==null){o={}; +for(var h=0;h>>1}return t}function A(t,E){return t>>E}function O(t,E,h,L,g,n){E[h]=A(A(11*t[g]-4*t[g+n]+t[g+n+n]+4,3)+t[L],1); +E[h+n]=A(A(5*t[g]+4*t[g+n]-t[g+n+n]+4,3)-t[L],1)}function J(t,E,h,L,g,n){var W=t[g-n]-t[g+n],j=t[g],$=t[L]; +E[h]=A(A(W+4,3)+j+$,1);E[h+n]=A(A(-W+4,3)+j-$,1)}function y(t,E,h,L,g,n){E[h]=A(A(5*t[g]+4*t[g-n]-t[g-n-n]+4,3)+t[L],1); +E[h+n]=A(A(11*t[g]-4*t[g-n]+t[g-n-n]+4,3)-t[L],1)}function q(t){t=t<0?0:t>4095?4095:t;t=k[t]>>>2;return t}function av(t,E,h,L,g,n){L=new Uint16Array(L.buffer); +var W=Date.now(),j=UTIF._binBE,$=E+h,r,u,X,I,ax,a3,R,ai,aa,ap,ah,ae,aD,al,i,aE,T,B;E+=4;var a5=n[0]==1; +while(E<$){var S=j.readShort(t,E),s=j.readUshort(t,E+2);E+=4;if(S==12)r=s;else if(S==20)u=s;else if(S==21)X=s; +else if(S==48)I=s;else if(S==53)ax=s;else if(S==35)a3=s;else if(S==62)R=s;else if(S==101)ai=s;else if(S==109)aa=s; +else if(S==84)ap=s;else if(S==106)ah=s;else if(S==107)ae=s;else if(S==108)aD=s;else if(S==102)al=s;else if(S==104)i=s; +else if(S==105)aE=s;else{var F=S<0?-S:S,D=F&65280,_=0;if(F&az){if(F&H){_=s&65535;_+=(F&255)<<16}else{_=s&65535}}if((F&V)==V){if(T==null){T=[]; +for(var M=0;M<4;M++)T[M]=new Int16Array((u>>>1)*(X>>>1));B=new Int16Array((u>>>1)*(X>>>1));C=new Int16Array(1024); +for(var M=0;M<1024;M++){var aG=M-512,p=Math.abs(aG),r=Math.floor(768*p*p*p/(255*255*255))+p;C[M]=Math.sign(aG)*r}k=new Uint16Array(4096); +var aA=(1<<16)-1;for(var M=0;M<4096;M++){var at=M,a1=aA*(Math.pow(113,at/4095)-1)/112;k[M]=Math.min(a1,aA)}}var w=T[R],v=m(u,1+P[I]),N=m(X,1+P[I]); +if(I==0){for(var b=0;b>>1)+G]=t[c]<<8|t[c+1]}}else{var a7=[t,E*8],a4=[],ay=0,aw=v*N,f=[0,0],Q=0,s=0; +while(ay0){a4[ay++]=s;Q--}}var l=(I-1)%3,aF=l!=1?v:0,a2=l!=0?N:0; +for(var b=0;b>>1)+aF,au=b*v;for(var G=0;G>>1,an=v*2,a9=N*2; +for(var b=0;b>14-K*2&3;var a6=aC[aB];if(a6!=0)for(var b=0;b>>1)*(u>>>1)+(G>>>1),z=a8[c],ao=ab[c]-2048,ak=aq[c]-2048,ad=as[c]-2048,aj=(ao<<1)+z,a0=(ak<<1)+z,aH=z+ad,am=z-ad; +if(a5){L[U]=q(aH);L[U+1]=q(a0);L[U+u]=q(aj);L[U+u+1]=q(am)}else{L[U]=q(aj);L[U+1]=q(aH);L[U+u]=q(am); +L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==9216){}else throw F.toString(16)}}console.log(Date.now()-W)}return av}() + + + +UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { + var w = img.width, qw=w*4; + var io = 0, out = new Uint8Array(qw); + + while(io>> (tab[i] >>> 8); - for(var c=0; c>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } - return; +} + +UTIF.decode._ljpeg_diff = function(data, prm, huff) { + var getbithuff = UTIF.decode._getbithuff; + var len, diff; + len = getbithuff(data, prm, huff[0], huff); + diff = getbithuff(data, prm, len, 0); + if ((diff & (1 << (len-1))) == 0) diff -= (1 << len) - 1; + return diff; +} +UTIF.decode._decodeARW = function(img, inp, off, src_length, tgt, toff) { + var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; + var bin=(img.isLE ? UTIF._binLE : UTIF._binBE); + //console.log(raw_width, height, tiff_bps, raw_width*height, src_length); + var arw2 = (raw_width*height == src_length) || (raw_width*height*1.5 == src_length); + //arw2 = true; + //console.log("ARW2: ", arw2, raw_width*height, src_length, tgt.length); + if(!arw2) { //"sony_arw_load_raw"; // not arw2 + height+=8; + var prm = [off,0,0,0]; + var huff = new Uint16Array(32770); + var tab = [ 0xf11,0xf10,0xe0f,0xd0e,0xc0d,0xb0c,0xa0b,0x90a,0x809, + 0x708,0x607,0x506,0x405,0x304,0x303,0x300,0x202,0x201 ]; + var i, c, n, col, row, sum=0; + var ljpeg_diff = UTIF.decode._ljpeg_diff; + + huff[0] = 15; + for (n=i=0; i < 18; i++) { + var lim = 32768 >>> (tab[i] >>> 8); + for(var c=0; c>> 11); - imax = 0x0f & (val >>> 22); - imin = 0x0f & (val >>> 26); - for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); - for (bit=30, i=0; i < 16; i++) - if (i == imax) pix[i] = max; - else if (i == imin) pix[i] = min; - else { - pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; - if (pix[i] > 0x7ff) pix[i] = 0x7ff; - bit += 7; - } - for (i=0; i < 16; i++, col+=2) { - //RAW(row,col) = curve[pix[i] << 1] >> 2; - var clr = pix[i]<<1; //clr = 0xffff; + for (col = raw_width; col--; ) + for (row=0; row < height+1; row+=2) { + if (row == height) row = 1; + sum += ljpeg_diff(inp, prm, huff); + if (row < height) { + var clr = (sum)&4095; UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); } - col -= col & 1 ? 1:31; } - } + return; } - - UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) - { - var nikon_tree = [ - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ - 5,4,3,6,2,7,1,0,8,9,11,10,12 ], - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ - 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], - [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ - 5,4,6,3,7,2,8,1,9,0,10,11,12 ], - [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ - 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], - [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ - 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], - [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ - 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; - - var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; - - var tree = 0, split = 0; - var make_decoder = UTIF.decode._make_decoder; - var getbithuff = UTIF.decode._getbithuff; - - var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); - //console.log(md[0].toString(16), md[1].toString(16), tiff_bps); - var ver0 = md[mdo++], ver1 = md[mdo++]; - if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; - if (ver0 == 0x46) tree = 2; - if (tiff_bps == 14) tree += 3; - - var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); - for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] - //console.log(vpred); - - - var max = 1 << tiff_bps & 0x7fff, step=0; - var csize = bin.readShort(md,mdo); mdo+=2; - if (csize > 1) step = Math.floor(max / (csize-1)); - if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); - - - var i; - var row, col; - var len, shl, diff; - var min_v = 0; - var hpred = [0,0]; - var huff = make_decoder(nikon_tree[tree]); - - //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; - var prm = [off,0,0,0]; - //console.log(split); split = 170; - - for (min_v=row=0; row < height; row++) { - if (split && row == split) { - //free (huff); - huff = make_decoder (nikon_tree[tree+1]); - //max_v += (min_v = 16) << 1; - } - for (col=0; col < raw_width; col++) { - i = getbithuff(data,prm,huff[0],huff); - len = i & 15; - shl = i >>> 4; - diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; - if ((diff & (1 << (len-1))) == 0) - diff -= (1 << len) - (shl==0?1:0); - if (col < 2) hpred[col] = vpred[row & 1][col] += diff; - else hpred[col & 1] += diff; - - var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } + return; + } + + var pix = new Uint16Array(16); + var row, col, val, max, min, imax, imin, sh, bit, i, dp; + + var data = new Uint8Array(raw_width+1); + for (row=0; row < height; row++) { + //fread (data, 1, raw_width, ifp); + for(var j=0; j>> 11); + imax = 0x0f & (val >>> 22); + imin = 0x0f & (val >>> 26); + for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); + for (bit=30, i=0; i < 16; i++) + if (i == imax) pix[i] = max; + else if (i == imin) pix[i] = min; + else { + pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; + if (pix[i] > 0x7ff) pix[i] = 0x7ff; + bit += 7; + } + for (i=0; i < 16; i++, col+=2) { + //RAW(row,col) = curve[pix[i] << 1] >> 2; + var clr = pix[i]<<1; //clr = 0xffff; + UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); } + col -= col & 1 ? 1:31; } } - // put 16 bits - UTIF.decode._putsF= function(dt, pos, val) { val = val<<(8-(pos&7)); var o=(pos>>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } - - - UTIF.decode._getbithuff = function(data,prm,nbits, huff) { - var zero_after_ff = 0; - var get_byte = UTIF.decode._get_byte; - var c; - - var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; - - //if (nbits > 25) return 0; - //if (nbits < 0) return bitbuf = vbits = reset = 0; - if (nbits == 0 || vbits < 0) return 0; - while (!reset && vbits < nbits && (c = data[off++]) != -1 && - !(reset = zero_after_ff && c == 0xff && data[off++])) { - //console.log("byte read into c"); - bitbuf = (bitbuf << 8) + c; - vbits += 8; - } - c = (bitbuf << (32-vbits)) >>> (32-nbits); - if (huff) { - vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); - c = huff[c+1]&255; - } else - vbits -= nbits; - if (vbits < 0) throw "e"; - - prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; - - return c; +} + +UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) +{ + var nikon_tree = [ + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ + 5,4,3,6,2,7,1,0,8,9,11,10,12 ], + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ + 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], + [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ + 5,4,6,3,7,2,8,1,9,0,10,11,12 ], + [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ + 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], + [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ + 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], + [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ + 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; + + var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; + + var tree = 0, split = 0; + var make_decoder = UTIF.decode._make_decoder; + var getbithuff = UTIF.decode._getbithuff; + + var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); + //console.log(md[0].toString(16), md[1].toString(16), tiff_bps); + var ver0 = md[mdo++], ver1 = md[mdo++]; + if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; + if (ver0 == 0x46) tree = 2; + if (tiff_bps == 14) tree += 3; + + var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); + for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] + //console.log(vpred); + + + var max = 1 << tiff_bps & 0x7fff, step=0; + var csize = bin.readShort(md,mdo); mdo+=2; + if (csize > 1) step = Math.floor(max / (csize-1)); + if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); + + + var i; + var row, col; + var len, shl, diff; + var min_v = 0; + var hpred = [0,0]; + var huff = make_decoder(nikon_tree[tree]); + + //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; + var prm = [off,0,0,0]; + //console.log(split); split = 170; + + for (min_v=row=0; row < height; row++) { + if (split && row == split) { + //free (huff); + huff = make_decoder (nikon_tree[tree+1]); + //max_v += (min_v = 16) << 1; + } + for (col=0; col < raw_width; col++) { + i = getbithuff(data,prm,huff[0],huff); + len = i & 15; + shl = i >>> 4; + diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - (shl==0?1:0); + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + + var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } + + +UTIF.decode._getbithuff = function(data,prm,nbits, huff) { + var zero_after_ff = 0; + var get_byte = UTIF.decode._get_byte; + var c; + + var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; + + //if (nbits > 25) return 0; + //if (nbits < 0) return bitbuf = vbits = reset = 0; + if (nbits == 0 || vbits < 0) return 0; + while (!reset && vbits < nbits && (c = data[off++]) != -1 && + !(reset = zero_after_ff && c == 0xff && data[off++])) { + //console.log("byte read into c"); + bitbuf = (bitbuf << 8) + c; + vbits += 8; + } + c = (bitbuf << (32-vbits)) >>> (32-nbits); + if (huff) { + vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); + c = huff[c+1]&255; + } else + vbits -= nbits; + if (vbits < 0) throw "e"; + + prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; + + return c; +} + +UTIF.decode._make_decoder = function(source) { + var max, len, h, i, j; + var huff = []; + + for (max=16; max!=0 && !source[max]; max--); + var si=17; + + huff[0] = max; + for (h=len=1; len <= max; len++) + for (i=0; i < source[len]; i++, ++si) + for (j=0; j < 1 << (max-len); j++) + if (h <= 1 << max) + huff[h++] = (len << 8) | source[si]; + return huff; +} + +UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff) +{ + len = Math.min(len, data.length-off); + var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); + + if (tables) { + var SOI = 216, EOI = 217, boff = 0; + for (var i=0; i<(tlen-1); i++) + { + // Skip EOI marker from JPEGTables + if (tables[i]==255 && tables[i+1]==EOI) break; + buff[boff++] = tables[i]; + } - UTIF.decode._make_decoder = function(source) { - var max, len, h, i, j; - var huff = []; - - for (max=16; max!=0 && !source[max]; max--); - var si=17; - - huff[0] = max; - for (h=len=1; len <= max; len++) - for (i=0; i < source[len]; i++, ++si) - for (j=0; j < 1 << (max-len); j++) - if (h <= 1 << max) - huff[h++] = (len << 8) | source[si]; - return huff; + // Skip SOI marker from data + var byte1 = data[off], byte2 = data[off + 1]; + if (byte1!=255 || byte2!=SOI) + { + buff[boff++] = byte1; + buff[boff++] = byte2; + } + for (var i=2; i>>8); } + else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } } - else for (var i=0; i>>8); } - else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } - } - else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 - var rst = 16-bps; - for(var i=0; i 1); - } + soff = soffTag[0]; + isTiled = (soffTag.length > 1); + } - if(!isTiled) + if(!isTiled) + { + if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; + if(jpgIchgFmt!=null) { - if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; - if(jpgIchgFmt!=null) - { - if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; - else log("JPEGInterchangeFormat does not point to SOI"); + if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; + else log("JPEGInterchangeFormat does not point to SOI"); - if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); - else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); + if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); + else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); - if(joff != null) return { jpegOffset: joff }; - } + if(joff != null) return { jpegOffset: joff }; } + } - if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } - - if(jpgIchgFmt!=null) - if(jpgIchgFmtLen!=null) - if(jiflen >= 2 && (jifoff+jiflen) <= soff) - { - if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); - else tables = new Uint8Array(jiflen); - - for(i=0; i offset to first strip or tile"); - - if(tables == null) - { - var ooff = 0, out = []; - out[ooff++] = 255; out[ooff++] = SOI; + if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } - var qtables = img["t519"]; - if(qtables==null) throw new Error("JPEGQTables tag is missing"); - for(i=0; i= 2 && (jifoff+jiflen) <= soff) { - out[ooff++] = 255; out[ooff++] = DQT; out[ooff++] = 0; out[ooff++] = 67; out[ooff++] = i; - for(j=0; j<64; j++) out[ooff++] = data[off+qtables[i]+j]; - } + if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); + else tables = new Uint8Array(jiflen); - for(k=0; k<2; k++) - { - var htables = img[(k == 0) ? "t520" : "t521"]; - if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing"); - for(i=0; i>> 8); out[ooff++] = nc & 255; - out[ooff++] = (i | (k << 4)); - for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; - for(j=0; j offset to first strip or tile"); - out[ooff++] = 255; out[ooff++] = SOF0; - out[ooff++] = 0; out[ooff++] = 8 + 3*spp; out[ooff++] = 8; - out[ooff++] = (img.height >>> 8) & 255; out[ooff++] = img.height & 255; - out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; - out[ooff++] = spp; - if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } - else for(i=0; i<3; i++) - { - out[ooff++] = i + 1; - out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - out[ooff++] = i; - } - - if(jpgresint!=null && jpgresint[0]!=0) - { - out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; - out[ooff++] = (jpgresint[0] >>> 8) & 255; - out[ooff++] = jpgresint[0] & 255; - } - - tables = new Uint8Array(out); - } + if(tables == null) + { + var ooff = 0, out = []; + out[ooff++] = 255; out[ooff++] = SOI; - var sofpos = -1; - i = 0; - while(i < (tables.length - 1)) { - if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } - i++; + var qtables = img["t519"]; + if(qtables==null) throw new Error("JPEGQTables tag is missing"); + for(i=0; i>> 8) & 255; tables[tmpoff++] = img.height & 255; - tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; - tables[tmpoff++] = spp; - if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } - else for(i=0; i<3; i++) + var htables = img[(k == 0) ? "t520" : "t521"]; + if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing"); + for(i=0; i>> 8); out[ooff++] = nc & 255; + out[ooff++] = (i | (k << 4)); + for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; + for(j=0; j>> 8) & 255; out[ooff++] = img.height & 255; + out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; + out[ooff++] = spp; + if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } + else for(i=0; i<3; i++) { - var soslen = (data[soff+2]<<8) | data[soff+3]; - sosMarker = new Uint8Array(soslen+2); - sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; - for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; + out[ooff++] = i + 1; + out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + out[ooff++] = i; } - else + + if(jpgresint!=null && jpgresint[0]!=0) { - sosMarker = new Uint8Array(2 + 6 + 2*spp); - var sosoff = 0; - sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; - if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } - else for(i=0; i<3; i++) - { - sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; - } - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; + out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; + out[ooff++] = (jpgresint[0] >>> 8) & 255; + out[ooff++] = jpgresint[0] & 255; } - return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; + tables = new Uint8Array(out); } - UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) - { - var i, dlen, tlen, buff, buffoff; - var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); + var sofpos = -1; + i = 0; + while(i < (tables.length - 1)) { + if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } + i++; + } - if(jpegData.jpegOffset!=null) + if(sofpos == -1) + { + var tmptab = new Uint8Array(tables.length + 10 + 3*spp); + tmptab.set(tables); + var tmpoff = tables.length; + sofpos = tables.length; + tables = tmptab; + + tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; + tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; + tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; + tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; + tables[tmpoff++] = spp; + if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } + else for(i=0; i<3; i++) { - dlen = off+len-jpegData.jpegOffset; - buff = new Uint8Array(dlen); - for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; - buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; - - if(data[off]!=255 || data[off+1]!=SOS) - { - buff.set(jpegData.sosMarker, buffoff); - buffoff += sosMarker.length; - } - for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } - if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } + sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; } - return toff; + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; } - UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) + return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; +} + +UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) +{ + var i, dlen, tlen, buff, buffoff; + var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); + + if(jpegData.jpegOffset!=null) + { + dlen = off+len-jpegData.jpegOffset; + buff = new Uint8Array(dlen); + for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; + buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; + + if(data[off]!=255 || data[off+1]!=SOS) { - var b = data[off], msk = (b>>>6), n = (b&63); off++; - if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + buff.set(jpegData.sosMarker, buffoff); + buffoff += sosMarker.length; } + for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } + if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } + } + return toff; +} + +UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) +{ + var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; + var lim = off+len, qoff = toff*2, px = 0; + while(off>>6), n = (b&63); off++; + if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + } +} + +UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 }; +UTIF.decode._lens = ( function() +{ + var addKeys = function(lens, arr, i0, inc) { for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + if(mode=="H") + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + } + } + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + } + if(line.length==w && mode=="") + { + U._writeBits(line, tgt, toff*8+y*bipl); + clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; + } + //if(wrd.length>150) { log(wrd); break; throw "e"; } + } +} + +UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } + +UTIF.decode._makeDiff = function(line) +{ + var out = []; if(line[0]==1) out.push(0,1); + for(var i=1; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + len = U._lens[clr][wrd]; + if(len!=null) { + U._addNtimes(line,len,clr); wrd=""; + if(len<64) clr = 1-clr; + if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } + } + } +} + +UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) +{ + var U = UTIF.decode, boff=off<<3, len=0, wrd=""; + var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; - while((boff>>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; if(mode=="H") { if(U._lens[clr][wrd]!=null) @@ -1042,630 +1123,547 @@ const UTIF = /* @__PURE */ ( () => { if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } } - if(line.length==w && mode=="") - { - U._writeBits(line, tgt, toff*8+y*bipl); - clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; + } + if(wrd.endsWith("000000000001")) // needed for some files + { + if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); + if(twoDim) { + if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; + if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; + boff++; } - //if(wrd.length>150) { log(wrd); break; throw "e"; } + //log("EOL",y, "next 1D:", is1D); + wrd=""; clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; } } - - UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } - - UTIF.decode._makeDiff = function(line) - { - var out = []; if(line[0]==1) out.push(0,1); - for(var i=1; i>>3] |= (bits[i]<<(7-((boff+i)&7))); +} + +UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>>----------------"); + for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - len = U._lens[clr][wrd]; - if(len!=null) { - U._addNtimes(line,len,clr); wrd=""; - if(len<64) clr = 1-clr; - if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } - } + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if(type== 1 || type==7) { var no=(num<5 ? offset-4 : voff); if(no+num>data.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } + if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); + if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); + else arr = new Uint8Array(data.buffer, o0, len); } + if(type== 3) { for(var j=0; j>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - if(is1D) - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } - } - } - else - { - if(mode=="H") - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } - } + if(tag==37500 && prm.parseMN) { + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if(bin.readASCII(mn,0,5)=="Nikon") ifd.makerNote = UTIF["decode"](mn.slice(10).buffer)[0]; + else if(bin.readASCII(mn,0,5)=="OLYMP" || bin.readASCII(mn,0,9)=="OM SYSTEM") { // ??? + var inds = [8208,8224,8240,8256,8272]; + var subsub = []; UTIF._readIFD(bin, mn, mn[1]==77 ? 16 : (mn[5]==85 ? 12 : 8), subsub, depth+1, prm); + var obj = ifd.makerNote = subsub.pop(); + for(var j=0; j=0) U._writeBits(line, tgt, toff*8+y*bipl); - if(twoDim) { - if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; - if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; - boff++; - } - //log("EOL",y, "next 1D:", is1D); - wrd=""; clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; + else if(bin.readUshort(data,voff)<300 && bin.readUshort(data,voff+4)<=12){ + var subsub=[]; UTIF._readIFD(bin, data, voff, subsub, depth+1, prm); + ifd.makerNote = subsub[0]; } } - if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); } + ifds.push(ifd); + if(prm.debug) log(" ".repeat(depth),"<<<---------------"); + return offset; +} + +UTIF._writeIFD = function(bin, types, data, offset, ifd) +{ + var keys = Object.keys(ifd), knum=keys.length; if(ifd["exifIFD"]) knum--; if(ifd["gpsiIFD"]) knum--; + bin.writeUshort(data, offset, knum); offset+=2; - UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); + var key = keys[ki]; if(key=="t34665" || key=="t34853") continue; + if(key=="exifIFD") key="t34665"; if(key=="gpsiIFD") key="t34853"; + var tag = parseInt(key.slice(1)), type = types.main[tag]; if(type==null) type=types.rest[tag]; + if(type==null || type==0) throw new Error("unknown type of tag: "+tag); + //console.log(offset+":", tag, type, eoff); + var val = ifd[key]; + if(tag==34665) { + var outp = UTIF._writeIFD(bin, types, data, eoff, ifd["exifIFD"]); + val = [eoff]; eoff = outp[1]; + } + if(tag==34853) { + var outp = UTIF._writeIFD(bin, UTIF._types.gps, data, eoff, ifd["gpsiIFD"]); + val = [eoff]; eoff = outp[1]; + } + if(type==2) val=val[0]+"\u0000"; var num = val.length; + bin.writeUshort(data, offset, tag ); offset+=2; + bin.writeUshort(data, offset, type); offset+=2; + bin.writeUint (data, offset, num ); offset+=4; + + var dlen = [-1, 1, 1, 2, 4, 8, 0, 1, 0, 4, 8, 0, 8][type] * num; //if(dlen<1) throw "e"; + var toff = offset; + if(dlen>4) { bin.writeUint(data, offset, eoff); toff=eoff; } + + if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } + offset += 4; } - - UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } + if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>>----------------"); - for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } - if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); - if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); - else arr = new Uint8Array(data.buffer, o0, len); } - if(type== 3) { for(var j=0; j>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } + if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } } - ifds.push(ifd); - if(prm.debug) log(" ".repeat(depth),"<<<---------------"); - return offset; } - - UTIF._writeIFD = function(bin, types, data, offset, ifd) + else if(intp==2) { - var keys = Object.keys(ifd), knum=keys.length; if(ifd["exifIFD"]) knum--; if(ifd["gpsiIFD"]) knum--; - bin.writeUshort(data, offset, knum); offset+=2; - - var eoff = offset + knum*12 + 4; - - for(var ki=0; ki4) { bin.writeUint(data, offset, eoff); toff=eoff; } - - if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } - offset += 4; + if(smpls==1) for(var i=0; i=4) for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; - var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; - var img = new Uint8Array(area*4); - //console.log(out); - // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK - var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); - if(out["t262"]==null && bps==1) intp=0; - - var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); - var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format - var bpl = Math.ceil(smpls*bps*w/8); - - //log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); - - if(false) {} - else if(intp==0) - { - scl = 1/256; // "Photopeatest.tif" - for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } - if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>>3)]>>>(7- (x&7)))& 1; + else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; + else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; + else if(bps==8) mi= data[dof+x*smpls]; + else throw bps; + img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; } - } - else if(intp==1) - { - if(scl==null) scl=1/256; - var f32 = ((data.length&3)==0) ? new Float32Array(data.buffer) : null; + } + else if(intp==5) + { + var gotAlpha = smpls>4 ? 1 : 0; + for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } - if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } + if(window.UDOC) { + var C=data[si], M=data[si+1], Y=data[si+2], K=data[si+3]; + var c = UDOC.C.cmykToRgb([C*(1/255), M*(1/255), Y*(1/255), K*(1/255)]); + img[qi] = ~~(0.5+255*c[0]); img[qi+1] = ~~(0.5+255*c[1]); img[qi+2] = ~~(0.5+255*c[2]); } - } - else if(intp==2) - { - if(bps== 8) - { - if(smpls==1) for(var i=0; i=4) for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; - - for(var y=0; y>>3)]>>>(7- (x&7)))& 1; - else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; - else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; - else if(bps==8) mi= data[dof+x*smpls]; - else throw bps; - img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; - } + img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha; } - else if(intp==5) - { - var gotAlpha = smpls>4 ? 1 : 0; - for(var i=0; i>>1); + var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; - if(window.UDOC) { - var C=data[si], M=data[si+1], Y=data[si+2], K=data[si+3]; - var c = UDOC.C.cmykToRgb([C*(1/255), M*(1/255), Y*(1/255), K*(1/255)]); - img[qi] = ~~(0.5+255*c[0]); img[qi+1] = ~~(0.5+255*c[1]); img[qi+2] = ~~(0.5+255*c[2]); - } - else { - var C=255-data[si], M=255-data[si+1], Y=255-data[si+2], K=(255-data[si+3])*(1/255); - img[qi]=~~(C*K+0.5); img[qi+1]=~~(M*K+0.5); img[qi+2]=~~(Y*K+0.5); - } + var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; + var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; + var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; - img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha; + img[qi ]=Math.max(0,Math.min(255,r)); + img[qi+1]=Math.max(0,Math.min(255,g)); + img[qi+2]=Math.max(0,Math.min(255,b)); + img[qi+3]=255; } } - else if(intp==6 && out["t278"]) { // only for DSC_1538.TIF - var rps = out["t278"][0]; - for(var y=0; y>>1); - var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; - - var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; - var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; - var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; - - img[qi ]=Math.max(0,Math.min(255,r)); - img[qi+1]=Math.max(0,Math.min(255,g)); - img[qi+2]=Math.max(0,Math.min(255,b)); - img[qi+3]=255; - } + var L = Math.pow(2, (L + 0.5) / 256 - 64); + var u = (data[si+3] + 0.5) / 410; + var v = (data[si+5] + 0.5) / 410; + + // Luv to xyY + var sX = (9 * u) / (6 * u - 16 * v + 12); + var sY = (4 * v) / (6 * u - 16 * v + 12); + var bY = L; + + // xyY to XYZ + var X = (sX*bY)/sY, Y = bY, Z = (1-sX-sY)*bY/sY; + + + var r = 2.690*X -1.276*Y -0.414*Z + var g = -1.022*X +1.978*Y +0.044*Z + var b = 0.061*X -0.224*Y +1.163*Z + + img[qi ] = gamma(Math.min(r,1))*255; + img[qi+1] = gamma(Math.min(g,1))*255; + img[qi+2] = gamma(Math.min(b,1))*255; + img[qi+3] = 255; } - } - else if(intp==32845) { - - for(var y=0; yma) { ma=ar; page=img; } - } - UTIF.decodeImage(buff, page, ifds); - var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; - - var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; - var ctx = cnv.getContext("2d"); - var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); - ctx.putImageData(imgd,0,0); - return cnv.toDataURL(); - } - - - UTIF._binBE = - { - nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, - readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, - readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, - writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, - writeDouble: function(buff, p, n) - { - UTIF._binBE.fl64[0] = n; - for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; - } +} + +UTIF._xhrs = []; UTIF._imgs = []; +UTIF._imgLoaded = function(e) { + var ind = UTIF._xhrs.indexOf(e.target), img = UTIF._imgs[ind]; + UTIF._xhrs.splice(ind,1); UTIF._imgs.splice(ind,1); + + img.setAttribute("src",UTIF.bufferToURI(e.target.response)); +} + +UTIF.bufferToURI = function(buff) { + var ifds = UTIF.decode(buff); //console.log(ifds); + var vsns = ifds, ma=0, page=vsns[0]; if(ifds[0].subIFD) vsns = vsns.concat(ifds[0].subIFD); + for(var i=0; ima) { ma=ar; page=img; } } - UTIF._binBE.ui8 = new Uint8Array (8); - UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); - UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer); - UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer); - UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer); - UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); - - UTIF._binLE = + UTIF.decodeImage(buff, page, ifds); + var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; + + var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; + var ctx = cnv.getContext("2d"); + var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); + ctx.putImageData(imgd,0,0); + return cnv.toDataURL(); +} + + +UTIF._binBE = +{ + nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, + readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, + readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, + writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, + writeDouble: function(buff, p, n) { - nextZero : UTIF._binBE.nextZero, - readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, - readASCII : UTIF._binBE.readASCII, - readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, - readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, - - writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, - writeASCII : UTIF._binBE.writeASCII + UTIF._binBE.fl64[0] = n; + for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; } - UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) +} +UTIF._binBE.ui8 = new Uint8Array (8); +UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); +UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer); +UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer); +UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer); +UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); + +UTIF._binLE = +{ + nextZero : UTIF._binBE.nextZero, + readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, + readASCII : UTIF._binBE.readASCII, + readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, + readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, + + writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, + writeASCII : UTIF._binBE.writeASCII +} +UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) +{ + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min(tw, w-xoff); + var ylim = Math.min(th, h-yoff); + for(var y=0; y>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); - var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; - w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; - h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; - cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; - d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; - if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); - d+=Y&15;while(w>>4; - if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); - n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; - while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; - H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; - H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; - return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); - (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; - V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; - N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; - N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); - H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); - n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); - - - - UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; - for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x>--s&1; - Y=I[Y+F]}E[w]=Y}}function z(h,V,I,f){if(h[V+3]!=255)return 0;if(I==0)return V;for(var w=0;w<2;w++){if(h[V+w]==0){h[V+w]=h.length; - h.push(0,0,f,255)}var x=z(h,h[V+w],I-1,f+1);if(x!=0)return x}return 0}function i(h){var V=h.b,I=h.f; - while(V<25&&h.a>(V.b-=h)&65535>>16-h}function g(h,V){var I=h[0],f=0,w=255,x=0;if(V.b<16)i(V);var T=V.f>>V.b-8&255; - f=h[1][T];w=I[f+3];V.b-=I[f+2];while(w==255){x=V.f>>--V.b&1;f=I[f+x];w=I[f+3]}return w}function P(h,V){if(h<32768>>16-V)h+=-(1<>4,J&15]}}else if(Y==65476){var a3=O+F-2;while(O>>4];x[v[0]]=v.slice(1)}I=l();O+=2;break}else if(Y==65501){w=m()}else{O+=F-2}}var a4=f>8?Uint16Array:Uint8Array,$=new a4(s*_*E),M={b:0,f:0,c:I==8,a:O,data:b,d:b.length,e:w}; - if(M.c)a1($,_*E,M,G[0],s);else{var c=[],p=0,D=0;for(var t=0;tp)p=S; - if(K>D)D=K;c.push(S*K)}if(p!=1||D!=1){if(E!=3||c[1]!=1||c[2]!=1)throw"e";if(p!=2||D!=1&&D!=2)throw"e"; - var u=[],Z=0;for(var t=0;t>>1)*B+(S>>>1))*Z,y=(K&1)*2+(S&1); - $[q]=n[k+y];$[q+1]=n[k+4];$[q+2]=n[k+5]}else for(var S=0;S<_;S++){var q=(K*_+S)*E,k=(K*B+(S>>>1))*Z,y=S&1; - $[q]=n[k+y];$[q+1]=n[k+2];$[q+2]=n[k+3]}}}else{X($,_*E,M,G,E,s);if(w==0)j($,I,_,s,0,E,E,f);else{var U=Math.floor(w/_); - for(var K=0;K>>1);else if(V==6)Q=h[J]+(r-h[J-G]>>>1);else if(V==7)Q=r+h[J]>>>1;else throw V; - h[a]+=Q}}}}return C}(); - - - (function(){var G=0,F=1,i=2,b=3,J=4,N=5,E=6,s=7,c=8,T=9,a3=10,f=11,q=12,M=13,m=14,x=15,L=16,$=17,p=18; - function a5(t){var Z=UTIF._binBE.readUshort,u={b:Z(t,0),i:t[2],C:t[3],u:t[4],q:Z(t,5),k:Z(t,7),e:Z(t,9),l:Z(t,11),s:t[13],d:Z(t,14)}; - if(u.b!=18771||u.i>1||u.q<6||u.q%6||u.e<768||u.e%24||u.l!=768||u.k=u.l||u.s>16||u.s!=u.k/u.l||u.s!=Math.ceil(u.e/u.l)||u.d!=u.q/6||u.u!=12&&u.u!=14&&u.u!=16||u.C!=16&&u.C!=0){throw"Invalid data"}if(u.i==0){throw"Not implemented. We need this file!"}u.h=u.C==16; - u.m=(u.h?u.l*2/3:u.l>>>1)|0;u.A=u.m+2;u.f=64;u.g=(1<>>6);for(var e=0;e<3;e++){for(var Q=0; - Q<41;Q++){Z[e][Q]=[u,1]}}return Z}function a4(t){for(var Z=-1,u=0;!u;Z++){u=t[t.j]>>>7-t.a&1;t.a++;t.a&=7; - if(!t.a)t.j++}return Z}function K(t,Z){var u=0,e=8-t.a,Q=t.j,V=t.a;if(Z){if(Z>=e){do{u<<=e;Z-=e;u|=t[t.j]&(1<=8)}if(Z){u<<=Z;e-=Z;u|=t[t.j]>>>e&(1<n&&C>>2;if(o){w[X]=h;return}l=Z.t*Z.c[t.g+Y-H]+Z.c[t.g+g-Y]}else{h=Y>g&&Y>P||Y>>2:A+v>>>1; - l=Z.t*Z.c[t.g+Y-g]+Z.c[t.g+g-A]}R=y(l);var W=a4(u);if(W>>1):a>>>1; - O[R][0]+=y(a);if(O[R][1]==t.f){O[R][0]>>>=1;O[R][1]>>>=1}O[R][1]++;h=l<0?h-a:h+a;if(t.i){if(h<0)h+=Z.w; - else if(h>t.g)h-=Z.w}w[X]=h>=0?Math.min(h,t.g):0}function U(t,Z,u){var e=t[0].length;for(var Q=Z;Q<=u; - Q++){t[Q][0]=t[Q-1][1];t[Q][e-1]=t[Q-1][e-2]}}function B(t){U(t,s,q);U(t,i,J);U(t,x,$)}function _(t,Z,u,e,Q,V,O,o,X,k,j,I,a){var l=0,R=1,w=QJ; - while(R8){r(t,Z,u,e,Q,R,o[X]);r(t,Z,u,e,V,R,o[X]);R+=2}}B(e)}function a8(t,Z,u,e,Q,V){_(t,Z,u,e,i,s,Q,V,0,0,1,0,8); - _(t,Z,u,e,c,x,Q,V,1,0,1,0,8);_(t,Z,u,e,b,T,Q,V,2,1,0,3,0);_(t,Z,u,e,a3,L,Q,V,0,0,0,3,2);_(t,Z,u,e,J,f,Q,V,1,0,0,3,2); - _(t,Z,u,e,q,$,Q,V,2,1,0,3,0)}function a9(t,Z,u,e,Q,V){var O=V.length,o=t.l;if(Q+1==t.s)o=t.e-Q*t.l;var X=6*t.e*e+Q*t.l; - for(var k=0;k<6;k++){for(var j=0;j>>1)}else if(I==2){a=x+(k>>>1)}else{a=s+k}var l=t.h?(j*2/3&2147483646|j%3&1)+(j%3>>>1):j>>>1; - Z[X+j]=u[a][l+1]}X+=t.e}}UTIF._decompressRAF=function(t,Z){var u=a5(t),e=a7(t,u),Q=a2(u),V=new Int16Array(u.e*u.q); - if(Z==null){Z=u.h?[[1,1,0,1,1,2],[1,1,2,1,1,0],[2,0,1,0,2,1],[1,1,2,1,1,0],[1,1,0,1,1,2],[0,2,1,2,0,1]]:[[0,1],[3,2]]}var O=[[G,b],[F,J],[N,f],[E,q],[M,L],[m,$]],o=[]; - for(var X=0;X>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); + var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; + w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; + h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; + cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; + d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; + if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); + d+=Y&15;while(w>>4; + if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); + n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; + while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; + H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; + H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; + return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); + (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; + V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; + N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; + N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); + H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); + n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); + + + +UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; +for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x>--s&1; +Y=I[Y+F]}E[w]=Y}}function z(h,V,I,f){if(h[V+3]!=255)return 0;if(I==0)return V;for(var w=0;w<2;w++){if(h[V+w]==0){h[V+w]=h.length; +h.push(0,0,f,255)}var x=z(h,h[V+w],I-1,f+1);if(x!=0)return x}return 0}function i(h){var V=h.b,I=h.f; +while(V<25&&h.a>(V.b-=h)&65535>>16-h}function g(h,V){var I=h[0],f=0,w=255,x=0;if(V.b<16)i(V);var T=V.f>>V.b-8&255; +f=h[1][T];w=I[f+3];V.b-=I[f+2];while(w==255){x=V.f>>--V.b&1;f=I[f+x];w=I[f+3]}return w}function P(h,V){if(h<32768>>16-V)h+=-(1<>4,J&15]}}else if(Y==65476){var a3=O+F-2;while(O>>4];x[v[0]]=v.slice(1)}I=l();O+=2;break}else if(Y==65501){w=m()}else{O+=F-2}}var a4=f>8?Uint16Array:Uint8Array,$=new a4(s*_*E),M={b:0,f:0,c:I==8,a:O,data:b,d:b.length,e:w}; +if(M.c)a1($,_*E,M,G[0],s);else{var c=[],p=0,D=0;for(var t=0;tp)p=S; +if(K>D)D=K;c.push(S*K)}if(p!=1||D!=1){if(E!=3||c[1]!=1||c[2]!=1)throw"e";if(p!=2||D!=1&&D!=2)throw"e"; +var u=[],Z=0;for(var t=0;t>>1)*B+(S>>>1))*Z,y=(K&1)*2+(S&1); +$[q]=n[k+y];$[q+1]=n[k+4];$[q+2]=n[k+5]}else for(var S=0;S<_;S++){var q=(K*_+S)*E,k=(K*B+(S>>>1))*Z,y=S&1; +$[q]=n[k+y];$[q+1]=n[k+2];$[q+2]=n[k+3]}}}else{X($,_*E,M,G,E,s);if(w==0)j($,I,_,s,0,E,E,f);else{var U=Math.floor(w/_); +for(var K=0;K>>1);else if(V==6)Q=h[J]+(r-h[J-G]>>>1);else if(V==7)Q=r+h[J]>>>1;else throw V; +h[a]+=Q}}}}return C}(); + + +(function(){var G=0,F=1,i=2,b=3,J=4,N=5,E=6,s=7,c=8,T=9,a3=10,f=11,q=12,M=13,m=14,x=15,L=16,$=17,p=18; +function a5(t){var Z=UTIF._binBE.readUshort,u={b:Z(t,0),i:t[2],C:t[3],u:t[4],q:Z(t,5),k:Z(t,7),e:Z(t,9),l:Z(t,11),s:t[13],d:Z(t,14)}; +if(u.b!=18771||u.i>1||u.q<6||u.q%6||u.e<768||u.e%24||u.l!=768||u.k=u.l||u.s>16||u.s!=u.k/u.l||u.s!=Math.ceil(u.e/u.l)||u.d!=u.q/6||u.u!=12&&u.u!=14&&u.u!=16||u.C!=16&&u.C!=0){throw"Invalid data"}if(u.i==0){throw"Not implemented. We need this file!"}u.h=u.C==16; +u.m=(u.h?u.l*2/3:u.l>>>1)|0;u.A=u.m+2;u.f=64;u.g=(1<>>6);for(var e=0;e<3;e++){for(var Q=0; +Q<41;Q++){Z[e][Q]=[u,1]}}return Z}function a4(t){for(var Z=-1,u=0;!u;Z++){u=t[t.j]>>>7-t.a&1;t.a++;t.a&=7; +if(!t.a)t.j++}return Z}function K(t,Z){var u=0,e=8-t.a,Q=t.j,V=t.a;if(Z){if(Z>=e){do{u<<=e;Z-=e;u|=t[t.j]&(1<=8)}if(Z){u<<=Z;e-=Z;u|=t[t.j]>>>e&(1<n&&C>>2;if(o){w[X]=h;return}l=Z.t*Z.c[t.g+Y-H]+Z.c[t.g+g-Y]}else{h=Y>g&&Y>P||Y>>2:A+v>>>1; +l=Z.t*Z.c[t.g+Y-g]+Z.c[t.g+g-A]}R=y(l);var W=a4(u);if(W>>1):a>>>1; +O[R][0]+=y(a);if(O[R][1]==t.f){O[R][0]>>>=1;O[R][1]>>>=1}O[R][1]++;h=l<0?h-a:h+a;if(t.i){if(h<0)h+=Z.w; +else if(h>t.g)h-=Z.w}w[X]=h>=0?Math.min(h,t.g):0}function U(t,Z,u){var e=t[0].length;for(var Q=Z;Q<=u; +Q++){t[Q][0]=t[Q-1][1];t[Q][e-1]=t[Q-1][e-2]}}function B(t){U(t,s,q);U(t,i,J);U(t,x,$)}function _(t,Z,u,e,Q,V,O,o,X,k,j,I,a){var l=0,R=1,w=QJ; +while(R8){r(t,Z,u,e,Q,R,o[X]);r(t,Z,u,e,V,R,o[X]);R+=2}}B(e)}function a8(t,Z,u,e,Q,V){_(t,Z,u,e,i,s,Q,V,0,0,1,0,8); +_(t,Z,u,e,c,x,Q,V,1,0,1,0,8);_(t,Z,u,e,b,T,Q,V,2,1,0,3,0);_(t,Z,u,e,a3,L,Q,V,0,0,0,3,2);_(t,Z,u,e,J,f,Q,V,1,0,0,3,2); +_(t,Z,u,e,q,$,Q,V,2,1,0,3,0)}function a9(t,Z,u,e,Q,V){var O=V.length,o=t.l;if(Q+1==t.s)o=t.e-Q*t.l;var X=6*t.e*e+Q*t.l; +for(var k=0;k<6;k++){for(var j=0;j>>1)}else if(I==2){a=x+(k>>>1)}else{a=s+k}var l=t.h?(j*2/3&2147483646|j%3&1)+(j%3>>>1):j>>>1; +Z[X+j]=u[a][l+1]}X+=t.e}}UTIF._decompressRAF=function(t,Z){var u=a5(t),e=a7(t,u),Q=a2(u),V=new Int16Array(u.e*u.q); +if(Z==null){Z=u.h?[[1,1,0,1,1,2],[1,1,2,1,1,0],[2,0,1,0,2,1],[1,1,2,1,1,0],[1,1,0,1,1,2],[0,2,1,2,0,1]]:[[0,1],[3,2]]}var O=[[G,b],[F,J],[N,f],[E,q],[M,L],[m,$]],o=[]; +for(var X=0;X Date: Sat, 7 Oct 2023 09:50:25 -0500 Subject: [PATCH 22/45] OrbitControls: destructure MathUtils for tree-shaking See https://github.com/mrdoob/three.js/pull/26912#discussion_r1349519605 --- examples/jsm/controls/OrbitControls.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/jsm/controls/OrbitControls.js b/examples/jsm/controls/OrbitControls.js index b40c85d7aa6545..6839795d0c0fa1 100644 --- a/examples/jsm/controls/OrbitControls.js +++ b/examples/jsm/controls/OrbitControls.js @@ -8,6 +8,7 @@ import { Vector3, Plane, Ray, + MathUtils, } from 'three'; // OrbitControls performs orbiting, dollying (zooming), and panning. @@ -22,7 +23,9 @@ const _startEvent = { type: 'start' }; const _endEvent = { type: 'end' }; const _ray = /* @__PURE__ */ new Ray(); const _plane = /* @__PURE__ */ new Plane(); -const TILT_LIMIT = Math.cos( 70 * ( Math.PI / 180 ) ); + +const { DEG2RAD } = MathUtils; +const TILT_LIMIT = Math.cos( 70 * DEG2RAD ); class OrbitControls extends EventDispatcher { From 4654d8e0df465c8da0fb1f1888158b9fac8c37ac Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:36:35 -0500 Subject: [PATCH 23/45] offscreen: revert changes See https://github.com/mrdoob/three.js/pull/26910#discussion_r1349552937 --- examples/jsm/offscreen/offscreen.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/jsm/offscreen/offscreen.js b/examples/jsm/offscreen/offscreen.js index e117aaa57a372a..410c99bd9366f5 100644 --- a/examples/jsm/offscreen/offscreen.js +++ b/examples/jsm/offscreen/offscreen.js @@ -1,12 +1,8 @@ import init from './scene.js'; -/* @__PURE__ */ ( () => { +self.onmessage = function ( message ) { - self.onmessage = function ( message ) { + const data = message.data; + init( data.drawingSurface, data.width, data.height, data.pixelRatio, data.path ); - const data = message.data; - init( data.drawingSurface, data.width, data.height, data.pixelRatio, data.path ); - - }; - -} )(); +}; \ No newline at end of file From b4f33bc7a8e3173a2d3b1cef57c7b5b0c8a67391 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:46:31 -0500 Subject: [PATCH 24/45] mmdparser: tree-shake with IIFE --- examples/jsm/libs/mmdparser.module.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/jsm/libs/mmdparser.module.js b/examples/jsm/libs/mmdparser.module.js index 5a587f10dc43c6..637d65e79b39b2 100644 --- a/examples/jsm/libs/mmdparser.module.js +++ b/examples/jsm/libs/mmdparser.module.js @@ -4,6 +4,8 @@ * Simple CharsetEncoder. */ +const { CharsetEncoder, Parser } = /* @__PURE__ */ ( () => { + function CharsetEncoder() { } @@ -11522,9 +11524,13 @@ Parser.prototype.leftToRightVpd = function ( vpd ) { }; -var MMDParser = { - CharsetEncoder: CharsetEncoder, - Parser: Parser +return { CharsetEncoder, Parser }; + +} )(); + +const MMDParser = { + CharsetEncoder, + Parser }; export { MMDParser, CharsetEncoder, Parser }; From 2cae69b48b4f81e35f726175569badb1d17e95a5 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:09:29 -0500 Subject: [PATCH 25/45] Addons: keep static methods --- .../jsm/animation/AnimationClipCreator.js | 26 +++++----- examples/jsm/capabilities/WebGL.js | 26 +++++----- examples/jsm/capabilities/WebGPU.js | 10 ++-- examples/jsm/lights/LightProbeGenerator.js | 10 ++-- .../jsm/lights/RectAreaLightUniformsLib.js | 6 +-- examples/jsm/loaders/SVGLoader.js | 21 ++++---- examples/jsm/math/ColorConverter.js | 10 ++-- examples/jsm/math/ConvexHull.js | 48 +++++++++---------- examples/jsm/misc/ConvexObjectBreaker.js | 20 ++++---- examples/jsm/nodes/materials/NodeMaterial.js | 8 +--- examples/jsm/utils/LDrawUtils.js | 6 +-- examples/jsm/webxr/ARButton.js | 6 +-- examples/jsm/webxr/VRButton.js | 12 ++--- 13 files changed, 97 insertions(+), 112 deletions(-) diff --git a/examples/jsm/animation/AnimationClipCreator.js b/examples/jsm/animation/AnimationClipCreator.js index daa243daf44a69..cf9ee6bb425477 100644 --- a/examples/jsm/animation/AnimationClipCreator.js +++ b/examples/jsm/animation/AnimationClipCreator.js @@ -7,9 +7,9 @@ import { VectorKeyframeTrack } from 'three'; -const AnimationClipCreator = { +class AnimationClipCreator { - CreateRotationAnimation( period, axis = 'x' ) { + static CreateRotationAnimation( period, axis = 'x' ) { const times = [ 0, period ], values = [ 0, 360 ]; @@ -19,9 +19,9 @@ const AnimationClipCreator = { return new AnimationClip( null, period, [ track ] ); - }, + } - CreateScaleAxisAnimation( period, axis = 'x' ) { + static CreateScaleAxisAnimation( period, axis = 'x' ) { const times = [ 0, period ], values = [ 0, 1 ]; @@ -31,9 +31,9 @@ const AnimationClipCreator = { return new AnimationClip( null, period, [ track ] ); - }, + } - CreateShakeAnimation( duration, shakeScale ) { + static CreateShakeAnimation( duration, shakeScale ) { const times = [], values = [], tmp = new Vector3(); @@ -53,9 +53,9 @@ const AnimationClipCreator = { return new AnimationClip( null, duration, [ track ] ); - }, + } - CreatePulsationAnimation( duration, pulseScale ) { + static CreatePulsationAnimation( duration, pulseScale ) { const times = [], values = [], tmp = new Vector3(); @@ -75,9 +75,9 @@ const AnimationClipCreator = { return new AnimationClip( null, duration, [ track ] ); - }, + } - CreateVisibilityAnimation( duration ) { + static CreateVisibilityAnimation( duration ) { const times = [ 0, duration / 2, duration ], values = [ true, false, true ]; @@ -87,9 +87,9 @@ const AnimationClipCreator = { return new AnimationClip( null, duration, [ track ] ); - }, + } - CreateMaterialColorAnimation( duration, colors ) { + static CreateMaterialColorAnimation( duration, colors ) { const times = [], values = [], timeStep = duration / colors.length; @@ -111,6 +111,6 @@ const AnimationClipCreator = { } -}; +} export { AnimationClipCreator }; diff --git a/examples/jsm/capabilities/WebGL.js b/examples/jsm/capabilities/WebGL.js index 9ddc7ad454d119..2abf2614fccbc0 100644 --- a/examples/jsm/capabilities/WebGL.js +++ b/examples/jsm/capabilities/WebGL.js @@ -1,6 +1,6 @@ -const WebGL = { +class WebGL { - isWebGLAvailable() { + static isWebGLAvailable() { try { @@ -13,9 +13,9 @@ const WebGL = { } - }, + } - isWebGL2Available() { + static isWebGL2Available() { try { @@ -28,9 +28,9 @@ const WebGL = { } - }, + } - isColorSpaceAvailable( colorSpace ) { + static isColorSpaceAvailable( colorSpace ) { try { @@ -45,21 +45,21 @@ const WebGL = { } - }, + } - getWebGLErrorMessage() { + static getWebGLErrorMessage() { return this.getErrorMessage( 1 ); - }, + } - getWebGL2ErrorMessage() { + static getWebGL2ErrorMessage() { return this.getErrorMessage( 2 ); - }, + } - getErrorMessage( version ) { + static getErrorMessage( version ) { const names = { 1: 'WebGL', @@ -103,6 +103,6 @@ const WebGL = { } -}; +} export default WebGL; diff --git a/examples/jsm/capabilities/WebGPU.js b/examples/jsm/capabilities/WebGPU.js index 01e685766c2c93..3587718e259ccb 100644 --- a/examples/jsm/capabilities/WebGPU.js +++ b/examples/jsm/capabilities/WebGPU.js @@ -18,15 +18,15 @@ if ( navigator.gpu !== undefined ) { } -const WebGPU = { +class WebGPU { - isAvailable() { + static isAvailable() { return isAvailable; - }, + } - getErrorMessage() { + static getErrorMessage() { const message = 'Your browser does not support WebGPU yet'; @@ -48,6 +48,6 @@ const WebGPU = { } -}; +} export default WebGPU; diff --git a/examples/jsm/lights/LightProbeGenerator.js b/examples/jsm/lights/LightProbeGenerator.js index 47a62d8cc1c821..4bd9896c8cd3fd 100644 --- a/examples/jsm/lights/LightProbeGenerator.js +++ b/examples/jsm/lights/LightProbeGenerator.js @@ -10,10 +10,10 @@ import { DataUtils } from 'three'; -const LightProbeGenerator = { +class LightProbeGenerator { // https://www.ppsloan.org/publications/StupidSH36.pdf - fromCubeTexture( cubeTexture ) { + static fromCubeTexture( cubeTexture ) { let totalWeight = 0; @@ -124,9 +124,9 @@ const LightProbeGenerator = { return new LightProbe( sh ); - }, + } - fromCubeRenderTarget( renderer, cubeRenderTarget ) { + static fromCubeRenderTarget( renderer, cubeRenderTarget ) { // The renderTarget must be set to RGBA in order to make readRenderTargetPixels works let totalWeight = 0; @@ -256,7 +256,7 @@ const LightProbeGenerator = { } -}; +} function convertColorToLinear( color, colorSpace ) { diff --git a/examples/jsm/lights/RectAreaLightUniformsLib.js b/examples/jsm/lights/RectAreaLightUniformsLib.js index 4daf7683f61417..cf916b31ab251e 100644 --- a/examples/jsm/lights/RectAreaLightUniformsLib.js +++ b/examples/jsm/lights/RectAreaLightUniformsLib.js @@ -28,9 +28,9 @@ import { // by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt // code: https://github.com/selfshadow/ltc_code/ -const RectAreaLightUniformsLib = { +class RectAreaLightUniformsLib { - init() { + static init() { // source: https://github.com/selfshadow/ltc_code/tree/master/fit/results/ltc.js @@ -74,6 +74,6 @@ const RectAreaLightUniformsLib = { } -}; +} export { RectAreaLightUniformsLib }; diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index 79037c13715af8..76a081b9ef821e 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -1916,11 +1916,7 @@ class SVGLoader extends Loader { } -} - -/* @__PURE__ */ Object.assign( SVGLoader, { - - createShapes( shapePath ) { + static createShapes( shapePath ) { // Param shapePath: a shapepath as returned by the parse function of this class // Returns Shape object @@ -2362,9 +2358,9 @@ class SVGLoader extends Loader { return shapesToReturn; - }, + } - getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { + static getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { // Param width: Stroke width // Param color: As returned by THREE.Color.getStyle() @@ -2387,9 +2383,9 @@ class SVGLoader extends Loader { strokeMiterLimit: miterLimit }; - }, + } - pointsToStroke( points, style, arcDivisions, minDistance ) { + static pointsToStroke( points, style, arcDivisions, minDistance ) { // Generates a stroke with some width around the given path. // The path can be open or closed (last point equals to first point) @@ -2416,9 +2412,9 @@ class SVGLoader extends Loader { return geometry; - }, + } - pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { + static pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { // This function can be called to update existing arrays or buffers. // Accepts same parameters as pointsToStroke, plus the buffers and optional offset. @@ -3170,6 +3166,7 @@ class SVGLoader extends Loader { } -} ); + +} export { SVGLoader }; diff --git a/examples/jsm/math/ColorConverter.js b/examples/jsm/math/ColorConverter.js index fdbb3a3f67b959..f45b3c7208ceb6 100644 --- a/examples/jsm/math/ColorConverter.js +++ b/examples/jsm/math/ColorConverter.js @@ -2,9 +2,9 @@ import { MathUtils } from 'three'; const _hsl = {}; -const ColorConverter = { +class ColorConverter { - setHSV( color, h, s, v ) { + static setHSV( color, h, s, v ) { // https://gist.github.com/xpansive/1337890#file-index-js @@ -14,9 +14,9 @@ const ColorConverter = { return color.setHSL( h, ( s * v ) / ( ( h = ( 2 - s ) * v ) < 1 ? h : ( 2 - h ) ), h * 0.5 ); - }, + } - getHSV( color, target ) { + static getHSV( color, target ) { color.getHSL( _hsl ); @@ -31,6 +31,6 @@ const ColorConverter = { } -}; +} export { ColorConverter }; diff --git a/examples/jsm/math/ConvexHull.js b/examples/jsm/math/ConvexHull.js index d57b9ecb917cdc..46acf2712e8692 100644 --- a/examples/jsm/math/ConvexHull.js +++ b/examples/jsm/math/ConvexHull.js @@ -921,6 +921,28 @@ class Face { } + static create( a, b, c ) { + + const face = new Face(); + + const e0 = new HalfEdge( a, face ); + const e1 = new HalfEdge( b, face ); + const e2 = new HalfEdge( c, face ); + + // join edges + + e0.next = e2.prev = e1; + e1.next = e0.prev = e2; + e2.next = e1.prev = e0; + + // main half edge reference + + face.edge = e0; + + return face.compute(); + + } + getEdge( i ) { let edge = this.edge; @@ -969,32 +991,6 @@ class Face { } -/* @__PURE__ */ Object.assign( Face, { - - create( a, b, c ) { - - const face = new Face(); - - const e0 = new HalfEdge( a, face ); - const e1 = new HalfEdge( b, face ); - const e2 = new HalfEdge( c, face ); - - // join edges - - e0.next = e2.prev = e1; - e1.next = e0.prev = e2; - e2.next = e1.prev = e0; - - // main half edge reference - - face.edge = e0; - - return face.compute(); - - } - -} ); - // Entity for a Doubly-Connected Edge List (DCEL). class HalfEdge { diff --git a/examples/jsm/misc/ConvexObjectBreaker.js b/examples/jsm/misc/ConvexObjectBreaker.js index dee14ee5165fc5..378559b24355a9 100644 --- a/examples/jsm/misc/ConvexObjectBreaker.js +++ b/examples/jsm/misc/ConvexObjectBreaker.js @@ -449,11 +449,7 @@ class ConvexObjectBreaker { } -} - -/* @__PURE__ */ Object.assign( ConvexObjectBreaker, { - - transformFreeVector( v, m ) { + static transformFreeVector( v, m ) { // input: // vector interpreted as a free vector @@ -468,9 +464,9 @@ class ConvexObjectBreaker { return v; - }, + } - transformFreeVectorInverse( v, m ) { + static transformFreeVectorInverse( v, m ) { // input: // vector interpreted as a free vector @@ -485,9 +481,9 @@ class ConvexObjectBreaker { return v; - }, + } - transformTiedVectorInverse( v, m ) { + static transformTiedVectorInverse( v, m ) { // input: // vector interpreted as a tied (ordinary) vector @@ -502,9 +498,9 @@ class ConvexObjectBreaker { return v; - }, + } - transformPlaneToLocalSpace( plane, m, resultPlane ) { + static transformPlaneToLocalSpace( plane, m, resultPlane ) { resultPlane.normal.copy( plane.normal ); resultPlane.constant = plane.constant; @@ -518,6 +514,6 @@ class ConvexObjectBreaker { } -} ); +} export { ConvexObjectBreaker }; diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index 5b4d9062b78a1b..d917dc3f108e1f 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -489,11 +489,7 @@ class NodeMaterial extends ShaderMaterial { } -} - -/* @__PURE__ */ Object.assign( NodeMaterial, { - - fromMaterial( material ) { + static fromMaterial( material ) { if ( material.isNodeMaterial === true ) { // is already a node material @@ -521,7 +517,7 @@ class NodeMaterial extends ShaderMaterial { } -} ); +} export default NodeMaterial; diff --git a/examples/jsm/utils/LDrawUtils.js b/examples/jsm/utils/LDrawUtils.js index cd940bc1e2d74f..64bb11c226ff4a 100644 --- a/examples/jsm/utils/LDrawUtils.js +++ b/examples/jsm/utils/LDrawUtils.js @@ -9,9 +9,9 @@ import { import { mergeGeometries } from './BufferGeometryUtils.js'; -const LDrawUtils = { +class LDrawUtils { - mergeObject( object ) { + static mergeObject( object ) { // Merges geometries in object by materials and returns new object. Use on not indexed geometries. // The object buffers reference the old object ones. @@ -197,6 +197,6 @@ const LDrawUtils = { } -}; +} export { LDrawUtils }; diff --git a/examples/jsm/webxr/ARButton.js b/examples/jsm/webxr/ARButton.js index bbb56c7254ee26..cc2b0173109074 100644 --- a/examples/jsm/webxr/ARButton.js +++ b/examples/jsm/webxr/ARButton.js @@ -1,6 +1,6 @@ -const ARButton = { +class ARButton { - createButton( renderer, sessionInit = {} ) { + static createButton( renderer, sessionInit = {} ) { const button = document.createElement( 'button' ); @@ -203,6 +203,6 @@ const ARButton = { } -}; +} export { ARButton }; diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index a3a604d43f36d5..496a91cadbaf46 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -1,6 +1,6 @@ -const VRButton = { +class VRButton { - createButton( renderer ) { + static createButton( renderer ) { const button = document.createElement( 'button' ); @@ -172,11 +172,11 @@ const VRButton = { } - }, + } - xrSessionIsGranted: false, + static registerSessionGrantedListener() { - registerSessionGrantedListener() { + VRButton.xrSessionIsGranted = false; if ( 'xr' in navigator ) { @@ -194,7 +194,7 @@ const VRButton = { } -}; +} /* @__PURE__ */ VRButton.registerSessionGrantedListener(); From 07ae8d16372f2f6df211c7def3ff27a32b0a5be8 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:12:07 -0500 Subject: [PATCH 26/45] MaterialXLoader: revert changes --- examples/jsm/loaders/MaterialXLoader.js | 146 ++++++++++++------------ 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/examples/jsm/loaders/MaterialXLoader.js b/examples/jsm/loaders/MaterialXLoader.js index 4885fd6c3a146d..a7a928c6199c12 100644 --- a/examples/jsm/loaders/MaterialXLoader.js +++ b/examples/jsm/loaders/MaterialXLoader.js @@ -42,89 +42,89 @@ class MtlXElement { const MtlXElements = [ // << Math >> - /* @__PURE__ */ new MtlXElement( 'add', add, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'subtract', sub, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'multiply', mul, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'divide', div, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'modulo', mod, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'absval', abs, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'sign', sign, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'floor', floor, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'ceil', ceil, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'round', round, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'power', pow, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'sin', sin, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'cos', cos, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'tan', tan, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'asin', asin, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'acos', acos, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'atan2', atan2, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'sqrt', sqrt, [ 'in' ] ), - // /* @__PURE__ */ new MtlXElement( 'ln', ... ), - /* @__PURE__ */ new MtlXElement( 'exp', exp, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ), - /* @__PURE__ */ new MtlXElement( 'min', min, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'max', max, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'normalize', normalize, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'magnitude', length, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'crossproduct', cross, [ 'in' ] ), - // /* @__PURE__ */ new MtlXElement( 'transformpoint', ... ), - // /* @__PURE__ */ new MtlXElement( 'transformvector', ... ), - // /* @__PURE__ */ new MtlXElement( 'transformnormal', ... ), - // /* @__PURE__ */ new MtlXElement( 'transformmatrix', ... ), - /* @__PURE__ */ new MtlXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ), - // /* @__PURE__ */ new MtlXElement( 'transpose', ... ), - // /* @__PURE__ */ new MtlXElement( 'determinant', ... ), - // /* @__PURE__ */ new MtlXElement( 'invertmatrix', ... ), - // /* @__PURE__ */ new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ), - // /* @__PURE__ */ new MtlXElement( 'rotate3d', ... ), - // /* @__PURE__ */ new MtlXElement( 'arrayappend', ... ), - // /* @__PURE__ */ new MtlXElement( 'dot', ... ), + new MtlXElement( 'add', add, [ 'in1', 'in2' ] ), + new MtlXElement( 'subtract', sub, [ 'in1', 'in2' ] ), + new MtlXElement( 'multiply', mul, [ 'in1', 'in2' ] ), + new MtlXElement( 'divide', div, [ 'in1', 'in2' ] ), + new MtlXElement( 'modulo', mod, [ 'in1', 'in2' ] ), + new MtlXElement( 'absval', abs, [ 'in1', 'in2' ] ), + new MtlXElement( 'sign', sign, [ 'in1', 'in2' ] ), + new MtlXElement( 'floor', floor, [ 'in1', 'in2' ] ), + new MtlXElement( 'ceil', ceil, [ 'in1', 'in2' ] ), + new MtlXElement( 'round', round, [ 'in1', 'in2' ] ), + new MtlXElement( 'power', pow, [ 'in1', 'in2' ] ), + new MtlXElement( 'sin', sin, [ 'in' ] ), + new MtlXElement( 'cos', cos, [ 'in' ] ), + new MtlXElement( 'tan', tan, [ 'in' ] ), + new MtlXElement( 'asin', asin, [ 'in' ] ), + new MtlXElement( 'acos', acos, [ 'in' ] ), + new MtlXElement( 'atan2', atan2, [ 'in1', 'in2' ] ), + new MtlXElement( 'sqrt', sqrt, [ 'in' ] ), + //new MtlXElement( 'ln', ... ), + new MtlXElement( 'exp', exp, [ 'in' ] ), + new MtlXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ), + new MtlXElement( 'min', min, [ 'in1', 'in2' ] ), + new MtlXElement( 'max', max, [ 'in1', 'in2' ] ), + new MtlXElement( 'normalize', normalize, [ 'in' ] ), + new MtlXElement( 'magnitude', length, [ 'in1', 'in2' ] ), + new MtlXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ), + new MtlXElement( 'crossproduct', cross, [ 'in' ] ), + //new MtlXElement( 'transformpoint', ... ), + //new MtlXElement( 'transformvector', ... ), + //new MtlXElement( 'transformnormal', ... ), + //new MtlXElement( 'transformmatrix', ... ), + new MtlXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ), + //new MtlXElement( 'transpose', ... ), + //new MtlXElement( 'determinant', ... ), + //new MtlXElement( 'invertmatrix', ... ), + //new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ), + //new MtlXElement( 'rotate3d', ... ), + //new MtlXElement( 'arrayappend', ... ), + //new MtlXElement( 'dot', ... ), // << Adjustment >> - /* @__PURE__ */ new MtlXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ), - /* @__PURE__ */ new MtlXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ), - // /* @__PURE__ */ new MtlXElement( 'curveadjust', ... ), - // /* @__PURE__ */ new MtlXElement( 'curvelookup', ... ), - /* @__PURE__ */ new MtlXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ), - /* @__PURE__ */ new MtlXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ), - /* @__PURE__ */ new MtlXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ), + new MtlXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ), + new MtlXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ), + //new MtlXElement( 'curveadjust', ... ), + //new MtlXElement( 'curvelookup', ... ), + new MtlXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ), + new MtlXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ), + new MtlXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ), // << Mix >> - /* @__PURE__ */ new MtlXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ), + new MtlXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ), // << Channel >> - /* @__PURE__ */ new MtlXElement( 'combine2', vec2, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ), - /* @__PURE__ */ new MtlXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ), + new MtlXElement( 'combine2', vec2, [ 'in1', 'in2' ] ), + new MtlXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ), + new MtlXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ), // << Procedural >> - /* @__PURE__ */ new MtlXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ), - /* @__PURE__ */ new MtlXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ), - /* @__PURE__ */ new MtlXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ), - /* @__PURE__ */ new MtlXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ), - /* @__PURE__ */ new MtlXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), - /* @__PURE__ */ new MtlXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), - /* @__PURE__ */ new MtlXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ), - /* @__PURE__ */ new MtlXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ), - /* @__PURE__ */ new MtlXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ), - /* @__PURE__ */ new MtlXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), - /* @__PURE__ */ new MtlXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), + new MtlXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ), + new MtlXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ), + new MtlXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ), + new MtlXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ), + new MtlXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), + new MtlXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), + new MtlXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ), + new MtlXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ), + new MtlXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ), + new MtlXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), + new MtlXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), // << Supplemental >> - // /* @__PURE__ */ new MtlXElement( 'tiledimage', ... ), - // /* @__PURE__ */ new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ), - // /* @__PURE__ */ new MtlXElement( 'ramp4', ... ), - // /* @__PURE__ */ new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ), - /* @__PURE__ */ new MtlXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ), - /* @__PURE__ */ new MtlXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ), - // /* @__PURE__ */ new MtlXElement( 'hsvadjust', ... ), - /* @__PURE__ */ new MtlXElement( 'saturate', saturation, [ 'in', 'amount' ] ), - // /* @__PURE__ */ new MtlXElement( 'extract', ... ), - // /* @__PURE__ */ new MtlXElement( 'separate2', ... ), - // /* @__PURE__ */ new MtlXElement( 'separate3', ... ), - // /* @__PURE__ */ new MtlXElement( 'separate4', ... ) + //new MtlXElement( 'tiledimage', ... ), + //new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ), + //new MtlXElement( 'ramp4', ... ), + //new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ), + new MtlXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ), + new MtlXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ), + //new MtlXElement( 'hsvadjust', ... ), + new MtlXElement( 'saturate', saturation, [ 'in', 'amount' ] ), + //new MtlXElement( 'extract', ... ), + //new MtlXElement( 'separate2', ... ), + //new MtlXElement( 'separate3', ... ), + //new MtlXElement( 'separate4', ... ) ]; From f4383e05e60015448398eb34f22195d9c347689d Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:14:33 -0500 Subject: [PATCH 27/45] XRButton: cleanup --- examples/jsm/webxr/XRButton.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jsm/webxr/XRButton.js b/examples/jsm/webxr/XRButton.js index a2ce66fab64153..d48f78b5fe74bc 100644 --- a/examples/jsm/webxr/XRButton.js +++ b/examples/jsm/webxr/XRButton.js @@ -1,4 +1,4 @@ -const XRButton = { +class XRButton { createButton( renderer ) { @@ -193,6 +193,6 @@ const XRButton = { } -}; +} export { XRButton }; From 82db00e3973faf535ef2b40955c494068cffdaf6 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:22:27 -0500 Subject: [PATCH 28/45] XRButton: fix conflicts --- examples/jsm/webxr/XRButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/webxr/XRButton.js b/examples/jsm/webxr/XRButton.js index d48f78b5fe74bc..f33cbbea25111b 100644 --- a/examples/jsm/webxr/XRButton.js +++ b/examples/jsm/webxr/XRButton.js @@ -1,6 +1,6 @@ class XRButton { - createButton( renderer ) { + static createButton( renderer ) { const button = document.createElement( 'button' ); From 321ba48d3c1ac2a510950d2686e70add7403738d Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 07:20:33 -0500 Subject: [PATCH 29/45] fflate: cleanup --- examples/jsm/libs/fflate.module.js | 4582 ++++++++++++++-------------- 1 file changed, 2291 insertions(+), 2291 deletions(-) diff --git a/examples/jsm/libs/fflate.module.js b/examples/jsm/libs/fflate.module.js index 6235f8f489b532..8710fb41ded792 100644 --- a/examples/jsm/libs/fflate.module.js +++ b/examples/jsm/libs/fflate.module.js @@ -64,2510 +64,2510 @@ const { } = /* @__PURE__ */ ( () => { - var ch2 = {}; - var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; - var cwk = function (u) { return new Worker(u); }; - try { - URL.revokeObjectURL(durl('')); - } - catch (e) { - // We're in Deno or a very old browser - durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; - // If Deno, this is necessary; if not, this changes nothing - cwk = function (u) { return new Worker(u, { type: 'module' }); }; - } - var wk = (function (c, id, msg, transfer, cb) { - var w = cwk(ch2[id] || (ch2[id] = durl(c))); - w.onerror = function (e) { return cb(e.error, null); }; - w.onmessage = function (e) { return cb(null, e.data); }; - w.postMessage(msg, transfer); - return w; - }); +var ch2 = {}; +var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; +var cwk = function (u) { return new Worker(u); }; +try { + URL.revokeObjectURL(durl('')); +} +catch (e) { + // We're in Deno or a very old browser + durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; + // If Deno, this is necessary; if not, this changes nothing + cwk = function (u) { return new Worker(u, { type: 'module' }); }; +} +var wk = (function (c, id, msg, transfer, cb) { + var w = cwk(ch2[id] || (ch2[id] = durl(c))); + w.onerror = function (e) { return cb(e.error, null); }; + w.onmessage = function (e) { return cb(null, e.data); }; + w.postMessage(msg, transfer); + return w; +}); - // aliases for shorter compressed code (most minifers don't do this) - var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; - // fixed length extra bits - var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); - // fixed distance extra bits - // see fleb note - var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); - // code length index map - var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); - // get base, reverse index map from extra bits - var freb = function (eb, start) { - var b = new u16(31); - for (var i = 0; i < 31; ++i) { - b[i] = start += 1 << eb[i - 1]; - } - // numbers here are at max 18 bits - var r = new u32(b[30]); - for (var i = 1; i < 30; ++i) { - for (var j = b[i]; j < b[i + 1]; ++j) { - r[j] = ((j - b[i]) << 5) | i; - } - } - return [b, r]; - }; - var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; - // we can ignore the fact that the other numbers are wrong; they never happen anyway - fl[28] = 258, revfl[258] = 28; - var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; - // map of value to reverse (assuming 16 bits) - var rev = new u16(32768); - for (var i = 0; i < 32768; ++i) { - // reverse table algorithm from SO - var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); - x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); - x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); - rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; - } - // create huffman tree from u8 "map": index -> code length for code index - // mb (max bits) must be at most 15 - // TODO: optimize/split up? - var hMap = (function (cd, mb, r) { - var s = cd.length; - // index - var i = 0; - // u16 "map": index -> # of codes with bit length = index - var l = new u16(mb); - // length of cd must be 288 (total # of codes) - for (; i < s; ++i) - ++l[cd[i] - 1]; - // u16 "map": index -> minimum code for bit length = index - var le = new u16(mb); - for (i = 0; i < mb; ++i) { - le[i] = (le[i - 1] + l[i - 1]) << 1; +// aliases for shorter compressed code (most minifers don't do this) +var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; +// fixed length extra bits +var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); +// fixed distance extra bits +// see fleb note +var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); +// code length index map +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +// get base, reverse index map from extra bits +var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; + } + // numbers here are at max 18 bits + var r = new u32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; } - var co; - if (r) { - // u16 "map": index -> number of actual bits, symbol for code - co = new u16(1 << mb); - // bits to remove for reverser - var rvb = 15 - mb; - for (i = 0; i < s; ++i) { - // ignore 0 lengths - if (cd[i]) { - // num encoding both symbol and bits read - var sv = (i << 4) | cd[i]; - // free bits - var r_1 = mb - cd[i]; - // start value - var v = le[cd[i] - 1]++ << r_1; - // m is end value - for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { - // every 16 bit value starting with the code yields the same result - co[rev[v] >>> rvb] = sv; - } + } + return [b, r]; +}; +var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; +// we can ignore the fact that the other numbers are wrong; they never happen anyway +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; +// map of value to reverse (assuming 16 bits) +var rev = new u16(32768); +for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; +} +// create huffman tree from u8 "map": index -> code length for code index +// mb (max bits) must be at most 15 +// TODO: optimize/split up? +var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) + ++l[cd[i] - 1]; + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 0; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + if (r) { + // u16 "map": index -> number of actual bits, symbol for code + co = new u16(1 << mb); + // bits to remove for reverser + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + // ignore 0 lengths + if (cd[i]) { + // num encoding both symbol and bits read + var sv = (i << 4) | cd[i]; + // free bits + var r_1 = mb - cd[i]; + // start value + var v = le[cd[i] - 1]++ << r_1; + // m is end value + for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { + // every 16 bit value starting with the code yields the same result + co[rev[v] >>> rvb] = sv; } } } - else { - co = new u16(s); - for (i = 0; i < s; ++i) { - if (cd[i]) { - co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); - } + } + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); } } - return co; - }); - // fixed length tree - var flt = new u8(288); - for (var i = 0; i < 144; ++i) - flt[i] = 8; - for (var i = 144; i < 256; ++i) - flt[i] = 9; - for (var i = 256; i < 280; ++i) - flt[i] = 7; - for (var i = 280; i < 288; ++i) - flt[i] = 8; - // fixed distance tree - var fdt = new u8(32); - for (var i = 0; i < 32; ++i) - fdt[i] = 5; - // fixed length map - var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); - // fixed distance map - var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); - // find max of array - var max = function (a) { - var m = a[0]; - for (var i = 1; i < a.length; ++i) { - if (a[i] > m) - m = a[i]; + } + return co; +}); +// fixed length tree +var flt = new u8(288); +for (var i = 0; i < 144; ++i) + flt[i] = 8; +for (var i = 144; i < 256; ++i) + flt[i] = 9; +for (var i = 256; i < 280; ++i) + flt[i] = 7; +for (var i = 280; i < 288; ++i) + flt[i] = 8; +// fixed distance tree +var fdt = new u8(32); +for (var i = 0; i < 32; ++i) + fdt[i] = 5; +// fixed length map +var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); +// fixed distance map +var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); +// find max of array +var max = function (a) { + var m = a[0]; + for (var i = 1; i < a.length; ++i) { + if (a[i] > m) + m = a[i]; + } + return m; +}; +// read d, starting at bit p and mask with m +var bits = function (d, p, m) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; +}; +// read d, starting at bit p continuing for at least 16 bits +var bits16 = function (d, p) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); +}; +// get end of byte +var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; +// typed array slice - allows garbage collector to free original reference, +// while being more compatible than .slice +var slc = function (v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); + n.set(v.subarray(s, e)); + return n; +}; +// expands raw DEFLATE data +var inflt = function (dat, buf, st) { + // source length + var sl = dat.length; + if (!sl || (st && !st.l && sl < 5)) + return buf || new u8(0); + // have to estimate size + var noBuf = !buf || st; + // no state + var noSt = !st || st.i; + if (!st) + st = {}; + // Assumes roughly 33% compression ratio average + if (!buf) + buf = new u8(sl * 3); + // ensure buffer can fit at least l elements + var cbuf = function (l) { + var bl = buf.length; + // need to increase size to fit + if (l > bl) { + // Double or set to necessary, whichever is greater + var nbuf = new u8(Math.max(bl * 2, l)); + nbuf.set(buf); + buf = nbuf; } - return m; - }; - // read d, starting at bit p and mask with m - var bits = function (d, p, m) { - var o = (p / 8) | 0; - return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; - }; - // read d, starting at bit p continuing for at least 16 bits - var bits16 = function (d, p) { - var o = (p / 8) | 0; - return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); }; - // get end of byte - var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; - // typed array slice - allows garbage collector to free original reference, - // while being more compatible than .slice - var slc = function (v, s, e) { - if (s == null || s < 0) - s = 0; - if (e == null || e > v.length) - e = v.length; - // can't use .constructor in case user-supplied - var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); - n.set(v.subarray(s, e)); - return n; - }; - // expands raw DEFLATE data - var inflt = function (dat, buf, st) { - // source length - var sl = dat.length; - if (!sl || (st && !st.l && sl < 5)) - return buf || new u8(0); - // have to estimate size - var noBuf = !buf || st; - // no state - var noSt = !st || st.i; - if (!st) - st = {}; - // Assumes roughly 33% compression ratio average - if (!buf) - buf = new u8(sl * 3); - // ensure buffer can fit at least l elements - var cbuf = function (l) { - var bl = buf.length; - // need to increase size to fit - if (l > bl) { - // Double or set to necessary, whichever is greater - var nbuf = new u8(Math.max(bl * 2, l)); - nbuf.set(buf); - buf = nbuf; + // last chunk bitpos bytes + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + // total bits + var tbts = sl * 8; + do { + if (!lm) { + // BFINAL - this is only 1 when last chunk is next + st.f = final = bits(dat, pos, 1); + // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + // go to end of byte boundary + var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; + if (t > sl) { + if (noSt) + throw 'unexpected EOF'; + break; + } + // ensure size + if (noBuf) + cbuf(bt + l); + // Copy over uncompressed data + buf.set(dat.subarray(s, t), bt); + // Get new bitpos, update byte count + st.b = bt += l, st.p = pos = t * 8; + continue; } - }; - // last chunk bitpos bytes - var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; - // total bits - var tbts = sl * 8; - do { - if (!lm) { - // BFINAL - this is only 1 when last chunk is next - st.f = final = bits(dat, pos, 1); - // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman - var type = bits(dat, pos + 1, 3); - pos += 3; - if (!type) { - // go to end of byte boundary - var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; - if (t > sl) { - if (noSt) - throw 'unexpected EOF'; - break; - } - // ensure size - if (noBuf) - cbuf(bt + l); - // Copy over uncompressed data - buf.set(dat.subarray(s, t), bt); - // Get new bitpos, update byte count - st.b = bt += l, st.p = pos = t * 8; - continue; + else if (type == 1) + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + else if (type == 2) { + // literal lengths + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + // length+distance tree + var ldt = new u8(tl); + // code length tree + var clt = new u8(19); + for (var i = 0; i < hcLen; ++i) { + // use index map to get real code + clt[clim[i]] = bits(dat, pos + i * 3, 7); } - else if (type == 1) - lm = flrm, dm = fdrm, lbt = 9, dbt = 5; - else if (type == 2) { - // literal lengths - var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; - var tl = hLit + bits(dat, pos + 5, 31) + 1; - pos += 14; - // length+distance tree - var ldt = new u8(tl); - // code length tree - var clt = new u8(19); - for (var i = 0; i < hcLen; ++i) { - // use index map to get real code - clt[clim[i]] = bits(dat, pos + i * 3, 7); + pos += hcLen * 3; + // code lengths bits + var clb = max(clt), clbmsk = (1 << clb) - 1; + // code lengths map + var clm = hMap(clt, clb, 1); + for (var i = 0; i < tl;) { + var r = clm[bits(dat, pos, clbmsk)]; + // bits read + pos += r & 15; + // symbol + var s = r >>> 4; + // code length to copy + if (s < 16) { + ldt[i++] = s; } - pos += hcLen * 3; - // code lengths bits - var clb = max(clt), clbmsk = (1 << clb) - 1; - // code lengths map - var clm = hMap(clt, clb, 1); - for (var i = 0; i < tl;) { - var r = clm[bits(dat, pos, clbmsk)]; - // bits read - pos += r & 15; - // symbol - var s = r >>> 4; - // code length to copy - if (s < 16) { - ldt[i++] = s; - } - else { - // copy count - var c = 0, n = 0; - if (s == 16) - n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; - else if (s == 17) - n = 3 + bits(dat, pos, 7), pos += 3; - else if (s == 18) - n = 11 + bits(dat, pos, 127), pos += 7; - while (n--) - ldt[i++] = c; - } + else { + // copy count + var c = 0, n = 0; + if (s == 16) + n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; + else if (s == 17) + n = 3 + bits(dat, pos, 7), pos += 3; + else if (s == 18) + n = 11 + bits(dat, pos, 127), pos += 7; + while (n--) + ldt[i++] = c; } - // length tree distance tree - var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); - // max length bits - lbt = max(lt); - // max dist bits - dbt = max(dt); - lm = hMap(lt, lbt, 1); - dm = hMap(dt, dbt, 1); - } - else - throw 'invalid block type'; - if (pos > tbts) { - if (noSt) - throw 'unexpected EOF'; - break; } + // length tree distance tree + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + // max length bits + lbt = max(lt); + // max dist bits + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); + } + else + throw 'invalid block type'; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + } + // Make sure the buffer can hold this + the largest possible addition + // Maximum chunk size (practically, theoretically infinite) is 2^17; + if (noBuf) + cbuf(bt + 131072); + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (;; lpos = pos) { + // bits read, code + var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; + pos += c & 15; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; } - // Make sure the buffer can hold this + the largest possible addition - // Maximum chunk size (practically, theoretically infinite) is 2^17; - if (noBuf) - cbuf(bt + 131072); - var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; - var lpos = pos; - for (;; lpos = pos) { - // bits read, code - var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; - pos += c & 15; + if (!c) + throw 'invalid length/literal'; + if (sym < 256) + buf[bt++] = sym; + else if (sym == 256) { + lpos = pos, lm = null; + break; + } + else { + var add = sym - 254; + // no extra bits needed if less + if (sym > 264) { + // index + var i = sym - 257, b = fleb[i]; + add = bits(dat, pos, (1 << b) - 1) + fl[i]; + pos += b; + } + // dist + var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; + if (!d) + throw 'invalid distance'; + pos += d & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; + } if (pos > tbts) { if (noSt) throw 'unexpected EOF'; break; } - if (!c) - throw 'invalid length/literal'; - if (sym < 256) - buf[bt++] = sym; - else if (sym == 256) { - lpos = pos, lm = null; - break; - } - else { - var add = sym - 254; - // no extra bits needed if less - if (sym > 264) { - // index - var i = sym - 257, b = fleb[i]; - add = bits(dat, pos, (1 << b) - 1) + fl[i]; - pos += b; - } - // dist - var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; - if (!d) - throw 'invalid distance'; - pos += d & 15; - var dt = fd[dsym]; - if (dsym > 3) { - var b = fdeb[dsym]; - dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; - } - if (pos > tbts) { - if (noSt) - throw 'unexpected EOF'; - break; - } - if (noBuf) - cbuf(bt + 131072); - var end = bt + add; - for (; bt < end; bt += 4) { - buf[bt] = buf[bt - dt]; - buf[bt + 1] = buf[bt + 1 - dt]; - buf[bt + 2] = buf[bt + 2 - dt]; - buf[bt + 3] = buf[bt + 3 - dt]; - } - bt = end; + if (noBuf) + cbuf(bt + 131072); + var end = bt + add; + for (; bt < end; bt += 4) { + buf[bt] = buf[bt - dt]; + buf[bt + 1] = buf[bt + 1 - dt]; + buf[bt + 2] = buf[bt + 2 - dt]; + buf[bt + 3] = buf[bt + 3 - dt]; } + bt = end; } - st.l = lm, st.p = lpos, st.b = bt; - if (lm) - final = 1, st.m = lbt, st.d = dm, st.n = dbt; - } while (!final); - return bt == buf.length ? buf : slc(buf, 0, bt); - }; - // starting at p, write the minimum number of bits that can hold v to d - var wbits = function (d, p, v) { - v <<= p & 7; - var o = (p / 8) | 0; - d[o] |= v; - d[o + 1] |= v >>> 8; - }; - // starting at p, write the minimum number of bits (>8) that can hold v to d - var wbits16 = function (d, p, v) { - v <<= p & 7; - var o = (p / 8) | 0; - d[o] |= v; - d[o + 1] |= v >>> 8; - d[o + 2] |= v >>> 16; - }; - // creates code lengths from a frequency table - var hTree = function (d, mb) { - // Need extra info to make a tree - var t = []; - for (var i = 0; i < d.length; ++i) { - if (d[i]) - t.push({ s: i, f: d[i] }); } - var s = t.length; - var t2 = t.slice(); - if (!s) - return [et, 0]; - if (s == 1) { - var v = new u8(t[0].s + 1); - v[t[0].s] = 1; - return [v, 1]; + st.l = lm, st.p = lpos, st.b = bt; + if (lm) + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } while (!final); + return bt == buf.length ? buf : slc(buf, 0, bt); +}; +// starting at p, write the minimum number of bits that can hold v to d +var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; +}; +// starting at p, write the minimum number of bits (>8) that can hold v to d +var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; + d[o + 2] |= v >>> 16; +}; +// creates code lengths from a frequency table +var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); + } + var s = t.length; + var t2 = t.slice(); + if (!s) + return [et, 0]; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return [v, 1]; + } + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; + } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; + } + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; + } + else + break; } - t.sort(function (a, b) { return a.f - b.f; }); - // after i2 reaches last ind, will be stopped - // freq must be greater than largest possible number of symbols - t.push({ s: -1, f: 25001 }); - var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; - t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; - // efficient algorithm from UZIP.js - // i0 is lookbehind, i2 is lookahead - after processing two low-freq - // symbols that combined have high freq, will start processing i2 (high-freq, - // non-composite) symbols instead - // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ - while (i1 != s - 1) { - l = t[t[i0].f < t[i2].f ? i0++ : i2++]; - r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; - t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; + dt >>>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; } - var maxSym = t2[0].s; - for (var i = 1; i < s; ++i) { - if (t2[i].s > maxSym) - maxSym = t2[i].s; + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } } - // code lengths - var tr = new u16(maxSym + 1); - // max bits in tree - var mbt = ln(t[i1 - 1], tr, 0); - if (mbt > mb) { - // more algorithms from UZIP.js - // TODO: find out how this code works (debt) - // ind debt - var i = 0, dt = 0; - // left cost - var lft = mbt - mb, cst = 1 << lft; - t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); - for (; i < s; ++i) { - var i2_1 = t2[i].s; - if (tr[i2_1] > mb) { - dt += cst - (1 << (mbt - tr[i2_1])); - tr[i2_1] = mb; + mbt = mb; + } + return [new u8(tr), mbt]; +}; +// get the max length and assign length codes +var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); +}; +// length codes generation +var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; } - else - break; - } - dt >>>= lft; - while (dt > 0) { - var i2_2 = t2[i].s; - if (tr[i2_2] < mb) - dt -= 1 << (mb - tr[i2_2]++ - 1); - else - ++i; } - for (; i >= 0 && dt; --i) { - var i2_3 = t2[i].s; - if (tr[i2_3] == mb) { - --tr[i2_3]; - ++dt; - } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; } - mbt = mb; + while (cls--) + w(cln); + cls = 1; + cln = c[i]; } - return [new u8(tr), mbt]; - }; - // get the max length and assign length codes - var ln = function (n, l, d) { - return n.s == -1 - ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) - : (l[n.s] = d); - }; - // length codes generation - var lc = function (c) { - var s = c.length; - // Note that the semicolon was intentional - while (s && !c[--s]) - ; - var cl = new u16(++s); - // ind num streak - var cli = 0, cln = c[0], cls = 1; - var w = function (v) { cl[cli++] = v; }; - for (var i = 1; i <= s; ++i) { - if (c[i] == cln && i != s) - ++cls; - else { - if (!cln && cls > 2) { - for (; cls > 138; cls -= 138) - w(32754); - if (cls > 2) { - w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); - cls = 0; - } - } - else if (cls > 3) { - w(cln), --cls; - for (; cls > 6; cls -= 6) - w(8304); - if (cls > 2) - w(((cls - 3) << 5) | 8208), cls = 0; - } - while (cls--) - w(cln); - cls = 1; - cln = c[i]; + } + return [cl.subarray(0, cli), s]; +}; +// calculate the length of output from tree, code lengths +var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; +}; +// writes a fixed block +// returns the new bit pos +var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >>> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; +}; +// writes a block +var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; + var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; + var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; + var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + lcfreq[lclt[i] & 31]++; + for (var i = 0; i < lcdt.length; ++i) + lcfreq[lcdt[i] & 31]++; + var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); + if (flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; + var llm = hMap(lct, mlcb, 0); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; } } - return [cl.subarray(0, cli), s]; - }; - // calculate the length of output from tree, code lengths - var clen = function (cf, cl) { - var l = 0; - for (var i = 0; i < cl.length; ++i) - l += cf[i] * cl[i]; - return l; - }; - // writes a fixed block - // returns the new bit pos - var wfblk = function (out, pos, dat) { - // no need to write 00 as type: TypedArray defaults to 0 - var s = dat.length; - var o = shft(pos + 2); - out[o] = s & 255; - out[o + 1] = s >>> 8; - out[o + 2] = out[o] ^ 255; - out[o + 3] = out[o + 1] ^ 255; - for (var i = 0; i < s; ++i) - out[o + i + 4] = dat[i]; - return (o + 4 + s) * 8; - }; - // writes a block - var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { - wbits(out, p++, final); - ++lf[256]; - var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; - var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; - var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; - var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; - var lcfreq = new u16(19); - for (var i = 0; i < lclt.length; ++i) - lcfreq[lclt[i] & 31]++; - for (var i = 0; i < lcdt.length; ++i) - lcfreq[lcdt[i] & 31]++; - var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; - var nlcc = 19; - for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) - ; - var flen = (bl + 5) << 3; - var ftlen = clen(lf, flt) + clen(df, fdt) + eb; - var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); - if (flen <= ftlen && flen <= dtlen) - return wfblk(out, p, dat.subarray(bs, bs + bl)); - var lm, ll, dm, dl; - wbits(out, p, 1 + (dtlen < ftlen)), p += 2; - if (dtlen < ftlen) { - lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; - var llm = hMap(lct, mlcb, 0); - wbits(out, p, nlc - 257); - wbits(out, p + 5, ndc - 1); - wbits(out, p + 10, nlcc - 4); - p += 14; - for (var i = 0; i < nlcc; ++i) - wbits(out, p + 3 * i, lct[clim[i]]); - p += 3 * nlcc; - var lcts = [lclt, lcdt]; - for (var it = 0; it < 2; ++it) { - var clct = lcts[it]; - for (var i = 0; i < clct.length; ++i) { - var len = clct[i] & 31; - wbits(out, p, llm[len]), p += lct[len]; - if (len > 15) - wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; - } - } + } + else { + lm = flm, ll = flt, dm = fdm, dl = fdt; + } + for (var i = 0; i < li; ++i) { + if (syms[i] > 255) { + var len = (syms[i] >>> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; + var dst = syms[i] & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; } else { - lm = flm, ll = flt, dm = fdm, dl = fdt; + wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; } - for (var i = 0; i < li; ++i) { - if (syms[i] > 255) { - var len = (syms[i] >>> 18) & 31; - wbits16(out, p, lm[len + 257]), p += ll[len + 257]; - if (len > 7) - wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; - var dst = syms[i] & 31; - wbits16(out, p, dm[dst]), p += dl[dst]; - if (dst > 3) - wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; + } + wbits16(out, p, lm[256]); + return p + ll[256]; +}; +// deflate options (nice << 13) | chain +var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +// empty +var et = /*#__PURE__*/ new u8(0); +// compresses data into a raw DEFLATE buffer +var dflt = function (dat, lvl, plvl, pre, post, lst) { + var s = dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var pos = 0; + if (!lvl || s < 8) { + for (var i = 0; i <= s; i += 65535) { + // end + var e = i + 65535; + if (e < s) { + // write full block + pos = wfblk(w, pos, dat.subarray(i, e)); } else { - wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; + // write final block + w[i] = lst; + pos = wfblk(w, pos, dat.subarray(i, s)); } } - wbits16(out, p, lm[256]); - return p + ll[256]; - }; - // deflate options (nice << 13) | chain - var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); - // empty - var et = /*#__PURE__*/ new u8(0); - // compresses data into a raw DEFLATE buffer - var dflt = function (dat, lvl, plvl, pre, post, lst) { - var s = dat.length; - var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); - // writing to this writes to the output buffer - var w = o.subarray(pre, o.length - post); - var pos = 0; - if (!lvl || s < 8) { - for (var i = 0; i <= s; i += 65535) { - // end - var e = i + 65535; - if (e < s) { - // write full block - pos = wfblk(w, pos, dat.subarray(i, e)); - } - else { - // write final block - w[i] = lst; - pos = wfblk(w, pos, dat.subarray(i, s)); + } + else { + var opt = deo[lvl - 1]; + var n = opt >>> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = new u16(32768), head = new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new u32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx bitpos + var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; + for (; i < s; ++i) { + // hash value + // deopt when i > s - 3 - at end, deopt acceptable + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && rem > 423) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; } - } - } - else { - var opt = deo[lvl - 1]; - var n = opt >>> 13, c = opt & 8191; - var msk_1 = (1 << plvl) - 1; - // prev 2-byte val map curr 2-byte val map - var prev = new u16(32768), head = new u16(msk_1 + 1); - var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; - var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; - // 24576 is an arbitrary number of maximum symbols per block - // 424 buffer for last block - var syms = new u32(25000); - // length/literal freq distance freq - var lf = new u16(288), df = new u16(32); - // l/lcnt exbits index l/lind waitdx bitpos - var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; - for (; i < s; ++i) { - // hash value - // deopt when i > s - 3 - at end, deopt acceptable - var hv = hsh(i); - // index mod 32768 previous index mod - var imod = i & 32767, pimod = head[hv]; - prev[imod] = pimod; - head[hv] = imod; - // We always should modify head and prev, but only add symbols if - // this data is not yet processed ("wait" for wait index) - if (wi <= i) { - // bytes remaining - var rem = s - i; - if ((lc_1 > 7000 || li > 24576) && rem > 423) { - pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); - li = lc_1 = eb = 0, bs = i; - for (var j = 0; j < 286; ++j) - lf[j] = 0; - for (var j = 0; j < 30; ++j) - df[j] = 0; - } - // len dist chain - var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; - if (rem > 2 && hv == hsh(i - dif)) { - var maxn = Math.min(n, rem) - 1; - var maxd = Math.min(32767, i); - // max possible length - // not capped at dif because decompressors implement "rolling" index population - var ml = Math.min(258, rem); - while (dif <= maxd && --ch_1 && imod != pimod) { - if (dat[i + l] == dat[i + l - dif]) { - var nl = 0; - for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) - ; - if (nl > l) { - l = nl, d = dif; - // break out early when we reach "nice" (we are satisfied enough) - if (nl > maxn) - break; - // now, find the rarest 2-byte sequence within this - // length of literals and search for that instead. - // Much faster than just using the start - var mmd = Math.min(dif, nl - 2); - var md = 0; - for (var j = 0; j < mmd; ++j) { - var ti = (i - dif + j + 32768) & 32767; - var pti = prev[ti]; - var cd = (ti - pti + 32768) & 32767; - if (cd > md) - md = cd, pimod = ti; - } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = (i - dif + j + 32768) & 32767; + var pti = prev[ti]; + var cd = (ti - pti + 32768) & 32767; + if (cd > md) + md = cd, pimod = ti; } } - // check the previous match - imod = pimod, pimod = prev[imod]; - dif += (imod - pimod + 32768) & 32767; } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += (imod - pimod + 32768) & 32767; } - // d will be nonzero only when a match was found - if (d) { - // store both dist and len data in one Uint32 - // Make sure this is recognized as a len/dist with 28th bit (2^28) - syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; - var lin = revfl[l] & 31, din = revfd[d] & 31; - eb += fleb[lin] + fdeb[din]; - ++lf[257 + lin]; - ++df[din]; - wi = i + l; - ++lc_1; - } - else { - syms[li++] = dat[i]; - ++lf[dat[i]]; - } + } + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one Uint32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; } } - pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); - // this is the easiest way to avoid needing to maintain state - if (!lst && pos & 7) - pos = wfblk(w, pos + 1, et); - } - return slc(o, 0, pre + shft(pos) + post); - }; - // CRC32 table - var crct = /*#__PURE__*/ (function () { - var t = new u32(256); - for (var i = 0; i < 256; ++i) { - var c = i, k = 9; - while (--k) - c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); - t[i] = c; } - return t; - })(); - // CRC32 - var crc = function () { - var c = -1; - return { - p: function (d) { - // closures have awful performance - var cr = c; - for (var i = 0; i < d.length; ++i) - cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); - c = cr; - }, - d: function () { return ~c; } - }; + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + // this is the easiest way to avoid needing to maintain state + if (!lst && pos & 7) + pos = wfblk(w, pos + 1, et); + } + return slc(o, 0, pre + shft(pos) + post); +}; +// CRC32 table +var crct = /*#__PURE__*/ (function () { + var t = new u32(256); + for (var i = 0; i < 256; ++i) { + var c = i, k = 9; + while (--k) + c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); + t[i] = c; + } + return t; +})(); +// CRC32 +var crc = function () { + var c = -1; + return { + p: function (d) { + // closures have awful performance + var cr = c; + for (var i = 0; i < d.length; ++i) + cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); + c = cr; + }, + d: function () { return ~c; } }; - // Alder32 - var adler = function () { - var a = 1, b = 0; - return { - p: function (d) { - // closures have awful performance - var n = a, m = b; - var l = d.length; - for (var i = 0; i != l;) { - var e = Math.min(i + 2655, l); - for (; i < e; ++i) - m += n += d[i]; - n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); - } - a = n, b = m; - }, - d: function () { - a %= 65521, b %= 65521; - return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); +}; +// Alder32 +var adler = function () { + var a = 1, b = 0; + return { + p: function (d) { + // closures have awful performance + var n = a, m = b; + var l = d.length; + for (var i = 0; i != l;) { + var e = Math.min(i + 2655, l); + for (; i < e; ++i) + m += n += d[i]; + n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); } - }; - }; - ; - // deflate with opts - var dopt = function (dat, opt, pre, post, st) { - return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); - }; - // Walmart object spread - var mrg = function (a, b) { - var o = {}; - for (var k in a) - o[k] = a[k]; - for (var k in b) - o[k] = b[k]; - return o; + a = n, b = m; + }, + d: function () { + a %= 65521, b %= 65521; + return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); + } }; - // worker clone - // This is possibly the craziest part of the entire codebase, despite how simple it may seem. - // The only parameter to this function is a closure that returns an array of variables outside of the function scope. - // We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization. - // We will return an object mapping of true variable name to value (basically, the current scope as a JS object). - // The reason we can't just use the original variable names is minifiers mangling the toplevel scope. - // This took me three weeks to figure out how to do. - var wcln = function (fn, fnStr, td) { - var dt = fn(); - var st = fn.toString(); - var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); - for (var i = 0; i < dt.length; ++i) { - var v = dt[i], k = ks[i]; - if (typeof v == 'function') { - fnStr += ';' + k + '='; - var st_1 = v.toString(); - if (v.prototype) { - // for global objects - if (st_1.indexOf('[native code]') != -1) { - var spInd = st_1.indexOf(' ', 8) + 1; - fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd)); - } - else { - fnStr += st_1; - for (var t in v.prototype) - fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString(); - } +}; +; +// deflate with opts +var dopt = function (dat, opt, pre, post, st) { + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); +}; +// Walmart object spread +var mrg = function (a, b) { + var o = {}; + for (var k in a) + o[k] = a[k]; + for (var k in b) + o[k] = b[k]; + return o; +}; +// worker clone +// This is possibly the craziest part of the entire codebase, despite how simple it may seem. +// The only parameter to this function is a closure that returns an array of variables outside of the function scope. +// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization. +// We will return an object mapping of true variable name to value (basically, the current scope as a JS object). +// The reason we can't just use the original variable names is minifiers mangling the toplevel scope. +// This took me three weeks to figure out how to do. +var wcln = function (fn, fnStr, td) { + var dt = fn(); + var st = fn.toString(); + var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); + for (var i = 0; i < dt.length; ++i) { + var v = dt[i], k = ks[i]; + if (typeof v == 'function') { + fnStr += ';' + k + '='; + var st_1 = v.toString(); + if (v.prototype) { + // for global objects + if (st_1.indexOf('[native code]') != -1) { + var spInd = st_1.indexOf(' ', 8) + 1; + fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd)); } - else + else { fnStr += st_1; + for (var t in v.prototype) + fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString(); + } } else - td[k] = v; - } - return [fnStr, td]; - }; - var ch = []; - // clone bufs - var cbfs = function (v) { - var tl = []; - for (var k in v) { - if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) - tl.push((v[k] = new v[k].constructor(v[k])).buffer); - } - return tl; - }; - // use a worker to execute code - var wrkr = function (fns, init, id, cb) { - var _a; - if (!ch[id]) { - var fnStr = '', td_1 = {}, m = fns.length - 1; - for (var i = 0; i < m; ++i) - _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; - ch[id] = wcln(fns[m], fnStr, td_1); + fnStr += st_1; } - var td = mrg({}, ch[id][1]); - return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); - }; - // base async inflate fn - var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; - var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; - // gzip extra - var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; - // gunzip extra - var guze = function () { return [gzs, gzl]; }; - // zlib extra - var zle = function () { return [zlh, wbytes, adler]; }; - // unzlib extra - var zule = function () { return [zlv]; }; - // post buf - var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; - // get u8 - var gu8 = function (o) { return o && o.size && new u8(o.size); }; - // async helper - var cbify = function (dat, opts, fns, init, id, cb) { - var w = wrkr(fns, init, id, function (err, dat) { - w.terminate(); - cb(err, dat); - }); - w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []); - return function () { w.terminate(); }; - }; - // auto stream - var astrm = function (strm) { - strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; - return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; - }; - // async stream attach - var astrmify = function (fns, strm, opts, init, id) { - var t; - var w = wrkr(fns, init, id, function (err, dat) { - if (err) - w.terminate(), strm.ondata.call(strm, err); - else { - if (dat[1]) - w.terminate(); - strm.ondata.call(strm, err, dat[0], dat[1]); - } - }); - w.postMessage(opts); - strm.push = function (d, f) { - if (t) - throw 'stream finished'; - if (!strm.ondata) - throw 'no stream handler'; - w.postMessage([d, t = f], [d.buffer]); - }; - strm.terminate = function () { w.terminate(); }; - }; - // read 2 bytes - var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; - // read 4 bytes - var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; - var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; - // write bytes - var wbytes = function (d, b, v) { - for (; v; ++b) - d[b] = v, v >>>= 8; - }; - // gzip header - var gzh = function (c, o) { - var fn = o.filename; - c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix - if (o.mtime != 0) - wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000)); - if (fn) { - c[3] = 8; - for (var i = 0; i <= fn.length; ++i) - c[i + 10] = fn.charCodeAt(i); + else + td[k] = v; + } + return [fnStr, td]; +}; +var ch = []; +// clone bufs +var cbfs = function (v) { + var tl = []; + for (var k in v) { + if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) + tl.push((v[k] = new v[k].constructor(v[k])).buffer); + } + return tl; +}; +// use a worker to execute code +var wrkr = function (fns, init, id, cb) { + var _a; + if (!ch[id]) { + var fnStr = '', td_1 = {}, m = fns.length - 1; + for (var i = 0; i < m; ++i) + _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; + ch[id] = wcln(fns[m], fnStr, td_1); + } + var td = mrg({}, ch[id][1]); + return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); +}; +// base async inflate fn +var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; +var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; +// gzip extra +var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; +// gunzip extra +var guze = function () { return [gzs, gzl]; }; +// zlib extra +var zle = function () { return [zlh, wbytes, adler]; }; +// unzlib extra +var zule = function () { return [zlv]; }; +// post buf +var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; +// get u8 +var gu8 = function (o) { return o && o.size && new u8(o.size); }; +// async helper +var cbify = function (dat, opts, fns, init, id, cb) { + var w = wrkr(fns, init, id, function (err, dat) { + w.terminate(); + cb(err, dat); + }); + w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []); + return function () { w.terminate(); }; +}; +// auto stream +var astrm = function (strm) { + strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; + return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; +}; +// async stream attach +var astrmify = function (fns, strm, opts, init, id) { + var t; + var w = wrkr(fns, init, id, function (err, dat) { + if (err) + w.terminate(), strm.ondata.call(strm, err); + else { + if (dat[1]) + w.terminate(); + strm.ondata.call(strm, err, dat[0], dat[1]); } + }); + w.postMessage(opts); + strm.push = function (d, f) { + if (t) + throw 'stream finished'; + if (!strm.ondata) + throw 'no stream handler'; + w.postMessage([d, t = f], [d.buffer]); }; - // gzip footer: -8 to -4 = CRC, -4 to -0 is length - // gzip start - var gzs = function (d) { - if (d[0] != 31 || d[1] != 139 || d[2] != 8) - throw 'invalid gzip data'; - var flg = d[3]; - var st = 10; - if (flg & 4) - st += d[10] | (d[11] << 8) + 2; - for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) - ; - return st + (flg & 2); - }; - // gzip length - var gzl = function (d) { - var l = d.length; - return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; - }; - // gzip header length - var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; - // zlib header - var zlh = function (c, o) { - var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; - c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); - }; - // zlib valid - var zlv = function (d) { - if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) - throw 'invalid zlib data'; - if (d[1] & 32) - throw 'invalid zlib data: preset dictionaries not supported'; - }; - function AsyncCmpStrm(opts, cb) { + strm.terminate = function () { w.terminate(); }; +}; +// read 2 bytes +var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; +// read 4 bytes +var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; +var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; +// write bytes +var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; +}; +// gzip header +var gzh = function (c, o) { + var fn = o.filename; + c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix + if (o.mtime != 0) + wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000)); + if (fn) { + c[3] = 8; + for (var i = 0; i <= fn.length; ++i) + c[i + 10] = fn.charCodeAt(i); + } +}; +// gzip footer: -8 to -4 = CRC, -4 to -0 is length +// gzip start +var gzs = function (d) { + if (d[0] != 31 || d[1] != 139 || d[2] != 8) + throw 'invalid gzip data'; + var flg = d[3]; + var st = 10; + if (flg & 4) + st += d[10] | (d[11] << 8) + 2; + for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) + ; + return st + (flg & 2); +}; +// gzip length +var gzl = function (d) { + var l = d.length; + return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; +}; +// gzip header length +var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; +// zlib header +var zlh = function (c, o) { + var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; + c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); +}; +// zlib valid +var zlv = function (d) { + if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) + throw 'invalid zlib data'; + if (d[1] & 32) + throw 'invalid zlib data: preset dictionaries not supported'; +}; +function AsyncCmpStrm(opts, cb) { + if (!cb && typeof opts == 'function') + cb = opts, opts = {}; + this.ondata = cb; + return opts; +} +// zlib footer: -4 to -0 is Adler32 +/** + * Streaming DEFLATE compression + */ +var Deflate = /*#__PURE__*/ (function () { + function Deflate(opts, cb) { if (!cb && typeof opts == 'function') cb = opts, opts = {}; this.ondata = cb; - return opts; + this.o = opts || {}; } - // zlib footer: -4 to -0 is Adler32 - /** - * Streaming DEFLATE compression - */ - var Deflate = /*#__PURE__*/ (function () { - function Deflate(opts, cb) { - if (!cb && typeof opts == 'function') - cb = opts, opts = {}; - this.ondata = cb; - this.o = opts || {}; - } - Deflate.prototype.p = function (c, f) { - this.ondata(dopt(c, this.o, 0, 0, !f), f); - }; - /** - * Pushes a chunk to be deflated - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Deflate.prototype.push = function (chunk, final) { - if (this.d) - throw 'stream finished'; - if (!this.ondata) - throw 'no stream handler'; - this.d = final; - this.p(chunk, final || false); - }; - return Deflate; - }()); - + Deflate.prototype.p = function (c, f) { + this.ondata(dopt(c, this.o, 0, 0, !f), f); + }; /** - * Asynchronous streaming DEFLATE compression + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var AsyncDeflate = /*#__PURE__*/ (function () { - function AsyncDeflate(opts, cb) { - astrmify([ - bDflt, - function () { return [astrm, Deflate]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { - var strm = new Deflate(ev.data); - onmessage = astrm(strm); - }, 6); - } - return AsyncDeflate; - }()); + Deflate.prototype.push = function (chunk, final) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + this.d = final; + this.p(chunk, final || false); + }; + return Deflate; +}()); - function deflate(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ +/** + * Asynchronous streaming DEFLATE compression + */ +var AsyncDeflate = /*#__PURE__*/ (function () { + function AsyncDeflate(opts, cb) { + astrmify([ bDflt, - ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); + function () { return [astrm, Deflate]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Deflate(ev.data); + onmessage = astrm(strm); + }, 6); } + return AsyncDeflate; +}()); + +function deflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); +} +/** + * Compresses data with DEFLATE without any wrapper + * @param data The data to compress + * @param opts The compression options + * @returns The deflated version of the data + */ +function deflateSync(data, opts) { + return dopt(data, opts || {}, 0, 0); +} +/** + * Streaming DEFLATE decompression + */ +var Inflate = /*#__PURE__*/ (function () { /** - * Compresses data with DEFLATE without any wrapper - * @param data The data to compress - * @param opts The compression options - * @returns The deflated version of the data + * Creates an inflation stream + * @param cb The callback to call whenever data is inflated */ - function deflateSync(data, opts) { - return dopt(data, opts || {}, 0, 0); + function Inflate(cb) { + this.s = {}; + this.p = new u8(0); + this.ondata = cb; } + Inflate.prototype.e = function (c) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + var l = this.p.length; + var n = new u8(l + c.length); + n.set(this.p), n.set(c, l), this.p = n; + }; + Inflate.prototype.c = function (final) { + this.d = this.s.i = final || false; + var bts = this.s.b; + var dt = inflt(this.p, this.o, this.s); + this.ondata(slc(dt, bts, this.s.b), this.d); + this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; + this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; + }; /** - * Streaming DEFLATE decompression - */ - var Inflate = /*#__PURE__*/ (function () { - /** - * Creates an inflation stream - * @param cb The callback to call whenever data is inflated - */ - function Inflate(cb) { - this.s = {}; - this.p = new u8(0); - this.ondata = cb; - } - Inflate.prototype.e = function (c) { - if (this.d) - throw 'stream finished'; - if (!this.ondata) - throw 'no stream handler'; - var l = this.p.length; - var n = new u8(l + c.length); - n.set(this.p), n.set(c, l), this.p = n; - }; - Inflate.prototype.c = function (final) { - this.d = this.s.i = final || false; - var bts = this.s.b; - var dt = inflt(this.p, this.o, this.s); - this.ondata(slc(dt, bts, this.s.b), this.d); - this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; - this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; - }; - /** - * Pushes a chunk to be inflated - * @param chunk The chunk to push - * @param final Whether this is the final chunk - */ - Inflate.prototype.push = function (chunk, final) { - this.e(chunk), this.c(final); - }; - return Inflate; - }()); - - /** - * Asynchronous streaming DEFLATE decompression + * Pushes a chunk to be inflated + * @param chunk The chunk to push + * @param final Whether this is the final chunk */ - var AsyncInflate = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous inflation stream - * @param cb The callback to call whenever data is deflated - */ - function AsyncInflate(cb) { - this.ondata = cb; - astrmify([ - bInflt, - function () { return [astrm, Inflate]; } - ], this, 0, function () { - var strm = new Inflate(); - onmessage = astrm(strm); - }, 7); - } - return AsyncInflate; - }()); + Inflate.prototype.push = function (chunk, final) { + this.e(chunk), this.c(final); + }; + return Inflate; +}()); - function inflate(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ - bInflt - ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); - } +/** + * Asynchronous streaming DEFLATE decompression + */ +var AsyncInflate = /*#__PURE__*/ (function () { /** - * Expands DEFLATE data with no wrapper - * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. - * @returns The decompressed version of the data + * Creates an asynchronous inflation stream + * @param cb The callback to call whenever data is deflated */ - function inflateSync(data, out) { - return inflt(data, out); + function AsyncInflate(cb) { + this.ondata = cb; + astrmify([ + bInflt, + function () { return [astrm, Inflate]; } + ], this, 0, function () { + var strm = new Inflate(); + onmessage = astrm(strm); + }, 7); } - // before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. - /** - * Streaming GZIP compression - */ - var Gzip = /*#__PURE__*/ (function () { - function Gzip(opts, cb) { - this.c = crc(); - this.l = 0; - this.v = 1; - Deflate.call(this, opts, cb); - } - /** - * Pushes a chunk to be GZIPped - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Gzip.prototype.push = function (chunk, final) { - Deflate.prototype.push.call(this, chunk, final); - }; - Gzip.prototype.p = function (c, f) { - this.c.p(c); - this.l += c.length; - var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); - if (this.v) - gzh(raw, this.o), this.v = 0; - if (f) - wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); - this.ondata(raw, f); - }; - return Gzip; - }()); + return AsyncInflate; +}()); +function inflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt + ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); +} +/** + * Expands DEFLATE data with no wrapper + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +function inflateSync(data, out) { + return inflt(data, out); +} +// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. +/** + * Streaming GZIP compression + */ +var Gzip = /*#__PURE__*/ (function () { + function Gzip(opts, cb) { + this.c = crc(); + this.l = 0; + this.v = 1; + Deflate.call(this, opts, cb); + } /** - * Asynchronous streaming GZIP compression + * Pushes a chunk to be GZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var AsyncGzip = /*#__PURE__*/ (function () { - function AsyncGzip(opts, cb) { - astrmify([ - bDflt, - gze, - function () { return [astrm, Deflate, Gzip]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { - var strm = new Gzip(ev.data); - onmessage = astrm(strm); - }, 8); - } - return AsyncGzip; - }()); + Gzip.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Gzip.prototype.p = function (c, f) { + this.c.p(c); + this.l += c.length; + var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); + if (this.v) + gzh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); + this.ondata(raw, f); + }; + return Gzip; +}()); - function gzip(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ +/** + * Asynchronous streaming GZIP compression + */ +var AsyncGzip = /*#__PURE__*/ (function () { + function AsyncGzip(opts, cb) { + astrmify([ bDflt, gze, - function () { return [gzipSync]; } - ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb); + function () { return [astrm, Deflate, Gzip]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Gzip(ev.data); + onmessage = astrm(strm); + }, 8); } + return AsyncGzip; +}()); + +function gzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + gze, + function () { return [gzipSync]; } + ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb); +} +/** + * Compresses data with GZIP + * @param data The data to compress + * @param opts The compression options + * @returns The gzipped version of the data + */ +function gzipSync(data, opts) { + if (!opts) + opts = {}; + var c = crc(), l = data.length; + c.p(data); + var d = dopt(data, opts, gzhl(opts), 8), s = d.length; + return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; +} +/** + * Streaming GZIP decompression + */ +var Gunzip = /*#__PURE__*/ (function () { /** - * Compresses data with GZIP - * @param data The data to compress - * @param opts The compression options - * @returns The gzipped version of the data + * Creates a GUNZIP stream + * @param cb The callback to call whenever data is inflated */ - function gzipSync(data, opts) { - if (!opts) - opts = {}; - var c = crc(), l = data.length; - c.p(data); - var d = dopt(data, opts, gzhl(opts), 8), s = d.length; - return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; + function Gunzip(cb) { + this.v = 1; + Inflate.call(this, cb); } /** - * Streaming GZIP decompression + * Pushes a chunk to be GUNZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var Gunzip = /*#__PURE__*/ (function () { - /** - * Creates a GUNZIP stream - * @param cb The callback to call whenever data is inflated - */ - function Gunzip(cb) { - this.v = 1; - Inflate.call(this, cb); + Gunzip.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + var s = this.p.length > 3 ? gzs(this.p) : 4; + if (s >= this.p.length && !final) + return; + this.p = this.p.subarray(s), this.v = 0; } - /** - * Pushes a chunk to be GUNZIPped - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Gunzip.prototype.push = function (chunk, final) { - Inflate.prototype.e.call(this, chunk); - if (this.v) { - var s = this.p.length > 3 ? gzs(this.p) : 4; - if (s >= this.p.length && !final) - return; - this.p = this.p.subarray(s), this.v = 0; - } - if (final) { - if (this.p.length < 8) - throw 'invalid gzip stream'; - this.p = this.p.subarray(0, -8); - } - // necessary to prevent TS from using the closure value - // This allows for workerization to function correctly - Inflate.prototype.c.call(this, final); - }; - return Gunzip; - }()); + if (final) { + if (this.p.length < 8) + throw 'invalid gzip stream'; + this.p = this.p.subarray(0, -8); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Gunzip; +}()); +/** + * Asynchronous streaming GZIP decompression + */ +var AsyncGunzip = /*#__PURE__*/ (function () { /** - * Asynchronous streaming GZIP decompression + * Creates an asynchronous GUNZIP stream + * @param cb The callback to call whenever data is deflated */ - var AsyncGunzip = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous GUNZIP stream - * @param cb The callback to call whenever data is deflated - */ - function AsyncGunzip(cb) { - this.ondata = cb; - astrmify([ - bInflt, - guze, - function () { return [astrm, Inflate, Gunzip]; } - ], this, 0, function () { - var strm = new Gunzip(); - onmessage = astrm(strm); - }, 9); - } - return AsyncGunzip; - }()); - - function gunzip(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ + function AsyncGunzip(cb) { + this.ondata = cb; + astrmify([ bInflt, guze, - function () { return [gunzipSync]; } - ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); - } - /** - * Expands GZIP data - * @param data The data to decompress - * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. - * @returns The decompressed version of the data - */ - function gunzipSync(data, out) { - return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); + function () { return [astrm, Inflate, Gunzip]; } + ], this, 0, function () { + var strm = new Gunzip(); + onmessage = astrm(strm); + }, 9); } - /** - * Streaming Zlib compression - */ - var Zlib = /*#__PURE__*/ (function () { - function Zlib(opts, cb) { - this.c = adler(); - this.v = 1; - Deflate.call(this, opts, cb); - } - /** - * Pushes a chunk to be zlibbed - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Zlib.prototype.push = function (chunk, final) { - Deflate.prototype.push.call(this, chunk, final); - }; - Zlib.prototype.p = function (c, f) { - this.c.p(c); - var raw = dopt(c, this.o, this.v && 2, f && 4, !f); - if (this.v) - zlh(raw, this.o), this.v = 0; - if (f) - wbytes(raw, raw.length - 4, this.c.d()); - this.ondata(raw, f); - }; - return Zlib; - }()); + return AsyncGunzip; +}()); +function gunzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt, + guze, + function () { return [gunzipSync]; } + ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); +} +/** + * Expands GZIP data + * @param data The data to decompress + * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. + * @returns The decompressed version of the data + */ +function gunzipSync(data, out) { + return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); +} +/** + * Streaming Zlib compression + */ +var Zlib = /*#__PURE__*/ (function () { + function Zlib(opts, cb) { + this.c = adler(); + this.v = 1; + Deflate.call(this, opts, cb); + } /** - * Asynchronous streaming Zlib compression + * Pushes a chunk to be zlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var AsyncZlib = /*#__PURE__*/ (function () { - function AsyncZlib(opts, cb) { - astrmify([ - bDflt, - zle, - function () { return [astrm, Deflate, Zlib]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { - var strm = new Zlib(ev.data); - onmessage = astrm(strm); - }, 10); - } - return AsyncZlib; - }()); + Zlib.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Zlib.prototype.p = function (c, f) { + this.c.p(c); + var raw = dopt(c, this.o, this.v && 2, f && 4, !f); + if (this.v) + zlh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 4, this.c.d()); + this.ondata(raw, f); + }; + return Zlib; +}()); - function zlib(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ +/** + * Asynchronous streaming Zlib compression + */ +var AsyncZlib = /*#__PURE__*/ (function () { + function AsyncZlib(opts, cb) { + astrmify([ bDflt, zle, - function () { return [zlibSync]; } - ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb); + function () { return [astrm, Deflate, Zlib]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Zlib(ev.data); + onmessage = astrm(strm); + }, 10); } + return AsyncZlib; +}()); + +function zlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + zle, + function () { return [zlibSync]; } + ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb); +} +/** + * Compress data with Zlib + * @param data The data to compress + * @param opts The compression options + * @returns The zlib-compressed version of the data + */ +function zlibSync(data, opts) { + if (!opts) + opts = {}; + var a = adler(); + a.p(data); + var d = dopt(data, opts, 2, 4); + return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; +} +/** + * Streaming Zlib decompression + */ +var Unzlib = /*#__PURE__*/ (function () { /** - * Compress data with Zlib - * @param data The data to compress - * @param opts The compression options - * @returns The zlib-compressed version of the data + * Creates a Zlib decompression stream + * @param cb The callback to call whenever data is inflated */ - function zlibSync(data, opts) { - if (!opts) - opts = {}; - var a = adler(); - a.p(data); - var d = dopt(data, opts, 2, 4); - return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; + function Unzlib(cb) { + this.v = 1; + Inflate.call(this, cb); } /** - * Streaming Zlib decompression + * Pushes a chunk to be unzlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var Unzlib = /*#__PURE__*/ (function () { - /** - * Creates a Zlib decompression stream - * @param cb The callback to call whenever data is inflated - */ - function Unzlib(cb) { - this.v = 1; - Inflate.call(this, cb); + Unzlib.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + if (this.p.length < 2 && !final) + return; + this.p = this.p.subarray(2), this.v = 0; } - /** - * Pushes a chunk to be unzlibbed - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Unzlib.prototype.push = function (chunk, final) { - Inflate.prototype.e.call(this, chunk); - if (this.v) { - if (this.p.length < 2 && !final) - return; - this.p = this.p.subarray(2), this.v = 0; - } - if (final) { - if (this.p.length < 4) - throw 'invalid zlib stream'; - this.p = this.p.subarray(0, -4); - } - // necessary to prevent TS from using the closure value - // This allows for workerization to function correctly - Inflate.prototype.c.call(this, final); - }; - return Unzlib; - }()); + if (final) { + if (this.p.length < 4) + throw 'invalid zlib stream'; + this.p = this.p.subarray(0, -4); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Unzlib; +}()); +/** + * Asynchronous streaming Zlib decompression + */ +var AsyncUnzlib = /*#__PURE__*/ (function () { /** - * Asynchronous streaming Zlib decompression + * Creates an asynchronous Zlib decompression stream + * @param cb The callback to call whenever data is deflated */ - var AsyncUnzlib = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous Zlib decompression stream - * @param cb The callback to call whenever data is deflated - */ - function AsyncUnzlib(cb) { - this.ondata = cb; - astrmify([ - bInflt, - zule, - function () { return [astrm, Inflate, Unzlib]; } - ], this, 0, function () { - var strm = new Unzlib(); - onmessage = astrm(strm); - }, 11); - } - return AsyncUnzlib; - }()); - - function unzlib(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return cbify(data, opts, [ + function AsyncUnzlib(cb) { + this.ondata = cb; + astrmify([ bInflt, zule, - function () { return [unzlibSync]; } - ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); + function () { return [astrm, Inflate, Unzlib]; } + ], this, 0, function () { + var strm = new Unzlib(); + onmessage = astrm(strm); + }, 11); } + return AsyncUnzlib; +}()); + +function unzlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt, + zule, + function () { return [unzlibSync]; } + ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); +} +/** + * Expands Zlib data + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +function unzlibSync(data, out) { + return inflt((zlv(data), data.subarray(2, -4)), out); +} + +/** + * Streaming GZIP, Zlib, or raw DEFLATE decompression + */ +var Decompress = /*#__PURE__*/ (function () { /** - * Expands Zlib data - * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. - * @returns The decompressed version of the data + * Creates a decompression stream + * @param cb The callback to call whenever data is decompressed */ - function unzlibSync(data, out) { - return inflt((zlv(data), data.subarray(2, -4)), out); + function Decompress(cb) { + this.G = Gunzip; + this.I = Inflate; + this.Z = Unzlib; + this.ondata = cb; } - /** - * Streaming GZIP, Zlib, or raw DEFLATE decompression + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var Decompress = /*#__PURE__*/ (function () { - /** - * Creates a decompression stream - * @param cb The callback to call whenever data is decompressed - */ - function Decompress(cb) { - this.G = Gunzip; - this.I = Inflate; - this.Z = Unzlib; - this.ondata = cb; - } - /** - * Pushes a chunk to be decompressed - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Decompress.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no stream handler'; - if (!this.s) { - if (this.p && this.p.length) { - var n = new u8(this.p.length + chunk.length); - n.set(this.p), n.set(chunk, this.p.length); - } - else - this.p = chunk; - if (this.p.length > 2) { - var _this_1 = this; - var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; - this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) - ? new this.G(cb) - : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) - ? new this.I(cb) - : new this.Z(cb); - this.s.push(this.p, final); - this.p = null; - } + Decompress.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no stream handler'; + if (!this.s) { + if (this.p && this.p.length) { + var n = new u8(this.p.length + chunk.length); + n.set(this.p), n.set(chunk, this.p.length); } else - this.s.push(chunk, final); - }; - return Decompress; - }()); + this.p = chunk; + if (this.p.length > 2) { + var _this_1 = this; + var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; + this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) + ? new this.G(cb) + : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) + ? new this.I(cb) + : new this.Z(cb); + this.s.push(this.p, final); + this.p = null; + } + } + else + this.s.push(chunk, final); + }; + return Decompress; +}()); +/** + * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression + */ +var AsyncDecompress = /*#__PURE__*/ (function () { /** - * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression - */ - var AsyncDecompress = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous decompression stream - * @param cb The callback to call whenever data is decompressed + * Creates an asynchronous decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function AsyncDecompress(cb) { + this.G = AsyncGunzip; + this.I = AsyncInflate; + this.Z = AsyncUnzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - function AsyncDecompress(cb) { - this.G = AsyncGunzip; - this.I = AsyncInflate; - this.Z = AsyncUnzlib; - this.ondata = cb; - } - /** - * Pushes a chunk to be decompressed - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - AsyncDecompress.prototype.push = function (chunk, final) { - Decompress.prototype.push.call(this, chunk, final); - }; - return AsyncDecompress; - }()); + AsyncDecompress.prototype.push = function (chunk, final) { + Decompress.prototype.push.call(this, chunk, final); + }; + return AsyncDecompress; +}()); - function decompress(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - return (data[0] == 31 && data[1] == 139 && data[2] == 8) - ? gunzip(data, opts, cb) - : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) - ? inflate(data, opts, cb) - : unzlib(data, opts, cb); +function decompress(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzip(data, opts, cb) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflate(data, opts, cb) + : unzlib(data, opts, cb); +} +/** + * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +function decompressSync(data, out) { + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzipSync(data, out) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflateSync(data, out) + : unzlibSync(data, out); +} +// flatten a directory structure +var fltn = function (d, p, t, o) { + for (var k in d) { + var val = d[k], n = p + k; + if (val instanceof u8) + t[n] = [val, o]; + else if (Array.isArray(val)) + t[n] = [val[0], mrg(o, val[1])]; + else + fltn(val, n + '/', t, o); + } +}; +// text encoder +var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); +// text decoder +var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); +// text decoder stream +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; +} +catch (e) { } +// decode UTF8 +var dutf8 = function (d) { + for (var r = '', i = 0;;) { + var c = d[i++]; + var eb = (c > 127) + (c > 223) + (c > 239); + if (i + eb > d.length) + return [r, slc(d, i - 1)]; + if (!eb) + r += String.fromCharCode(c); + else if (eb == 3) { + c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, + r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); + } + else if (eb & 1) + r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); + else + r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); } +}; +/** + * Streaming UTF-8 decoding + */ +var DecodeUTF8 = /*#__PURE__*/ (function () { /** - * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format - * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. - * @returns The decompressed version of the data + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is decoded */ - function decompressSync(data, out) { - return (data[0] == 31 && data[1] == 139 && data[2] == 8) - ? gunzipSync(data, out) - : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) - ? inflateSync(data, out) - : unzlibSync(data, out); - } - // flatten a directory structure - var fltn = function (d, p, t, o) { - for (var k in d) { - var val = d[k], n = p + k; - if (val instanceof u8) - t[n] = [val, o]; - else if (Array.isArray(val)) - t[n] = [val[0], mrg(o, val[1])]; - else - fltn(val, n + '/', t, o); - } - }; - // text encoder - var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); - // text decoder - var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); - // text decoder stream - var tds = 0; - try { - td.decode(et, { stream: true }); - tds = 1; - } - catch (e) { } - // decode UTF8 - var dutf8 = function (d) { - for (var r = '', i = 0;;) { - var c = d[i++]; - var eb = (c > 127) + (c > 223) + (c > 239); - if (i + eb > d.length) - return [r, slc(d, i - 1)]; - if (!eb) - r += String.fromCharCode(c); - else if (eb == 3) { - c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, - r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); - } - else if (eb & 1) - r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); - else - r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); - } - }; + function DecodeUTF8(cb) { + this.ondata = cb; + if (tds) + this.t = new TextDecoder(); + else + this.p = et; + } /** - * Streaming UTF-8 decoding + * Pushes a chunk to be decoded from UTF-8 binary + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var DecodeUTF8 = /*#__PURE__*/ (function () { - /** - * Creates a UTF-8 decoding stream - * @param cb The callback to call whenever data is decoded - */ - function DecodeUTF8(cb) { - this.ondata = cb; - if (tds) - this.t = new TextDecoder(); - else - this.p = et; - } - /** - * Pushes a chunk to be decoded from UTF-8 binary - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - DecodeUTF8.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no callback'; - final = !!final; - if (this.t) { - this.ondata(this.t.decode(chunk, { stream: true }), final); - if (final) { - if (this.t.decode().length) - throw 'invalid utf-8 data'; - this.t = null; - } - return; - } - if (!this.p) - throw 'stream finished'; - var dat = new u8(this.p.length + chunk.length); - dat.set(this.p); - dat.set(chunk, this.p.length); - var _a = dutf8(dat), ch = _a[0], np = _a[1]; + DecodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + final = !!final; + if (this.t) { + this.ondata(this.t.decode(chunk, { stream: true }), final); if (final) { - if (np.length) + if (this.t.decode().length) throw 'invalid utf-8 data'; - this.p = null; + this.t = null; } - else - this.p = np; - this.ondata(ch, final); - }; - return DecodeUTF8; - }()); + return; + } + if (!this.p) + throw 'stream finished'; + var dat = new u8(this.p.length + chunk.length); + dat.set(this.p); + dat.set(chunk, this.p.length); + var _a = dutf8(dat), ch = _a[0], np = _a[1]; + if (final) { + if (np.length) + throw 'invalid utf-8 data'; + this.p = null; + } + else + this.p = np; + this.ondata(ch, final); + }; + return DecodeUTF8; +}()); +/** + * Streaming UTF-8 encoding + */ +var EncodeUTF8 = /*#__PURE__*/ (function () { /** - * Streaming UTF-8 encoding + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is encoded */ - var EncodeUTF8 = /*#__PURE__*/ (function () { - /** - * Creates a UTF-8 decoding stream - * @param cb The callback to call whenever data is encoded - */ - function EncodeUTF8(cb) { - this.ondata = cb; - } - /** - * Pushes a chunk to be encoded to UTF-8 - * @param chunk The string data to push - * @param final Whether this is the last chunk - */ - EncodeUTF8.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no callback'; - if (this.d) - throw 'stream finished'; - this.ondata(strToU8(chunk), this.d = final || false); - }; - return EncodeUTF8; - }()); - + function EncodeUTF8(cb) { + this.ondata = cb; + } /** - * Converts a string into a Uint8Array for use with compression/decompression methods - * @param str The string to encode - * @param latin1 Whether or not to interpret the data as Latin-1. This should - * not need to be true unless decoding a binary string. - * @returns The string encoded in UTF-8/Latin-1 binary + * Pushes a chunk to be encoded to UTF-8 + * @param chunk The string data to push + * @param final Whether this is the last chunk */ - function strToU8(str, latin1) { - if (latin1) { - var ar_1 = new u8(str.length); - for (var i = 0; i < str.length; ++i) - ar_1[i] = str.charCodeAt(i); - return ar_1; + EncodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + if (this.d) + throw 'stream finished'; + this.ondata(strToU8(chunk), this.d = final || false); + }; + return EncodeUTF8; +}()); + +/** + * Converts a string into a Uint8Array for use with compression/decompression methods + * @param str The string to encode + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless decoding a binary string. + * @returns The string encoded in UTF-8/Latin-1 binary + */ +function strToU8(str, latin1) { + if (latin1) { + var ar_1 = new u8(str.length); + for (var i = 0; i < str.length; ++i) + ar_1[i] = str.charCodeAt(i); + return ar_1; + } + if (te) + return te.encode(str); + var l = str.length; + var ar = new u8(str.length + (str.length >> 1)); + var ai = 0; + var w = function (v) { ar[ai++] = v; }; + for (var i = 0; i < l; ++i) { + if (ai + 5 > ar.length) { + var n = new u8(ai + 8 + ((l - i) << 1)); + n.set(ar); + ar = n; + } + var c = str.charCodeAt(i); + if (c < 128 || latin1) + w(c); + else if (c < 2048) + w(192 | (c >> 6)), w(128 | (c & 63)); + else if (c > 55295 && c < 57344) + c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), + w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + else + w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + } + return slc(ar, 0, ai); +} +/** + * Converts a Uint8Array to a string + * @param dat The data to decode to string + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless encoding to binary string. + * @returns The original UTF-8/Latin-1 string + */ +function strFromU8(dat, latin1) { + if (latin1) { + var r = ''; + for (var i = 0; i < dat.length; i += 16384) + r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); + return r; + } + else if (td) + return td.decode(dat); + else { + var _a = dutf8(dat), out = _a[0], ext = _a[1]; + if (ext.length) + throw 'invalid utf-8 data'; + return out; + } +} +; +// deflate bit flag +var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; +// skip local zip header +var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; +// read zip header +var zh = function (d, b, z) { + var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); + var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; + return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; +}; +// read zip64 extra field +var z64e = function (d, b) { + for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) + ; + return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; +}; +// extra field length +var exfl = function (ex) { + var le = 0; + if (ex) { + for (var k in ex) { + var l = ex[k].length; + if (l > 65535) + throw 'extra field too long'; + le += l + 4; } - if (te) - return te.encode(str); - var l = str.length; - var ar = new u8(str.length + (str.length >> 1)); - var ai = 0; - var w = function (v) { ar[ai++] = v; }; - for (var i = 0; i < l; ++i) { - if (ai + 5 > ar.length) { - var n = new u8(ai + 8 + ((l - i) << 1)); - n.set(ar); - ar = n; - } - var c = str.charCodeAt(i); - if (c < 128 || latin1) - w(c); - else if (c < 2048) - w(192 | (c >> 6)), w(128 | (c & 63)); - else if (c > 55295 && c < 57344) - c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), - w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); - else - w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + } + return le; +}; +// write zip header +var wzh = function (d, b, f, fn, u, c, ce, co) { + var fl = fn.length, ex = f.extra, col = co && co.length; + var exl = exfl(ex); + wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; + if (ce != null) + d[b++] = 20, d[b++] = f.os; + d[b] = 20, b += 2; // spec compliance? what's that? + d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; + d[b++] = f.compression & 255, d[b++] = f.compression >> 8; + var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; + if (y < 0 || y > 119) + throw 'date not in range 1980-2099'; + wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; + if (c != null) { + wbytes(d, b, f.crc); + wbytes(d, b + 4, c); + wbytes(d, b + 8, f.size); + } + wbytes(d, b + 12, fl); + wbytes(d, b + 14, exl), b += 16; + if (ce != null) { + wbytes(d, b, col); + wbytes(d, b + 6, f.attrs); + wbytes(d, b + 10, ce), b += 14; + } + d.set(fn, b); + b += fl; + if (exl) { + for (var k in ex) { + var exf = ex[k], l = exf.length; + wbytes(d, b, +k); + wbytes(d, b + 2, l); + d.set(exf, b + 4), b += 4 + l; } - return slc(ar, 0, ai); } + if (col) + d.set(co, b), b += col; + return b; +}; +// write zip footer (end of central directory) +var wzf = function (o, b, c, d, e) { + wbytes(o, b, 0x6054B50); // skip disk + wbytes(o, b + 8, c); + wbytes(o, b + 10, c); + wbytes(o, b + 12, d); + wbytes(o, b + 16, e); +}; +/** + * A pass-through stream to keep data uncompressed in a ZIP archive. + */ +var ZipPassThrough = /*#__PURE__*/ (function () { /** - * Converts a Uint8Array to a string - * @param dat The data to decode to string - * @param latin1 Whether or not to interpret the data as Latin-1. This should - * not need to be true unless encoding to binary string. - * @returns The original UTF-8/Latin-1 string + * Creates a pass-through stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream */ - function strFromU8(dat, latin1) { - if (latin1) { - var r = ''; - for (var i = 0; i < dat.length; i += 16384) - r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); - return r; - } - else if (td) - return td.decode(dat); - else { - var _a = dutf8(dat), out = _a[0], ext = _a[1]; - if (ext.length) - throw 'invalid utf-8 data'; - return out; - } + function ZipPassThrough(filename) { + this.filename = filename; + this.c = crc(); + this.size = 0; + this.compression = 0; } - ; - // deflate bit flag - var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; - // skip local zip header - var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; - // read zip header - var zh = function (d, b, z) { - var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); - var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; - return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; - }; - // read zip64 extra field - var z64e = function (d, b) { - for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) - ; - return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; + /** + * Processes a chunk and pushes to the output stream. You can override this + * method in a subclass for custom behavior, but by default this passes + * the data through. You must call this.ondata(err, chunk, final) at some + * point in this method. + * @param chunk The chunk to process + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.process = function (chunk, final) { + this.ondata(null, chunk, final); }; - // extra field length - var exfl = function (ex) { - var le = 0; - if (ex) { - for (var k in ex) { - var l = ex[k].length; - if (l > 65535) - throw 'extra field too long'; - le += l + 4; - } - } - return le; + /** + * Pushes a chunk to be added. If you are subclassing this with a custom + * compression algorithm, note that you must push data from the source + * file only, pre-compression. + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback - add to ZIP archive before pushing'; + this.c.p(chunk); + this.size += chunk.length; + if (final) + this.crc = this.c.d(); + this.process(chunk, final || false); }; - // write zip header - var wzh = function (d, b, f, fn, u, c, ce, co) { - var fl = fn.length, ex = f.extra, col = co && co.length; - var exl = exfl(ex); - wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; - if (ce != null) - d[b++] = 20, d[b++] = f.os; - d[b] = 20, b += 2; // spec compliance? what's that? - d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; - d[b++] = f.compression & 255, d[b++] = f.compression >> 8; - var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; - if (y < 0 || y > 119) - throw 'date not in range 1980-2099'; - wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; - if (c != null) { - wbytes(d, b, f.crc); - wbytes(d, b + 4, c); - wbytes(d, b + 8, f.size); - } - wbytes(d, b + 12, fl); - wbytes(d, b + 14, exl), b += 16; - if (ce != null) { - wbytes(d, b, col); - wbytes(d, b + 6, f.attrs); - wbytes(d, b + 10, ce), b += 14; + return ZipPassThrough; +}()); + +// I don't extend because TypeScript extension adds 1kB of runtime bloat +/** + * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate + * for better performance + */ +var ZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function ZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new Deflate(opts, function (dat, final) { + _this_1.ondata(null, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + } + ZipDeflate.prototype.process = function (chunk, final) { + try { + this.d.push(chunk, final); } - d.set(fn, b); - b += fl; - if (exl) { - for (var k in ex) { - var exf = ex[k], l = exf.length; - wbytes(d, b, +k); - wbytes(d, b + 2, l); - d.set(exf, b + 4), b += 4 + l; - } + catch (e) { + this.ondata(e, null, final); } - if (col) - d.set(co, b), b += col; - return b; - }; - // write zip footer (end of central directory) - var wzf = function (o, b, c, d, e) { - wbytes(o, b, 0x6054B50); // skip disk - wbytes(o, b + 8, c); - wbytes(o, b + 10, c); - wbytes(o, b + 12, d); - wbytes(o, b + 16, e); }; /** - * A pass-through stream to keep data uncompressed in a ZIP archive. + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var ZipPassThrough = /*#__PURE__*/ (function () { - /** - * Creates a pass-through stream that can be added to ZIP archives - * @param filename The filename to associate with this data stream - */ - function ZipPassThrough(filename) { - this.filename = filename; - this.c = crc(); - this.size = 0; - this.compression = 0; - } - /** - * Processes a chunk and pushes to the output stream. You can override this - * method in a subclass for custom behavior, but by default this passes - * the data through. You must call this.ondata(err, chunk, final) at some - * point in this method. - * @param chunk The chunk to process - * @param final Whether this is the last chunk - */ - ZipPassThrough.prototype.process = function (chunk, final) { - this.ondata(null, chunk, final); - }; - /** - * Pushes a chunk to be added. If you are subclassing this with a custom - * compression algorithm, note that you must push data from the source - * file only, pre-compression. - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - ZipPassThrough.prototype.push = function (chunk, final) { - if (!this.ondata) - throw 'no callback - add to ZIP archive before pushing'; - this.c.p(chunk); - this.size += chunk.length; - if (final) - this.crc = this.c.d(); - this.process(chunk, final || false); - }; - return ZipPassThrough; - }()); + ZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return ZipDeflate; +}()); - // I don't extend because TypeScript extension adds 1kB of runtime bloat +/** + * Asynchronous streaming DEFLATE compression for ZIP archives + */ +var AsyncZipDeflate = /*#__PURE__*/ (function () { /** - * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate - * for better performance + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options */ - var ZipDeflate = /*#__PURE__*/ (function () { - /** - * Creates a DEFLATE stream that can be added to ZIP archives - * @param filename The filename to associate with this data stream - * @param opts The compression options - */ - function ZipDeflate(filename, opts) { - var _this_1 = this; - if (!opts) - opts = {}; - ZipPassThrough.call(this, filename); - this.d = new Deflate(opts, function (dat, final) { - _this_1.ondata(null, dat, final); - }); - this.compression = 8; - this.flag = dbf(opts.level); - } - ZipDeflate.prototype.process = function (chunk, final) { - try { - this.d.push(chunk, final); - } - catch (e) { - this.ondata(e, null, final); - } - }; - /** - * Pushes a chunk to be deflated - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - ZipDeflate.prototype.push = function (chunk, final) { - ZipPassThrough.prototype.push.call(this, chunk, final); - }; - return ZipDeflate; - }()); - + function AsyncZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new AsyncDeflate(opts, function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + this.terminate = this.d.terminate; + } + AsyncZipDeflate.prototype.process = function (chunk, final) { + this.d.push(chunk, final); + }; /** - * Asynchronous streaming DEFLATE compression for ZIP archives + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var AsyncZipDeflate = /*#__PURE__*/ (function () { - /** - * Creates a DEFLATE stream that can be added to ZIP archives - * @param filename The filename to associate with this data stream - * @param opts The compression options - */ - function AsyncZipDeflate(filename, opts) { - var _this_1 = this; - if (!opts) - opts = {}; - ZipPassThrough.call(this, filename); - this.d = new AsyncDeflate(opts, function (err, dat, final) { - _this_1.ondata(err, dat, final); - }); - this.compression = 8; - this.flag = dbf(opts.level); - this.terminate = this.d.terminate; - } - AsyncZipDeflate.prototype.process = function (chunk, final) { - this.d.push(chunk, final); - }; - /** - * Pushes a chunk to be deflated - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - AsyncZipDeflate.prototype.push = function (chunk, final) { - ZipPassThrough.prototype.push.call(this, chunk, final); - }; - return AsyncZipDeflate; - }()); + AsyncZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return AsyncZipDeflate; +}()); - // TODO: Better tree shaking +// TODO: Better tree shaking +/** + * A zippable archive to which files can incrementally be added + */ +var Zip = /*#__PURE__*/ (function () { /** - * A zippable archive to which files can incrementally be added + * Creates an empty ZIP archive to which files can be added + * @param cb The callback to call whenever data for the generated ZIP archive + * is available */ - var Zip = /*#__PURE__*/ (function () { - /** - * Creates an empty ZIP archive to which files can be added - * @param cb The callback to call whenever data for the generated ZIP archive - * is available - */ - function Zip(cb) { - this.ondata = cb; - this.u = []; - this.d = 1; - } - /** - * Adds a file to the ZIP archive - * @param file The file stream to add - */ - Zip.prototype.add = function (file) { - var _this_1 = this; - if (this.d & 2) - throw 'stream finished'; - var f = strToU8(file.filename), fl = f.length; - var com = file.comment, o = com && strToU8(com); - var u = fl != file.filename.length || (o && (com.length != o.length)); - var hl = fl + exfl(file.extra) + 30; - if (fl > 65535) - throw 'filename too long'; - var header = new u8(hl); - wzh(header, 0, file, f, u); - var chks = [header]; - var pAll = function () { - for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { - var chk = chks_1[_i]; - _this_1.ondata(null, chk, false); - } - chks = []; - }; - var tr = this.d; - this.d = 0; - var ind = this.u.length; - var uf = mrg(file, { - f: f, - u: u, - o: o, - t: function () { - if (file.terminate) - file.terminate(); - }, - r: function () { - pAll(); - if (tr) { - var nxt = _this_1.u[ind + 1]; - if (nxt) - nxt.r(); - else - _this_1.d = 1; - } - tr = 1; - } - }); - var cl = 0; - file.ondata = function (err, dat, final) { - if (err) { - _this_1.ondata(err, dat, final); - _this_1.terminate(); - } - else { - cl += dat.length; - chks.push(dat); - if (final) { - var dd = new u8(16); - wbytes(dd, 0, 0x8074B50); - wbytes(dd, 4, file.crc); - wbytes(dd, 8, cl); - wbytes(dd, 12, file.size); - chks.push(dd); - uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; - if (tr) - uf.r(); - tr = 1; - } - else if (tr) - pAll(); - } - }; - this.u.push(uf); - }; - /** - * Ends the process of adding files and prepares to emit the final chunks. - * This *must* be called after adding all desired files for the resulting - * ZIP file to work properly. - */ - Zip.prototype.end = function () { - var _this_1 = this; - if (this.d & 2) { - if (this.d & 1) - throw 'stream finishing'; - throw 'stream finished'; - } - if (this.d) - this.e(); - else - this.u.push({ - r: function () { - if (!(_this_1.d & 1)) - return; - _this_1.u.splice(-1, 1); - _this_1.e(); - }, - t: function () { } - }); - this.d = 3; - }; - Zip.prototype.e = function () { - var bt = 0, l = 0, tl = 0; - for (var _i = 0, _a = this.u; _i < _a.length; _i++) { - var f = _a[_i]; - tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); - } - var out = new u8(tl + 22); - for (var _b = 0, _c = this.u; _b < _c.length; _b++) { - var f = _c[_b]; - wzh(out, bt, f, f.f, f.u, f.c, l, f.o); - bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; - } - wzf(out, bt, this.u.length, tl, l); - this.ondata(null, out, true); - this.d = 2; - }; - /** - * A method to terminate any internal workers used by the stream. Subsequent - * calls to add() will fail. - */ - Zip.prototype.terminate = function () { - for (var _i = 0, _a = this.u; _i < _a.length; _i++) { - var f = _a[_i]; - f.t(); + function Zip(cb) { + this.ondata = cb; + this.u = []; + this.d = 1; + } + /** + * Adds a file to the ZIP archive + * @param file The file stream to add + */ + Zip.prototype.add = function (file) { + var _this_1 = this; + if (this.d & 2) + throw 'stream finished'; + var f = strToU8(file.filename), fl = f.length; + var com = file.comment, o = com && strToU8(com); + var u = fl != file.filename.length || (o && (com.length != o.length)); + var hl = fl + exfl(file.extra) + 30; + if (fl > 65535) + throw 'filename too long'; + var header = new u8(hl); + wzh(header, 0, file, f, u); + var chks = [header]; + var pAll = function () { + for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { + var chk = chks_1[_i]; + _this_1.ondata(null, chk, false); } - this.d = 2; + chks = []; }; - return Zip; - }()); - - function zip(data, opts, cb) { - if (!cb) - cb = opts, opts = {}; - if (typeof cb != 'function') - throw 'no callback'; - var r = {}; - fltn(data, '', r, opts); - var k = Object.keys(r); - var lft = k.length, o = 0, tot = 0; - var slft = lft, files = new Array(lft); - var term = []; - var tAll = function () { - for (var i = 0; i < term.length; ++i) - term[i](); - }; - var cbf = function () { - var out = new u8(tot + 22), oe = o, cdl = tot - o; - tot = 0; - for (var i = 0; i < slft; ++i) { - var f = files[i]; - try { - var l = f.c.length; - wzh(out, tot, f, f.f, f.u, l); - var badd = 30 + f.f.length + exfl(f.extra); - var loc = tot + badd; - out.set(f.c, loc); - wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; - } - catch (e) { - return cb(e, null); + var tr = this.d; + this.d = 0; + var ind = this.u.length; + var uf = mrg(file, { + f: f, + u: u, + o: o, + t: function () { + if (file.terminate) + file.terminate(); + }, + r: function () { + pAll(); + if (tr) { + var nxt = _this_1.u[ind + 1]; + if (nxt) + nxt.r(); + else + _this_1.d = 1; } + tr = 1; } - wzf(out, o, files.length, cdl, oe); - cb(null, out); - }; - if (!lft) - cbf(); - var _loop_1 = function (i) { - var fn = k[i]; - var _a = r[fn], file = _a[0], p = _a[1]; - var c = crc(), size = file.length; - c.p(file); - var f = strToU8(fn), s = f.length; - var com = p.comment, m = com && strToU8(com), ms = m && m.length; - var exl = exfl(p.extra); - var compression = p.level == 0 ? 0 : 8; - var cbl = function (e, d) { - if (e) { - tAll(); - cb(e, null); - } - else { - var l = d.length; - files[i] = mrg(p, { - size: size, - crc: c.d(), - c: d, - f: f, - m: m, - u: s != fn.length || (m && (com.length != ms)), - compression: compression - }); - o += 30 + s + exl + l; - tot += 76 + 2 * (s + exl) + (ms || 0) + l; - if (!--lft) - cbf(); - } - }; - if (s > 65535) - cbl('filename too long', null); - if (!compression) - cbl(null, file); - else if (size < 160000) { - try { - cbl(null, deflateSync(file, p)); - } - catch (e) { - cbl(e, null); + }); + var cl = 0; + file.ondata = function (err, dat, final) { + if (err) { + _this_1.ondata(err, dat, final); + _this_1.terminate(); + } + else { + cl += dat.length; + chks.push(dat); + if (final) { + var dd = new u8(16); + wbytes(dd, 0, 0x8074B50); + wbytes(dd, 4, file.crc); + wbytes(dd, 8, cl); + wbytes(dd, 12, file.size); + chks.push(dd); + uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; + if (tr) + uf.r(); + tr = 1; } + else if (tr) + pAll(); } - else - term.push(deflate(file, p, cbl)); }; - // Cannot use lft because it can decrease - for (var i = 0; i < slft; ++i) { - _loop_1(i); - } - return tAll; - } + this.u.push(uf); + }; /** - * Synchronously creates a ZIP file. Prefer using `zip` for better performance - * with more than one file. - * @param data The directory structure for the ZIP archive - * @param opts The main options, merged with per-file options - * @returns The generated ZIP archive + * Ends the process of adding files and prepares to emit the final chunks. + * This *must* be called after adding all desired files for the resulting + * ZIP file to work properly. */ - function zipSync(data, opts) { - if (!opts) - opts = {}; - var r = {}; - var files = []; - fltn(data, '', r, opts); - var o = 0; - var tot = 0; - for (var fn in r) { - var _a = r[fn], file = _a[0], p = _a[1]; - var compression = p.level == 0 ? 0 : 8; - var f = strToU8(fn), s = f.length; - var com = p.comment, m = com && strToU8(com), ms = m && m.length; - var exl = exfl(p.extra); - if (s > 65535) - throw 'filename too long'; - var d = compression ? deflateSync(file, p) : file, l = d.length; - var c = crc(); - c.p(file); - files.push(mrg(p, { - size: file.length, - crc: c.d(), - c: d, - f: f, - m: m, - u: s != fn.length || (m && (com.length != ms)), - o: o, - compression: compression - })); - o += 30 + s + exl + l; - tot += 76 + 2 * (s + exl) + (ms || 0) + l; + Zip.prototype.end = function () { + var _this_1 = this; + if (this.d & 2) { + if (this.d & 1) + throw 'stream finishing'; + throw 'stream finished'; + } + if (this.d) + this.e(); + else + this.u.push({ + r: function () { + if (!(_this_1.d & 1)) + return; + _this_1.u.splice(-1, 1); + _this_1.e(); + }, + t: function () { } + }); + this.d = 3; + }; + Zip.prototype.e = function () { + var bt = 0, l = 0, tl = 0; + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); + } + var out = new u8(tl + 22); + for (var _b = 0, _c = this.u; _b < _c.length; _b++) { + var f = _c[_b]; + wzh(out, bt, f, f.f, f.u, f.c, l, f.o); + bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; + } + wzf(out, bt, this.u.length, tl, l); + this.ondata(null, out, true); + this.d = 2; + }; + /** + * A method to terminate any internal workers used by the stream. Subsequent + * calls to add() will fail. + */ + Zip.prototype.terminate = function () { + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + f.t(); } + this.d = 2; + }; + return Zip; +}()); + +function zip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + var r = {}; + fltn(data, '', r, opts); + var k = Object.keys(r); + var lft = k.length, o = 0, tot = 0; + var slft = lft, files = new Array(lft); + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var cbf = function () { var out = new u8(tot + 22), oe = o, cdl = tot - o; - for (var i = 0; i < files.length; ++i) { + tot = 0; + for (var i = 0; i < slft; ++i) { var f = files[i]; - wzh(out, f.o, f, f.f, f.u, f.c.length); - var badd = 30 + f.f.length + exfl(f.extra); - out.set(f.c, f.o + badd); - wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); + try { + var l = f.c.length; + wzh(out, tot, f, f.f, f.u, l); + var badd = 30 + f.f.length + exfl(f.extra); + var loc = tot + badd; + out.set(f.c, loc); + wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; + } + catch (e) { + return cb(e, null); + } } wzf(out, o, files.length, cdl, oe); - return out; + cb(null, out); + }; + if (!lft) + cbf(); + var _loop_1 = function (i) { + var fn = k[i]; + var _a = r[fn], file = _a[0], p = _a[1]; + var c = crc(), size = file.length; + c.p(file); + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + var compression = p.level == 0 ? 0 : 8; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); + } + else { + var l = d.length; + files[i] = mrg(p, { + size: size, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + compression: compression + }); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + if (!--lft) + cbf(); + } + }; + if (s > 65535) + cbl('filename too long', null); + if (!compression) + cbl(null, file); + else if (size < 160000) { + try { + cbl(null, deflateSync(file, p)); + } + catch (e) { + cbl(e, null); + } + } + else + term.push(deflate(file, p, cbl)); + }; + // Cannot use lft because it can decrease + for (var i = 0; i < slft; ++i) { + _loop_1(i); + } + return tAll; +} +/** + * Synchronously creates a ZIP file. Prefer using `zip` for better performance + * with more than one file. + * @param data The directory structure for the ZIP archive + * @param opts The main options, merged with per-file options + * @returns The generated ZIP archive + */ +function zipSync(data, opts) { + if (!opts) + opts = {}; + var r = {}; + var files = []; + fltn(data, '', r, opts); + var o = 0; + var tot = 0; + for (var fn in r) { + var _a = r[fn], file = _a[0], p = _a[1]; + var compression = p.level == 0 ? 0 : 8; + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + if (s > 65535) + throw 'filename too long'; + var d = compression ? deflateSync(file, p) : file, l = d.length; + var c = crc(); + c.p(file); + files.push(mrg(p, { + size: file.length, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + o: o, + compression: compression + })); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + } + var out = new u8(tot + 22), oe = o, cdl = tot - o; + for (var i = 0; i < files.length; ++i) { + var f = files[i]; + wzh(out, f.o, f, f.f, f.u, f.c.length); + var badd = 30 + f.f.length + exfl(f.extra); + out.set(f.c, f.o + badd); + wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); } + wzf(out, o, files.length, cdl, oe); + return out; +} +/** + * Streaming pass-through decompression for ZIP archives + */ +var UnzipPassThrough = /*#__PURE__*/ (function () { + function UnzipPassThrough() { + } + UnzipPassThrough.prototype.push = function (data, final) { + this.ondata(null, data, final); + }; + UnzipPassThrough.compression = 0; + return UnzipPassThrough; +}()); + +/** + * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for + * better performance. + */ +var UnzipInflate = /*#__PURE__*/ (function () { /** - * Streaming pass-through decompression for ZIP archives + * Creates a DEFLATE decompression that can be used in ZIP archives */ - var UnzipPassThrough = /*#__PURE__*/ (function () { - function UnzipPassThrough() { + function UnzipInflate() { + var _this_1 = this; + this.i = new Inflate(function (dat, final) { + _this_1.ondata(null, dat, final); + }); + } + UnzipInflate.prototype.push = function (data, final) { + try { + this.i.push(data, final); } - UnzipPassThrough.prototype.push = function (data, final) { - this.ondata(null, data, final); - }; - UnzipPassThrough.compression = 0; - return UnzipPassThrough; - }()); + catch (e) { + this.ondata(e, data, final); + } + }; + UnzipInflate.compression = 8; + return UnzipInflate; +}()); +/** + * Asynchronous streaming DEFLATE decompression for ZIP archives + */ +var AsyncUnzipInflate = /*#__PURE__*/ (function () { /** - * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for - * better performance. + * Creates a DEFLATE decompression that can be used in ZIP archives */ - var UnzipInflate = /*#__PURE__*/ (function () { - /** - * Creates a DEFLATE decompression that can be used in ZIP archives - */ - function UnzipInflate() { - var _this_1 = this; + function AsyncUnzipInflate(_, sz) { + var _this_1 = this; + if (sz < 320000) { this.i = new Inflate(function (dat, final) { _this_1.ondata(null, dat, final); }); } - UnzipInflate.prototype.push = function (data, final) { - try { - this.i.push(data, final); - } - catch (e) { - this.ondata(e, data, final); - } - }; - UnzipInflate.compression = 8; - return UnzipInflate; - }()); + else { + this.i = new AsyncInflate(function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.terminate = this.i.terminate; + } + } + AsyncUnzipInflate.prototype.push = function (data, final) { + if (this.i.terminate) + data = slc(data, 0); + this.i.push(data, final); + }; + AsyncUnzipInflate.compression = 8; + return AsyncUnzipInflate; +}()); +/** + * A ZIP archive decompression stream that emits files as they are discovered + */ +var Unzip = /*#__PURE__*/ (function () { /** - * Asynchronous streaming DEFLATE decompression for ZIP archives + * Creates a ZIP decompression stream + * @param cb The callback to call whenever a file in the ZIP archive is found */ - var AsyncUnzipInflate = /*#__PURE__*/ (function () { - /** - * Creates a DEFLATE decompression that can be used in ZIP archives - */ - function AsyncUnzipInflate(_, sz) { - var _this_1 = this; - if (sz < 320000) { - this.i = new Inflate(function (dat, final) { - _this_1.ondata(null, dat, final); - }); - } - else { - this.i = new AsyncInflate(function (err, dat, final) { - _this_1.ondata(err, dat, final); - }); - this.terminate = this.i.terminate; - } - } - AsyncUnzipInflate.prototype.push = function (data, final) { - if (this.i.terminate) - data = slc(data, 0); - this.i.push(data, final); + function Unzip(cb) { + this.onfile = cb; + this.k = []; + this.o = { + 0: UnzipPassThrough }; - AsyncUnzipInflate.compression = 8; - return AsyncUnzipInflate; - }()); - + this.p = et; + } /** - * A ZIP archive decompression stream that emits files as they are discovered + * Pushes a chunk to be unzipped + * @param chunk The chunk to push + * @param final Whether this is the last chunk */ - var Unzip = /*#__PURE__*/ (function () { - /** - * Creates a ZIP decompression stream - * @param cb The callback to call whenever a file in the ZIP archive is found - */ - function Unzip(cb) { - this.onfile = cb; - this.k = []; - this.o = { - 0: UnzipPassThrough - }; - this.p = et; + Unzip.prototype.push = function (chunk, final) { + var _this_1 = this; + if (!this.onfile) + throw 'no callback'; + if (!this.p) + throw 'stream finished'; + if (this.c > 0) { + var len = Math.min(this.c, chunk.length); + var toAdd = chunk.subarray(0, len); + this.c -= len; + if (this.d) + this.d.push(toAdd, !this.c); + else + this.k[0].push(toAdd); + chunk = chunk.subarray(len); + if (chunk.length) + return this.push(chunk, final); } - /** - * Pushes a chunk to be unzipped - * @param chunk The chunk to push - * @param final Whether this is the last chunk - */ - Unzip.prototype.push = function (chunk, final) { - var _this_1 = this; - if (!this.onfile) - throw 'no callback'; - if (!this.p) - throw 'stream finished'; - if (this.c > 0) { - var len = Math.min(this.c, chunk.length); - var toAdd = chunk.subarray(0, len); - this.c -= len; - if (this.d) - this.d.push(toAdd, !this.c); - else - this.k[0].push(toAdd); - chunk = chunk.subarray(len); - if (chunk.length) - return this.push(chunk, final); - } + else { + var f = 0, i = 0, is = void 0, buf = void 0; + if (!this.p.length) + buf = chunk; + else if (!chunk.length) + buf = this.p; else { - var f = 0, i = 0, is = void 0, buf = void 0; - if (!this.p.length) - buf = chunk; - else if (!chunk.length) - buf = this.p; - else { - buf = new u8(this.p.length + chunk.length); - buf.set(this.p), buf.set(chunk, this.p.length); - } - var l = buf.length, oc = this.c, add = oc && this.d; - var _loop_2 = function () { - var _a; - var sig = b4(buf, i); - if (sig == 0x4034B50) { - f = 1, is = i; - this_1.d = null; - this_1.c = 0; - var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); - if (l > i + 30 + fnl + es) { - var chks_2 = []; - this_1.k.unshift(chks_2); - f = 2; - var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); - var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); - if (sc_1 == 4294967295) { - _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1]; - } - else if (dd) - sc_1 = -1; - i += es; - this_1.c = sc_1; - var d_1; - var file_1 = { - name: fn_1, - compression: cmp_1, - start: function () { - if (!file_1.ondata) - throw 'no callback'; - if (!sc_1) - file_1.ondata(null, et, true); - else { - var ctr = _this_1.o[cmp_1]; - if (!ctr) - throw 'unknown compression type ' + cmp_1; - d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); - d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; - for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { - var dat = chks_3[_i]; - d_1.push(dat, false); - } - if (_this_1.k[0] == chks_2 && _this_1.c) - _this_1.d = d_1; - else - d_1.push(et, true); + buf = new u8(this.p.length + chunk.length); + buf.set(this.p), buf.set(chunk, this.p.length); + } + var l = buf.length, oc = this.c, add = oc && this.d; + var _loop_2 = function () { + var _a; + var sig = b4(buf, i); + if (sig == 0x4034B50) { + f = 1, is = i; + this_1.d = null; + this_1.c = 0; + var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); + if (l > i + 30 + fnl + es) { + var chks_2 = []; + this_1.k.unshift(chks_2); + f = 2; + var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); + var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); + if (sc_1 == 4294967295) { + _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1]; + } + else if (dd) + sc_1 = -1; + i += es; + this_1.c = sc_1; + var d_1; + var file_1 = { + name: fn_1, + compression: cmp_1, + start: function () { + if (!file_1.ondata) + throw 'no callback'; + if (!sc_1) + file_1.ondata(null, et, true); + else { + var ctr = _this_1.o[cmp_1]; + if (!ctr) + throw 'unknown compression type ' + cmp_1; + d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); + d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; + for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { + var dat = chks_3[_i]; + d_1.push(dat, false); } - }, - terminate: function () { - if (d_1 && d_1.terminate) - d_1.terminate(); + if (_this_1.k[0] == chks_2 && _this_1.c) + _this_1.d = d_1; + else + d_1.push(et, true); } - }; - if (sc_1 >= 0) - file_1.size = sc_1, file_1.originalSize = su_1; - this_1.onfile(file_1); - } + }, + terminate: function () { + if (d_1 && d_1.terminate) + d_1.terminate(); + } + }; + if (sc_1 >= 0) + file_1.size = sc_1, file_1.originalSize = su_1; + this_1.onfile(file_1); + } + return "break"; + } + else if (oc) { + if (sig == 0x8074B50) { + is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0; return "break"; } - else if (oc) { - if (sig == 0x8074B50) { - is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0; - return "break"; - } - else if (sig == 0x2014B50) { - is = i -= 4, f = 3, this_1.c = 0; - return "break"; - } + else if (sig == 0x2014B50) { + is = i -= 4, f = 3, this_1.c = 0; + return "break"; } - }; - var this_1 = this; - for (; i < l - 4; ++i) { - var state_1 = _loop_2(); - if (state_1 === "break") - break; - } - this.p = et; - if (oc < 0) { - var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i); - if (add) - add.push(dat, !!f); - else - this.k[+(f == 2)].push(dat); } - if (f & 2) - return this.push(buf.subarray(i), final); - this.p = buf.subarray(i); + }; + var this_1 = this; + for (; i < l - 4; ++i) { + var state_1 = _loop_2(); + if (state_1 === "break") + break; } - if (final) { - if (this.c) - throw 'invalid zip file'; - this.p = null; + this.p = et; + if (oc < 0) { + var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i); + if (add) + add.push(dat, !!f); + else + this.k[+(f == 2)].push(dat); } - }; - /** - * Registers a decoder with the stream, allowing for files compressed with - * the compression type provided to be expanded correctly - * @param decoder The decoder constructor - */ - Unzip.prototype.register = function (decoder) { - this.o[decoder.compression] = decoder; - }; - return Unzip; - }()); - + if (f & 2) + return this.push(buf.subarray(i), final); + this.p = buf.subarray(i); + } + if (final) { + if (this.c) + throw 'invalid zip file'; + this.p = null; + } + }; /** - * Asynchronously decompresses a ZIP archive - * @param data The raw compressed ZIP file - * @param cb The callback to call with the decompressed files - * @returns A function that can be used to immediately terminate the unzipping + * Registers a decoder with the stream, allowing for files compressed with + * the compression type provided to be expanded correctly + * @param decoder The decoder constructor */ - function unzip(data, cb) { - if (typeof cb != 'function') - throw 'no callback'; - var term = []; - var tAll = function () { - for (var i = 0; i < term.length; ++i) - term[i](); - }; - var files = {}; - var e = data.length - 22; - for (; b4(data, e) != 0x6054B50; --e) { - if (!e || data.length - e > 65558) { - cb('invalid zip file', null); - return; - } + Unzip.prototype.register = function (decoder) { + this.o[decoder.compression] = decoder; + }; + return Unzip; +}()); + +/** + * Asynchronously decompresses a ZIP archive + * @param data The raw compressed ZIP file + * @param cb The callback to call with the decompressed files + * @returns A function that can be used to immediately terminate the unzipping + */ +function unzip(data, cb) { + if (typeof cb != 'function') + throw 'no callback'; + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) { + cb('invalid zip file', null); + return; } - ; - var lft = b2(data, e + 8); - if (!lft) - cb(null, {}); - var c = lft; - var o = b4(data, e + 16); - var z = o == 4294967295; - if (z) { - e = b4(data, e - 12); - if (b4(data, e) != 0x6064B50) { - cb('invalid zip file', null); - return; + } + ; + var lft = b2(data, e + 8); + if (!lft) + cb(null, {}); + var c = lft; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) { + cb('invalid zip file', null); + return; + } + c = lft = b4(data, e + 32); + o = b4(data, e + 48); + } + var _loop_3 = function (i) { + var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); } - c = lft = b4(data, e + 32); - o = b4(data, e + 48); - } - var _loop_3 = function (i) { - var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); - o = no; - var cbl = function (e, d) { - if (e) { - tAll(); - cb(e, null); - } - else { - files[fn] = d; - if (!--lft) - cb(null, files); + else { + files[fn] = d; + if (!--lft) + cb(null, files); + } + }; + if (!c_1) + cbl(null, slc(data, b, b + sc)); + else if (c_1 == 8) { + var infl = data.subarray(b, b + sc); + if (sc < 320000) { + try { + cbl(null, inflateSync(infl, new u8(su))); } - }; - if (!c_1) - cbl(null, slc(data, b, b + sc)); - else if (c_1 == 8) { - var infl = data.subarray(b, b + sc); - if (sc < 320000) { - try { - cbl(null, inflateSync(infl, new u8(su))); - } - catch (e) { - cbl(e, null); - } + catch (e) { + cbl(e, null); } - else - term.push(inflate(infl, { size: su }, cbl)); } else - cbl('unknown compression type ' + c_1, null); - }; - for (var i = 0; i < c; ++i) { - _loop_3(i); + term.push(inflate(infl, { size: su }, cbl)); } - return tAll; + else + cbl('unknown compression type ' + c_1, null); + }; + for (var i = 0; i < c; ++i) { + _loop_3(i); } - /** - * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better - * performance with more than one file. - * @param data The raw compressed ZIP file - * @returns The decompressed files - */ - function unzipSync(data) { - var files = {}; - var e = data.length - 22; - for (; b4(data, e) != 0x6054B50; --e) { - if (!e || data.length - e > 65558) - throw 'invalid zip file'; - } - ; - var c = b2(data, e + 8); - if (!c) - return {}; - var o = b4(data, e + 16); - var z = o == 4294967295; - if (z) { - e = b4(data, e - 12); - if (b4(data, e) != 0x6064B50) - throw 'invalid zip file'; - c = b4(data, e + 32); - o = b4(data, e + 48); - } - for (var i = 0; i < c; ++i) { - var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); - o = no; - if (!c_2) - files[fn] = slc(data, b, b + sc); - else if (c_2 == 8) - files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); - else - throw 'unknown compression type ' + c_2; - } - return files; + return tAll; +} +/** + * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better + * performance with more than one file. + * @param data The raw compressed ZIP file + * @returns The decompressed files + */ +function unzipSync(data) { + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) + throw 'invalid zip file'; } + ; + var c = b2(data, e + 8); + if (!c) + return {}; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) + throw 'invalid zip file'; + c = b4(data, e + 32); + o = b4(data, e + 48); + } + for (var i = 0; i < c; ++i) { + var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + if (!c_2) + files[fn] = slc(data, b, b + sc); + else if (c_2 == 8) + files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); + else + throw 'unknown compression type ' + c_2; + } + return files; +} - return { +return { - Deflate, - AsyncDeflate, - deflate, - deflateSync, - Inflate, - AsyncInflate, - inflate, - inflateSync, - Gzip, - AsyncGzip, - gzip, - gzipSync, - Gunzip, - AsyncGunzip, - gunzip, - gunzipSync, - Zlib, - AsyncZlib, - zlib, - zlibSync, - Unzlib, - AsyncUnzlib, - unzlib, - unzlibSync, - Decompress, - AsyncDecompress, - decompress, - decompressSync, - DecodeUTF8, - EncodeUTF8, - strToU8, - strFromU8, - ZipPassThrough, - ZipDeflate, - AsyncZipDeflate, - Zip, - zip, - zipSync, - UnzipPassThrough, - UnzipInflate, - AsyncUnzipInflate, - Unzip, - unzip, - unzipSync, + Deflate, + AsyncDeflate, + deflate, + deflateSync, + Inflate, + AsyncInflate, + inflate, + inflateSync, + Gzip, + AsyncGzip, + gzip, + gzipSync, + Gunzip, + AsyncGunzip, + gunzip, + gunzipSync, + Zlib, + AsyncZlib, + zlib, + zlibSync, + Unzlib, + AsyncUnzlib, + unzlib, + unzlibSync, + Decompress, + AsyncDecompress, + decompress, + decompressSync, + DecodeUTF8, + EncodeUTF8, + strToU8, + strFromU8, + ZipPassThrough, + ZipDeflate, + AsyncZipDeflate, + Zip, + zip, + zipSync, + UnzipPassThrough, + UnzipInflate, + AsyncUnzipInflate, + Unzip, + unzip, + unzipSync, - }; +}; } )(); From 50d3b3f090c58e7045f65b90d46a372c69d37b87 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 07:27:42 -0500 Subject: [PATCH 30/45] Addons: minimize whitespace diff in libs --- examples/jsm/libs/chevrotain.module.min.js | 7 +- examples/jsm/libs/fflate.module.js | 6 +- examples/jsm/libs/lottie_canvas.module.js | 28248 ++++++++-------- .../jsm/libs/motion-controllers.module.js | 749 +- examples/jsm/libs/opentype.module.js | 26248 +++++++------- examples/jsm/libs/utif.module.js | 2 +- 6 files changed, 27578 insertions(+), 27682 deletions(-) diff --git a/examples/jsm/libs/chevrotain.module.min.js b/examples/jsm/libs/chevrotain.module.min.js index eebcb5932ec4b3..4b8a2a836f137c 100644 --- a/examples/jsm/libs/chevrotain.module.min.js +++ b/examples/jsm/libs/chevrotain.module.min.js @@ -1,5 +1,6 @@ /*! chevrotain - v9.0.1 */ -const Fa=/*@__PURE__*/(() =>{var R=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var Er=R(Pt=>{"use strict";Object.defineProperty(Pt,"__esModule",{value:!0});Pt.VERSION=void 0;Pt.VERSION="9.0.1"});var k=R((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(t,e){for(var r=0,n=e.length,i=t.length;r{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof St=="object"&&St.exports?St.exports=e():t.regexpToAst=e()})(typeof self!="undefined"?self:sn,function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(u){this.idx=u.idx,this.input=u.input,this.groupIdx=u.groupIdx},t.prototype.pattern=function(u){this.idx=0,this.input=u,this.groupIdx=0,this.consumeChar("/");var d=this.disjunction();this.consumeChar("/");for(var A={type:"Flags",loc:{begin:this.idx,end:u.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(A,"global");break;case"i":o(A,"ignoreCase");break;case"m":o(A,"multiLine");break;case"u":o(A,"unicode");break;case"y":o(A,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:A,value:d,loc:this.loc(0)}},t.prototype.disjunction=function(){var u=[],d=this.idx;for(u.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),u.push(this.alternative());return{type:"Disjunction",value:u,loc:this.loc(d)}},t.prototype.alternative=function(){for(var u=[],d=this.idx;this.isTerm();)u.push(this.term());return{type:"Alternative",value:u,loc:this.loc(d)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var u=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(u)};case"$":return{type:"EndAnchor",loc:this.loc(u)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(u)};case"B":return{type:"NonWordBoundary",loc:this.loc(u)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var d;switch(this.popChar()){case"=":d="Lookahead";break;case"!":d="NegativeLookahead";break}s(d);var A=this.disjunction();return this.consumeChar(")"),{type:d,value:A,loc:this.loc(u)}}c()},t.prototype.quantifier=function(u){var d,A=this.idx;switch(this.popChar()){case"*":d={atLeast:0,atMost:Infinity};break;case"+":d={atLeast:1,atMost:Infinity};break;case"?":d={atLeast:0,atMost:1};break;case"{":var _=this.integerIncludingZero();switch(this.popChar()){case"}":d={atLeast:_,atMost:_};break;case",":var g;this.isDigit()?(g=this.integerIncludingZero(),d={atLeast:_,atMost:g}):d={atLeast:_,atMost:Infinity},this.consumeChar("}");break}if(u===!0&&d===void 0)return;s(d);break}if(!(u===!0&&d===void 0))return s(d),this.peekChar(0)==="?"?(this.consumeChar("?"),d.greedy=!1):d.greedy=!0,d.type="Quantifier",d.loc=this.loc(A),d},t.prototype.atom=function(){var u,d=this.idx;switch(this.peekChar()){case".":u=this.dotAll();break;case"\\":u=this.atomEscape();break;case"[":u=this.characterClass();break;case"(":u=this.group();break}return u===void 0&&this.isPatternCharacter()&&(u=this.patternCharacter()),s(u),u.loc=this.loc(d),this.isQuantifier()&&(u.quantifier=this.quantifier()),u},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[i(` +const chevrotain = /*@__PURE__*/ (() => { +var R=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var Er=R(Pt=>{"use strict";Object.defineProperty(Pt,"__esModule",{value:!0});Pt.VERSION=void 0;Pt.VERSION="9.0.1"});var k=R((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(t,e){for(var r=0,n=e.length,i=t.length;r{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof St=="object"&&St.exports?St.exports=e():t.regexpToAst=e()})(typeof self!="undefined"?self:sn,function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(u){this.idx=u.idx,this.input=u.input,this.groupIdx=u.groupIdx},t.prototype.pattern=function(u){this.idx=0,this.input=u,this.groupIdx=0,this.consumeChar("/");var d=this.disjunction();this.consumeChar("/");for(var A={type:"Flags",loc:{begin:this.idx,end:u.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(A,"global");break;case"i":o(A,"ignoreCase");break;case"m":o(A,"multiLine");break;case"u":o(A,"unicode");break;case"y":o(A,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:A,value:d,loc:this.loc(0)}},t.prototype.disjunction=function(){var u=[],d=this.idx;for(u.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),u.push(this.alternative());return{type:"Disjunction",value:u,loc:this.loc(d)}},t.prototype.alternative=function(){for(var u=[],d=this.idx;this.isTerm();)u.push(this.term());return{type:"Alternative",value:u,loc:this.loc(d)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var u=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(u)};case"$":return{type:"EndAnchor",loc:this.loc(u)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(u)};case"B":return{type:"NonWordBoundary",loc:this.loc(u)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var d;switch(this.popChar()){case"=":d="Lookahead";break;case"!":d="NegativeLookahead";break}s(d);var A=this.disjunction();return this.consumeChar(")"),{type:d,value:A,loc:this.loc(u)}}c()},t.prototype.quantifier=function(u){var d,A=this.idx;switch(this.popChar()){case"*":d={atLeast:0,atMost:Infinity};break;case"+":d={atLeast:1,atMost:Infinity};break;case"?":d={atLeast:0,atMost:1};break;case"{":var _=this.integerIncludingZero();switch(this.popChar()){case"}":d={atLeast:_,atMost:_};break;case",":var g;this.isDigit()?(g=this.integerIncludingZero(),d={atLeast:_,atMost:g}):d={atLeast:_,atMost:Infinity},this.consumeChar("}");break}if(u===!0&&d===void 0)return;s(d);break}if(!(u===!0&&d===void 0))return s(d),this.peekChar(0)==="?"?(this.consumeChar("?"),d.greedy=!1):d.greedy=!0,d.type="Quantifier",d.loc=this.loc(A),d},t.prototype.atom=function(){var u,d=this.idx;switch(this.peekChar()){case".":u=this.dotAll();break;case"\\":u=this.atomEscape();break;case"[":u=this.characterClass();break;case"(":u=this.group();break}return u===void 0&&this.isPatternCharacter()&&(u=this.patternCharacter()),s(u),u.loc=this.loc(d),this.isQuantifier()&&(u.quantifier=this.quantifier()),u},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[i(` `),i("\r"),i("\u2028"),i("\u2029")]}},t.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},t.prototype.decimalEscapeAtom=function(){var u=this.positiveInteger();return{type:"GroupBackReference",value:u}},t.prototype.characterClassEscape=function(){var u,d=!1;switch(this.popChar()){case"d":u=p;break;case"D":u=p,d=!0;break;case"s":u=m;break;case"S":u=m,d=!0;break;case"w":u=l;break;case"W":u=l,d=!0;break}return s(u),{type:"Set",value:u,complement:d}},t.prototype.controlEscapeAtom=function(){var u;switch(this.popChar()){case"f":u=i("\f");break;case"n":u=i(` `);break;case"r":u=i("\r");break;case"t":u=i(" ");break;case"v":u=i("\v");break}return s(u),{type:"Character",value:u}},t.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var u=this.popChar();if(/[a-zA-Z]/.test(u)===!1)throw Error("Invalid ");var d=u.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:d}},t.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:i("\0")}},t.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},t.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},t.prototype.identityEscapeAtom=function(){var u=this.popChar();return{type:"Character",value:i(u)}},t.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` `:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var u=this.popChar();return{type:"Character",value:i(u)}}},t.prototype.characterClass=function(){var u=[],d=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),d=!0);this.isClassAtom();){var A=this.classAtom(),_=A.type==="Character";if(_&&this.isRangeDash()){this.consumeChar("-");var g=this.classAtom(),y=g.type==="Character";if(y){if(g.value{"use strict";Object.defineProperty(E,"__esModule",{value:!0});E.Parser=E.createSyntaxDiagramsCode=E.clearCache=E.GAstVisitor=E.serializeProduction=E.serializeGrammar=E.Terminal=E.Rule=E.RepetitionWithSeparator=E.RepetitionMandatoryWithSeparator=E.RepetitionMandatory=E.Repetition=E.Option=E.NonTerminal=E.Alternative=E.Alternation=E.defaultLexerErrorProvider=E.NoViableAltException=E.NotAllInputParsedException=E.MismatchedTokenException=E.isRecognitionException=E.EarlyExitException=E.defaultParserErrorProvider=E.tokenName=E.tokenMatcher=E.tokenLabel=E.EOF=E.createTokenInstance=E.createToken=E.LexerDefinitionErrorType=E.Lexer=E.EMPTY_ALT=E.ParserDefinitionErrorType=E.EmbeddedActionsParser=E.CstParser=E.VERSION=void 0;var ku=Er();Object.defineProperty(E,"VERSION",{enumerable:!0,get:function(){return ku.VERSION}});var lr=ce();Object.defineProperty(E,"CstParser",{enumerable:!0,get:function(){return lr.CstParser}});Object.defineProperty(E,"EmbeddedActionsParser",{enumerable:!0,get:function(){return lr.EmbeddedActionsParser}});Object.defineProperty(E,"ParserDefinitionErrorType",{enumerable:!0,get:function(){return lr.ParserDefinitionErrorType}});Object.defineProperty(E,"EMPTY_ALT",{enumerable:!0,get:function(){return lr.EMPTY_ALT}});var Ma=ft();Object.defineProperty(E,"Lexer",{enumerable:!0,get:function(){return Ma.Lexer}});Object.defineProperty(E,"LexerDefinitionErrorType",{enumerable:!0,get:function(){return Ma.LexerDefinitionErrorType}});var nt=Ue();Object.defineProperty(E,"createToken",{enumerable:!0,get:function(){return nt.createToken}});Object.defineProperty(E,"createTokenInstance",{enumerable:!0,get:function(){return nt.createTokenInstance}});Object.defineProperty(E,"EOF",{enumerable:!0,get:function(){return nt.EOF}});Object.defineProperty(E,"tokenLabel",{enumerable:!0,get:function(){return nt.tokenLabel}});Object.defineProperty(E,"tokenMatcher",{enumerable:!0,get:function(){return nt.tokenMatcher}});Object.defineProperty(E,"tokenName",{enumerable:!0,get:function(){return nt.tokenName}});var Pu=mt();Object.defineProperty(E,"defaultParserErrorProvider",{enumerable:!0,get:function(){return Pu.defaultParserErrorProvider}});var Nt=et();Object.defineProperty(E,"EarlyExitException",{enumerable:!0,get:function(){return Nt.EarlyExitException}});Object.defineProperty(E,"isRecognitionException",{enumerable:!0,get:function(){return Nt.isRecognitionException}});Object.defineProperty(E,"MismatchedTokenException",{enumerable:!0,get:function(){return Nt.MismatchedTokenException}});Object.defineProperty(E,"NotAllInputParsedException",{enumerable:!0,get:function(){return Nt.NotAllInputParsedException}});Object.defineProperty(E,"NoViableAltException",{enumerable:!0,get:function(){return Nt.NoViableAltException}});var Su=kr();Object.defineProperty(E,"defaultLexerErrorProvider",{enumerable:!0,get:function(){return Su.defaultLexerErrorProvider}});var Se=ne();Object.defineProperty(E,"Alternation",{enumerable:!0,get:function(){return Se.Alternation}});Object.defineProperty(E,"Alternative",{enumerable:!0,get:function(){return Se.Alternative}});Object.defineProperty(E,"NonTerminal",{enumerable:!0,get:function(){return Se.NonTerminal}});Object.defineProperty(E,"Option",{enumerable:!0,get:function(){return Se.Option}});Object.defineProperty(E,"Repetition",{enumerable:!0,get:function(){return Se.Repetition}});Object.defineProperty(E,"RepetitionMandatory",{enumerable:!0,get:function(){return Se.RepetitionMandatory}});Object.defineProperty(E,"RepetitionMandatoryWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionMandatoryWithSeparator}});Object.defineProperty(E,"RepetitionWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionWithSeparator}});Object.defineProperty(E,"Rule",{enumerable:!0,get:function(){return Se.Rule}});Object.defineProperty(E,"Terminal",{enumerable:!0,get:function(){return Se.Terminal}});var ba=ne();Object.defineProperty(E,"serializeGrammar",{enumerable:!0,get:function(){return ba.serializeGrammar}});Object.defineProperty(E,"serializeProduction",{enumerable:!0,get:function(){return ba.serializeProduction}});var xu=$e();Object.defineProperty(E,"GAstVisitor",{enumerable:!0,get:function(){return xu.GAstVisitor}});function Cu(){console.warn(`The clearCache function was 'soft' removed from the Chevrotain API. It performs no action other than printing this message. Please avoid using it as it will be completely removed in the future`)}E.clearCache=Cu;var Lu=La();Object.defineProperty(E,"createSyntaxDiagramsCode",{enumerable:!0,get:function(){return Lu.createSyntaxDiagramsCode}});var Mu=function(){function t(){throw new Error(`The Parser class has been deprecated, use CstParser or EmbeddedActionsParser instead. -See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return t}();E.Parser=Mu});return Fa;})();export default/*@__PURE__*/Fa(); +See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return t}();E.Parser=Mu});return Fa();})(); + +export default chevrotain; diff --git a/examples/jsm/libs/fflate.module.js b/examples/jsm/libs/fflate.module.js index 8710fb41ded792..54b752c47b0c05 100644 --- a/examples/jsm/libs/fflate.module.js +++ b/examples/jsm/libs/fflate.module.js @@ -1477,9 +1477,9 @@ var Decompress = /*#__PURE__*/ (function () { */ var AsyncDecompress = /*#__PURE__*/ (function () { /** - * Creates an asynchronous decompression stream - * @param cb The callback to call whenever data is decompressed - */ + * Creates an asynchronous decompression stream + * @param cb The callback to call whenever data is decompressed + */ function AsyncDecompress(cb) { this.G = AsyncGunzip; this.I = AsyncInflate; diff --git a/examples/jsm/libs/lottie_canvas.module.js b/examples/jsm/libs/lottie_canvas.module.js index 1a537d5500bc99..77726c1e1f0c4e 100644 --- a/examples/jsm/libs/lottie_canvas.module.js +++ b/examples/jsm/libs/lottie_canvas.module.js @@ -1,14849 +1,14849 @@ const lottie = /* @__PURE */ ( () => { - const svgNS = 'http://www.w3.org/2000/svg'; +const svgNS = 'http://www.w3.org/2000/svg'; - let locationHref = ''; - let _useWebWorker = false; +let locationHref = ''; +let _useWebWorker = false; - const initialDefaultFrame = -999999; +const initialDefaultFrame = -999999; - const setWebWorker = (flag) => { _useWebWorker = !!flag; }; - const getWebWorker = () => _useWebWorker; +const setWebWorker = (flag) => { _useWebWorker = !!flag; }; +const getWebWorker = () => _useWebWorker; - const setLocationHref = (value) => { locationHref = value; }; - const getLocationHref = () => locationHref; +const setLocationHref = (value) => { locationHref = value; }; +const getLocationHref = () => locationHref; - function createTag(type) { - // return {appendChild:function(){},setAttribute:function(){},style:{}} - return document.createElement(type); - } +function createTag(type) { + // return {appendChild:function(){},setAttribute:function(){},style:{}} + return document.createElement(type); +} - function extendPrototype(sources, destination) { +function extendPrototype(sources, destination) { + var i; + var len = sources.length; + var sourcePrototype; + for (i = 0; i < len; i += 1) { + sourcePrototype = sources[i].prototype; + for (var attr in sourcePrototype) { + if (Object.prototype.hasOwnProperty.call(sourcePrototype, attr)) destination.prototype[attr] = sourcePrototype[attr]; + } + } +} + +function getDescriptor(object, prop) { + return Object.getOwnPropertyDescriptor(object, prop); +} + +function createProxyFunction(prototype) { + function ProxyFunction() {} + ProxyFunction.prototype = prototype; + return ProxyFunction; +} + +// import Howl from '../../3rd_party/howler'; + +const audioControllerFactory = (function () { + function AudioController(audioFactory) { + this.audios = []; + this.audioFactory = audioFactory; + this._volume = 1; + this._isMuted = false; + } + + AudioController.prototype = { + addAudio: function (audio) { + this.audios.push(audio); + }, + pause: function () { var i; - var len = sources.length; - var sourcePrototype; + var len = this.audios.length; for (i = 0; i < len; i += 1) { - sourcePrototype = sources[i].prototype; - for (var attr in sourcePrototype) { - if (Object.prototype.hasOwnProperty.call(sourcePrototype, attr)) destination.prototype[attr] = sourcePrototype[attr]; - } - } - } - - function getDescriptor(object, prop) { - return Object.getOwnPropertyDescriptor(object, prop); - } - - function createProxyFunction(prototype) { - function ProxyFunction() {} - ProxyFunction.prototype = prototype; - return ProxyFunction; - } - - // import Howl from '../../3rd_party/howler'; - - const audioControllerFactory = (function () { - function AudioController(audioFactory) { - this.audios = []; - this.audioFactory = audioFactory; - this._volume = 1; - this._isMuted = false; - } - - AudioController.prototype = { - addAudio: function (audio) { - this.audios.push(audio); - }, - pause: function () { - var i; - var len = this.audios.length; - for (i = 0; i < len; i += 1) { - this.audios[i].pause(); - } - }, - resume: function () { - var i; - var len = this.audios.length; - for (i = 0; i < len; i += 1) { - this.audios[i].resume(); - } - }, - setRate: function (rateValue) { - var i; - var len = this.audios.length; - for (i = 0; i < len; i += 1) { - this.audios[i].setRate(rateValue); - } - }, - createAudio: function (assetPath) { - if (this.audioFactory) { - return this.audioFactory(assetPath); - } if (window.Howl) { - return new window.Howl({ - src: [assetPath], - }); - } - return { - isPlaying: false, - play: function () { this.isPlaying = true; }, - seek: function () { this.isPlaying = false; }, - playing: function () {}, - rate: function () {}, - setVolume: function () {}, - }; - }, - setAudioFactory: function (audioFactory) { - this.audioFactory = audioFactory; - }, - setVolume: function (value) { - this._volume = value; - this._updateVolume(); - }, - mute: function () { - this._isMuted = true; - this._updateVolume(); - }, - unmute: function () { - this._isMuted = false; - this._updateVolume(); - }, - getVolume: function () { - return this._volume; - }, - _updateVolume: function () { - var i; - var len = this.audios.length; - for (i = 0; i < len; i += 1) { - this.audios[i].volume(this._volume * (this._isMuted ? 0 : 1)); - } - }, - }; - - return function () { - return new AudioController(); - }; - }()); - - const createTypedArray = (function () { - function createRegularArray(type, len) { - var i = 0; - var arr = []; - var value; - switch (type) { - case 'int16': - case 'uint8c': - value = 1; - break; - default: - value = 1.1; - break; - } - for (i = 0; i < len; i += 1) { - arr.push(value); - } - return arr; + this.audios[i].pause(); } - function createTypedArrayFactory(type, len) { - if (type === 'float32') { - return new Float32Array(len); - } if (type === 'int16') { - return new Int16Array(len); - } if (type === 'uint8c') { - return new Uint8ClampedArray(len); - } - return createRegularArray(type, len); - } - if (typeof Uint8ClampedArray === 'function' && typeof Float32Array === 'function') { - return createTypedArrayFactory; - } - return createRegularArray; - }()); - - function createSizedArray(len) { - return Array.apply(null, { length: len }); - } - - let subframeEnabled = true; - let expressionsPlugin = null; - let idPrefix$1 = ''; - const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); - let _shouldRoundValues = false; - const bmPow = Math.pow; - const bmSqrt = Math.sqrt; - const bmFloor = Math.floor; - const bmMax = Math.max; - const bmMin = Math.min; - - const BMMath = {}; - (function () { - var propertyNames = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'atan2', 'ceil', 'cbrt', 'expm1', 'clz32', 'cos', 'cosh', 'exp', 'floor', 'fround', 'hypot', 'imul', 'log', 'log1p', 'log2', 'log10', 'max', 'min', 'pow', 'random', 'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', 'E', 'LN10', 'LN2', 'LOG10E', 'LOG2E', 'PI', 'SQRT1_2', 'SQRT2']; + }, + resume: function () { var i; - var len = propertyNames.length; + var len = this.audios.length; for (i = 0; i < len; i += 1) { - BMMath[propertyNames[i]] = Math[propertyNames[i]]; - } - }()); - - function ProjectInterface$1() { return {}; } - BMMath.random = Math.random; - BMMath.abs = function (val) { - var tOfVal = typeof val; - if (tOfVal === 'object' && val.length) { - var absArr = createSizedArray(val.length); - var i; - var len = val.length; - for (i = 0; i < len; i += 1) { - absArr[i] = Math.abs(val[i]); - } - return absArr; + this.audios[i].resume(); } - return Math.abs(val); - }; - let defaultCurveSegments = 150; - const degToRads = Math.PI / 180; - const roundCorner = 0.5519; - - function roundValues(flag) { - _shouldRoundValues = !!flag; - } - - function bmRnd(value) { - if (_shouldRoundValues) { - return Math.round(value); + }, + setRate: function (rateValue) { + var i; + var len = this.audios.length; + for (i = 0; i < len; i += 1) { + this.audios[i].setRate(rateValue); + } + }, + createAudio: function (assetPath) { + if (this.audioFactory) { + return this.audioFactory(assetPath); + } if (window.Howl) { + return new window.Howl({ + src: [assetPath], + }); } - return value; - } - - function styleDiv(element) { - element.style.position = 'absolute'; - element.style.top = 0; - element.style.left = 0; - element.style.display = 'block'; - element.style.transformOrigin = '0 0'; - element.style.webkitTransformOrigin = '0 0'; - element.style.backfaceVisibility = 'visible'; - element.style.webkitBackfaceVisibility = 'visible'; - element.style.transformStyle = 'preserve-3d'; - element.style.webkitTransformStyle = 'preserve-3d'; - element.style.mozTransformStyle = 'preserve-3d'; - } - - function BMEnterFrameEvent(type, currentTime, totalTime, frameMultiplier) { - this.type = type; - this.currentTime = currentTime; - this.totalTime = totalTime; - this.direction = frameMultiplier < 0 ? -1 : 1; - } - - function BMCompleteEvent(type, frameMultiplier) { - this.type = type; - this.direction = frameMultiplier < 0 ? -1 : 1; - } - - function BMCompleteLoopEvent(type, totalLoops, currentLoop, frameMultiplier) { - this.type = type; - this.currentLoop = currentLoop; - this.totalLoops = totalLoops; - this.direction = frameMultiplier < 0 ? -1 : 1; - } - - function BMSegmentStartEvent(type, firstFrame, totalFrames) { - this.type = type; - this.firstFrame = firstFrame; - this.totalFrames = totalFrames; - } - - function BMDestroyEvent(type, target) { - this.type = type; - this.target = target; - } - - function BMRenderFrameErrorEvent(nativeError, currentTime) { - this.type = 'renderFrameError'; - this.nativeError = nativeError; - this.currentTime = currentTime; + return { + isPlaying: false, + play: function () { this.isPlaying = true; }, + seek: function () { this.isPlaying = false; }, + playing: function () {}, + rate: function () {}, + setVolume: function () {}, + }; + }, + setAudioFactory: function (audioFactory) { + this.audioFactory = audioFactory; + }, + setVolume: function (value) { + this._volume = value; + this._updateVolume(); + }, + mute: function () { + this._isMuted = true; + this._updateVolume(); + }, + unmute: function () { + this._isMuted = false; + this._updateVolume(); + }, + getVolume: function () { + return this._volume; + }, + _updateVolume: function () { + var i; + var len = this.audios.length; + for (i = 0; i < len; i += 1) { + this.audios[i].volume(this._volume * (this._isMuted ? 0 : 1)); + } + }, + }; + + return function () { + return new AudioController(); + }; +}()); + +const createTypedArray = (function () { + function createRegularArray(type, len) { + var i = 0; + var arr = []; + var value; + switch (type) { + case 'int16': + case 'uint8c': + value = 1; + break; + default: + value = 1.1; + break; } - - function BMConfigErrorEvent(nativeError) { - this.type = 'configError'; - this.nativeError = nativeError; + for (i = 0; i < len; i += 1) { + arr.push(value); } - - function BMAnimationConfigErrorEvent(type, nativeError) { - this.type = type; - this.nativeError = nativeError; + return arr; + } + function createTypedArrayFactory(type, len) { + if (type === 'float32') { + return new Float32Array(len); + } if (type === 'int16') { + return new Int16Array(len); + } if (type === 'uint8c') { + return new Uint8ClampedArray(len); } - - const createElementID = (function () { - var _count = 0; - return function createID() { - _count += 1; - return idPrefix$1 + '__lottie_element_' + _count; - }; - }()); - - function HSVtoRGB(h, s, v) { - var r; - var g; - var b; - var i; - var f; - var p; - var q; - var t; - i = Math.floor(h * 6); - f = h * 6 - i; - p = v * (1 - s); - q = v * (1 - f * s); - t = v * (1 - (1 - f) * s); - switch (i % 6) { - case 0: r = v; g = t; b = p; break; - case 1: r = q; g = v; b = p; break; - case 2: r = p; g = v; b = t; break; - case 3: r = p; g = q; b = v; break; - case 4: r = t; g = p; b = v; break; - case 5: r = v; g = p; b = q; break; - default: break; - } - return [r, - g, - b]; + return createRegularArray(type, len); + } + if (typeof Uint8ClampedArray === 'function' && typeof Float32Array === 'function') { + return createTypedArrayFactory; + } + return createRegularArray; +}()); + +function createSizedArray(len) { + return Array.apply(null, { length: len }); +} + +let subframeEnabled = true; +let expressionsPlugin = null; +let idPrefix$1 = ''; +const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); +let _shouldRoundValues = false; +const bmPow = Math.pow; +const bmSqrt = Math.sqrt; +const bmFloor = Math.floor; +const bmMax = Math.max; +const bmMin = Math.min; + +const BMMath = {}; +(function () { + var propertyNames = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'atan2', 'ceil', 'cbrt', 'expm1', 'clz32', 'cos', 'cosh', 'exp', 'floor', 'fround', 'hypot', 'imul', 'log', 'log1p', 'log2', 'log10', 'max', 'min', 'pow', 'random', 'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', 'E', 'LN10', 'LN2', 'LOG10E', 'LOG2E', 'PI', 'SQRT1_2', 'SQRT2']; + var i; + var len = propertyNames.length; + for (i = 0; i < len; i += 1) { + BMMath[propertyNames[i]] = Math[propertyNames[i]]; + } +}()); + +function ProjectInterface$1() { return {}; } +BMMath.random = Math.random; +BMMath.abs = function (val) { + var tOfVal = typeof val; + if (tOfVal === 'object' && val.length) { + var absArr = createSizedArray(val.length); + var i; + var len = val.length; + for (i = 0; i < len; i += 1) { + absArr[i] = Math.abs(val[i]); } - - function RGBtoHSV(r, g, b) { - var max = Math.max(r, g, b); - var min = Math.min(r, g, b); - var d = max - min; - var h; - var s = (max === 0 ? 0 : d / max); - var v = max / 255; - - switch (max) { - case min: h = 0; break; - case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break; - case g: h = (b - r) + d * 2; h /= 6 * d; break; - case b: h = (r - g) + d * 4; h /= 6 * d; break; - default: break; - } - - return [ - h, - s, - v, - ]; + return absArr; + } + return Math.abs(val); +}; +let defaultCurveSegments = 150; +const degToRads = Math.PI / 180; +const roundCorner = 0.5519; + +function roundValues(flag) { + _shouldRoundValues = !!flag; +} + +function bmRnd(value) { + if (_shouldRoundValues) { + return Math.round(value); + } + return value; +} + +function styleDiv(element) { + element.style.position = 'absolute'; + element.style.top = 0; + element.style.left = 0; + element.style.display = 'block'; + element.style.transformOrigin = '0 0'; + element.style.webkitTransformOrigin = '0 0'; + element.style.backfaceVisibility = 'visible'; + element.style.webkitBackfaceVisibility = 'visible'; + element.style.transformStyle = 'preserve-3d'; + element.style.webkitTransformStyle = 'preserve-3d'; + element.style.mozTransformStyle = 'preserve-3d'; +} + +function BMEnterFrameEvent(type, currentTime, totalTime, frameMultiplier) { + this.type = type; + this.currentTime = currentTime; + this.totalTime = totalTime; + this.direction = frameMultiplier < 0 ? -1 : 1; +} + +function BMCompleteEvent(type, frameMultiplier) { + this.type = type; + this.direction = frameMultiplier < 0 ? -1 : 1; +} + +function BMCompleteLoopEvent(type, totalLoops, currentLoop, frameMultiplier) { + this.type = type; + this.currentLoop = currentLoop; + this.totalLoops = totalLoops; + this.direction = frameMultiplier < 0 ? -1 : 1; +} + +function BMSegmentStartEvent(type, firstFrame, totalFrames) { + this.type = type; + this.firstFrame = firstFrame; + this.totalFrames = totalFrames; +} + +function BMDestroyEvent(type, target) { + this.type = type; + this.target = target; +} + +function BMRenderFrameErrorEvent(nativeError, currentTime) { + this.type = 'renderFrameError'; + this.nativeError = nativeError; + this.currentTime = currentTime; +} + +function BMConfigErrorEvent(nativeError) { + this.type = 'configError'; + this.nativeError = nativeError; +} + +function BMAnimationConfigErrorEvent(type, nativeError) { + this.type = type; + this.nativeError = nativeError; +} + +const createElementID = (function () { + var _count = 0; + return function createID() { + _count += 1; + return idPrefix$1 + '__lottie_element_' + _count; + }; +}()); + +function HSVtoRGB(h, s, v) { + var r; + var g; + var b; + var i; + var f; + var p; + var q; + var t; + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + default: break; + } + return [r, + g, + b]; +} + +function RGBtoHSV(r, g, b) { + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var d = max - min; + var h; + var s = (max === 0 ? 0 : d / max); + var v = max / 255; + + switch (max) { + case min: h = 0; break; + case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break; + case g: h = (b - r) + d * 2; h /= 6 * d; break; + case b: h = (r - g) + d * 4; h /= 6 * d; break; + default: break; + } + + return [ + h, + s, + v, + ]; +} + +function addSaturationToRGB(color, offset) { + var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); + hsv[1] += offset; + if (hsv[1] > 1) { + hsv[1] = 1; + } else if (hsv[1] <= 0) { + hsv[1] = 0; + } + return HSVtoRGB(hsv[0], hsv[1], hsv[2]); +} + +function addBrightnessToRGB(color, offset) { + var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); + hsv[2] += offset; + if (hsv[2] > 1) { + hsv[2] = 1; + } else if (hsv[2] < 0) { + hsv[2] = 0; + } + return HSVtoRGB(hsv[0], hsv[1], hsv[2]); +} + +function addHueToRGB(color, offset) { + var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); + hsv[0] += offset / 360; + if (hsv[0] > 1) { + hsv[0] -= 1; + } else if (hsv[0] < 0) { + hsv[0] += 1; + } + return HSVtoRGB(hsv[0], hsv[1], hsv[2]); +} + +const rgbToHex = (function () { + var colorMap = []; + var i; + var hex; + for (i = 0; i < 256; i += 1) { + hex = i.toString(16); + colorMap[i] = hex.length === 1 ? '0' + hex : hex; + } + + return function (r, g, b) { + if (r < 0) { + r = 0; } - - function addSaturationToRGB(color, offset) { - var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); - hsv[1] += offset; - if (hsv[1] > 1) { - hsv[1] = 1; - } else if (hsv[1] <= 0) { - hsv[1] = 0; - } - return HSVtoRGB(hsv[0], hsv[1], hsv[2]); + if (g < 0) { + g = 0; } - - function addBrightnessToRGB(color, offset) { - var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); - hsv[2] += offset; - if (hsv[2] > 1) { - hsv[2] = 1; - } else if (hsv[2] < 0) { - hsv[2] = 0; - } - return HSVtoRGB(hsv[0], hsv[1], hsv[2]); + if (b < 0) { + b = 0; } - - function addHueToRGB(color, offset) { - var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255); - hsv[0] += offset / 360; - if (hsv[0] > 1) { - hsv[0] -= 1; - } else if (hsv[0] < 0) { - hsv[0] += 1; - } - return HSVtoRGB(hsv[0], hsv[1], hsv[2]); + return '#' + colorMap[r] + colorMap[g] + colorMap[b]; + }; +}()); + +const setSubframeEnabled = (flag) => { subframeEnabled = !!flag; }; +const getSubframeEnabled = () => subframeEnabled; +const setExpressionsPlugin = (value) => { expressionsPlugin = value; }; +const getExpressionsPlugin = () => expressionsPlugin; +const setDefaultCurveSegments = (value) => { defaultCurveSegments = value; }; +const getDefaultCurveSegments = () => defaultCurveSegments; +const setIdPrefix = (value) => { idPrefix$1 = value; }; +const getIdPrefix = () => idPrefix$1; + +function createNS(type) { + // return {appendChild:function(){},setAttribute:function(){},style:{}} + return document.createElementNS(svgNS, type); +} + +const dataManager = (function () { + var _counterId = 1; + var processes = []; + var workerFn; + var workerInstance; + var workerProxy = { + onmessage: function () { + + }, + postMessage: function (path) { + workerFn({ + data: path, + }); + }, + }; + var _workerSelf = { + postMessage: function (data) { + workerProxy.onmessage({ + data: data, + }); + }, + }; + function createWorker(fn) { + if (window.Worker && window.Blob && getWebWorker()) { + var blob = new Blob(['var _workerSelf = self; self.onmessage = ', fn.toString()], { type: 'text/javascript' }); + // var blob = new Blob(['self.onmessage = ', fn.toString()], { type: 'text/javascript' }); + var url = URL.createObjectURL(blob); + return new Worker(url); } - - const rgbToHex = (function () { - var colorMap = []; - var i; - var hex; - for (i = 0; i < 256; i += 1) { - hex = i.toString(16); - colorMap[i] = hex.length === 1 ? '0' + hex : hex; - } - - return function (r, g, b) { - if (r < 0) { - r = 0; - } - if (g < 0) { - g = 0; - } - if (b < 0) { - b = 0; - } - return '#' + colorMap[r] + colorMap[g] + colorMap[b]; - }; - }()); - - const setSubframeEnabled = (flag) => { subframeEnabled = !!flag; }; - const getSubframeEnabled = () => subframeEnabled; - const setExpressionsPlugin = (value) => { expressionsPlugin = value; }; - const getExpressionsPlugin = () => expressionsPlugin; - const setDefaultCurveSegments = (value) => { defaultCurveSegments = value; }; - const getDefaultCurveSegments = () => defaultCurveSegments; - const setIdPrefix = (value) => { idPrefix$1 = value; }; - const getIdPrefix = () => idPrefix$1; - - function createNS(type) { - // return {appendChild:function(){},setAttribute:function(){},style:{}} - return document.createElementNS(svgNS, type); - } - - const dataManager = (function () { - var _counterId = 1; - var processes = []; - var workerFn; - var workerInstance; - var workerProxy = { - onmessage: function () { - - }, - postMessage: function (path) { - workerFn({ - data: path, - }); - }, - }; - var _workerSelf = { - postMessage: function (data) { - workerProxy.onmessage({ - data: data, - }); - }, - }; - function createWorker(fn) { - if (window.Worker && window.Blob && getWebWorker()) { - var blob = new Blob(['var _workerSelf = self; self.onmessage = ', fn.toString()], { type: 'text/javascript' }); - // var blob = new Blob(['self.onmessage = ', fn.toString()], { type: 'text/javascript' }); - var url = URL.createObjectURL(blob); - return new Worker(url); - } - workerFn = fn; - return workerProxy; - } - - function setupWorker() { - if (!workerInstance) { - workerInstance = createWorker(function workerStart(e) { - function dataFunctionManager() { - function completeLayers(layers, comps) { - var layerData; - var i; - var len = layers.length; - var j; - var jLen; - var k; - var kLen; - for (i = 0; i < len; i += 1) { - layerData = layers[i]; - if (('ks' in layerData) && !layerData.completed) { - layerData.completed = true; - if (layerData.tt) { - layers[i - 1].td = layerData.tt; - } - if (layerData.hasMask) { - var maskProps = layerData.masksProperties; - jLen = maskProps.length; - for (j = 0; j < jLen; j += 1) { - if (maskProps[j].pt.k.i) { - convertPathsToAbsoluteValues(maskProps[j].pt.k); - } else { - kLen = maskProps[j].pt.k.length; - for (k = 0; k < kLen; k += 1) { - if (maskProps[j].pt.k[k].s) { - convertPathsToAbsoluteValues(maskProps[j].pt.k[k].s[0]); - } - if (maskProps[j].pt.k[k].e) { - convertPathsToAbsoluteValues(maskProps[j].pt.k[k].e[0]); - } - } + workerFn = fn; + return workerProxy; + } + + function setupWorker() { + if (!workerInstance) { + workerInstance = createWorker(function workerStart(e) { + function dataFunctionManager() { + function completeLayers(layers, comps) { + var layerData; + var i; + var len = layers.length; + var j; + var jLen; + var k; + var kLen; + for (i = 0; i < len; i += 1) { + layerData = layers[i]; + if (('ks' in layerData) && !layerData.completed) { + layerData.completed = true; + if (layerData.tt) { + layers[i - 1].td = layerData.tt; + } + if (layerData.hasMask) { + var maskProps = layerData.masksProperties; + jLen = maskProps.length; + for (j = 0; j < jLen; j += 1) { + if (maskProps[j].pt.k.i) { + convertPathsToAbsoluteValues(maskProps[j].pt.k); + } else { + kLen = maskProps[j].pt.k.length; + for (k = 0; k < kLen; k += 1) { + if (maskProps[j].pt.k[k].s) { + convertPathsToAbsoluteValues(maskProps[j].pt.k[k].s[0]); + } + if (maskProps[j].pt.k[k].e) { + convertPathsToAbsoluteValues(maskProps[j].pt.k[k].e[0]); } } } - if (layerData.ty === 0) { - layerData.layers = findCompLayers(layerData.refId, comps); - completeLayers(layerData.layers, comps); - } else if (layerData.ty === 4) { - completeShapes(layerData.shapes); - } else if (layerData.ty === 5) { - completeText(layerData); - } } } + if (layerData.ty === 0) { + layerData.layers = findCompLayers(layerData.refId, comps); + completeLayers(layerData.layers, comps); + } else if (layerData.ty === 4) { + completeShapes(layerData.shapes); + } else if (layerData.ty === 5) { + completeText(layerData); + } } + } + } - function completeChars(chars, assets) { - if (chars) { - var i = 0; - var len = chars.length; - for (i = 0; i < len; i += 1) { - if (chars[i].t === 1) { - // var compData = findComp(chars[i].data.refId, assets); - chars[i].data.layers = findCompLayers(chars[i].data.refId, assets); - // chars[i].data.ip = 0; - // chars[i].data.op = 99999; - // chars[i].data.st = 0; - // chars[i].data.sr = 1; - // chars[i].w = compData.w; - // chars[i].data.ks = { - // a: { k: [0, 0, 0], a: 0 }, - // p: { k: [0, -compData.h, 0], a: 0 }, - // r: { k: 0, a: 0 }, - // s: { k: [100, 100], a: 0 }, - // o: { k: 100, a: 0 }, - // }; - completeLayers(chars[i].data.layers, assets); - } - } + function completeChars(chars, assets) { + if (chars) { + var i = 0; + var len = chars.length; + for (i = 0; i < len; i += 1) { + if (chars[i].t === 1) { + // var compData = findComp(chars[i].data.refId, assets); + chars[i].data.layers = findCompLayers(chars[i].data.refId, assets); + // chars[i].data.ip = 0; + // chars[i].data.op = 99999; + // chars[i].data.st = 0; + // chars[i].data.sr = 1; + // chars[i].w = compData.w; + // chars[i].data.ks = { + // a: { k: [0, 0, 0], a: 0 }, + // p: { k: [0, -compData.h, 0], a: 0 }, + // r: { k: 0, a: 0 }, + // s: { k: [100, 100], a: 0 }, + // o: { k: 100, a: 0 }, + // }; + completeLayers(chars[i].data.layers, assets); } } + } + } - function findComp(id, comps) { - var i = 0; - var len = comps.length; - while (i < len) { - if (comps[i].id === id) { - return comps[i]; - } - i += 1; - } - return null; + function findComp(id, comps) { + var i = 0; + var len = comps.length; + while (i < len) { + if (comps[i].id === id) { + return comps[i]; } + i += 1; + } + return null; + } - function findCompLayers(id, comps) { - var comp = findComp(id, comps); - if (comp) { - if (!comp.layers.__used) { - comp.layers.__used = true; - return comp.layers; - } - return JSON.parse(JSON.stringify(comp.layers)); - } - return null; + function findCompLayers(id, comps) { + var comp = findComp(id, comps); + if (comp) { + if (!comp.layers.__used) { + comp.layers.__used = true; + return comp.layers; } + return JSON.parse(JSON.stringify(comp.layers)); + } + return null; + } - function completeShapes(arr) { - var i; - var len = arr.length; - var j; - var jLen; - for (i = len - 1; i >= 0; i -= 1) { - if (arr[i].ty === 'sh') { - if (arr[i].ks.k.i) { - convertPathsToAbsoluteValues(arr[i].ks.k); - } else { - jLen = arr[i].ks.k.length; - for (j = 0; j < jLen; j += 1) { - if (arr[i].ks.k[j].s) { - convertPathsToAbsoluteValues(arr[i].ks.k[j].s[0]); - } - if (arr[i].ks.k[j].e) { - convertPathsToAbsoluteValues(arr[i].ks.k[j].e[0]); - } - } + function completeShapes(arr) { + var i; + var len = arr.length; + var j; + var jLen; + for (i = len - 1; i >= 0; i -= 1) { + if (arr[i].ty === 'sh') { + if (arr[i].ks.k.i) { + convertPathsToAbsoluteValues(arr[i].ks.k); + } else { + jLen = arr[i].ks.k.length; + for (j = 0; j < jLen; j += 1) { + if (arr[i].ks.k[j].s) { + convertPathsToAbsoluteValues(arr[i].ks.k[j].s[0]); + } + if (arr[i].ks.k[j].e) { + convertPathsToAbsoluteValues(arr[i].ks.k[j].e[0]); } - } else if (arr[i].ty === 'gr') { - completeShapes(arr[i].it); } } + } else if (arr[i].ty === 'gr') { + completeShapes(arr[i].it); } + } + } - function convertPathsToAbsoluteValues(path) { - var i; - var len = path.i.length; - for (i = 0; i < len; i += 1) { - path.i[i][0] += path.v[i][0]; - path.i[i][1] += path.v[i][1]; - path.o[i][0] += path.v[i][0]; - path.o[i][1] += path.v[i][1]; - } - } + function convertPathsToAbsoluteValues(path) { + var i; + var len = path.i.length; + for (i = 0; i < len; i += 1) { + path.i[i][0] += path.v[i][0]; + path.i[i][1] += path.v[i][1]; + path.o[i][0] += path.v[i][0]; + path.o[i][1] += path.v[i][1]; + } + } - function checkVersion(minimum, animVersionString) { - var animVersion = animVersionString ? animVersionString.split('.') : [100, 100, 100]; - if (minimum[0] > animVersion[0]) { - return true; - } if (animVersion[0] > minimum[0]) { - return false; - } - if (minimum[1] > animVersion[1]) { - return true; - } if (animVersion[1] > minimum[1]) { - return false; - } - if (minimum[2] > animVersion[2]) { - return true; - } if (animVersion[2] > minimum[2]) { - return false; - } - return null; - } + function checkVersion(minimum, animVersionString) { + var animVersion = animVersionString ? animVersionString.split('.') : [100, 100, 100]; + if (minimum[0] > animVersion[0]) { + return true; + } if (animVersion[0] > minimum[0]) { + return false; + } + if (minimum[1] > animVersion[1]) { + return true; + } if (animVersion[1] > minimum[1]) { + return false; + } + if (minimum[2] > animVersion[2]) { + return true; + } if (animVersion[2] > minimum[2]) { + return false; + } + return null; + } - var checkText = (function () { - var minimumVersion = [4, 4, 14]; - - function updateTextLayer(textLayer) { - var documentData = textLayer.t.d; - textLayer.t.d = { - k: [ - { - s: documentData, - t: 0, - }, - ], - }; + var checkText = (function () { + var minimumVersion = [4, 4, 14]; + + function updateTextLayer(textLayer) { + var documentData = textLayer.t.d; + textLayer.t.d = { + k: [ + { + s: documentData, + t: 0, + }, + ], + }; + } + + function iterateLayers(layers) { + var i; + var len = layers.length; + for (i = 0; i < len; i += 1) { + if (layers[i].ty === 5) { + updateTextLayer(layers[i]); } + } + } - function iterateLayers(layers) { + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { var i; - var len = layers.length; + var len = animationData.assets.length; for (i = 0; i < len; i += 1) { - if (layers[i].ty === 5) { - updateTextLayer(layers[i]); + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); } } } + } + }; + }()); - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { - var i; - var len = animationData.assets.length; - for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); + var checkChars = (function () { + var minimumVersion = [4, 7, 99]; + return function (animationData) { + if (animationData.chars && !checkVersion(minimumVersion, animationData.v)) { + var i; + var len = animationData.chars.length; + for (i = 0; i < len; i += 1) { + var charData = animationData.chars[i]; + if (charData.data && charData.data.shapes) { + completeShapes(charData.data.shapes); + charData.data.ip = 0; + charData.data.op = 99999; + charData.data.st = 0; + charData.data.sr = 1; + charData.data.ks = { + p: { k: [0, 0], a: 0 }, + s: { k: [100, 100], a: 0 }, + a: { k: [0, 0], a: 0 }, + r: { k: 0, a: 0 }, + o: { k: 100, a: 0 }, + }; + if (!animationData.chars[i].t) { + charData.data.shapes.push( + { + ty: 'no', } - } - } - } - }; - }()); - - var checkChars = (function () { - var minimumVersion = [4, 7, 99]; - return function (animationData) { - if (animationData.chars && !checkVersion(minimumVersion, animationData.v)) { - var i; - var len = animationData.chars.length; - for (i = 0; i < len; i += 1) { - var charData = animationData.chars[i]; - if (charData.data && charData.data.shapes) { - completeShapes(charData.data.shapes); - charData.data.ip = 0; - charData.data.op = 99999; - charData.data.st = 0; - charData.data.sr = 1; - charData.data.ks = { + ); + charData.data.shapes[0].it.push( + { p: { k: [0, 0], a: 0 }, s: { k: [100, 100], a: 0 }, a: { k: [0, 0], a: 0 }, r: { k: 0, a: 0 }, o: { k: 100, a: 0 }, - }; - if (!animationData.chars[i].t) { - charData.data.shapes.push( - { - ty: 'no', - } - ); - charData.data.shapes[0].it.push( - { - p: { k: [0, 0], a: 0 }, - s: { k: [100, 100], a: 0 }, - a: { k: [0, 0], a: 0 }, - r: { k: 0, a: 0 }, - o: { k: 100, a: 0 }, - sk: { k: 0, a: 0 }, - sa: { k: 0, a: 0 }, - ty: 'tr', - } - ); + sk: { k: 0, a: 0 }, + sa: { k: 0, a: 0 }, + ty: 'tr', } - } + ); } } - }; - }()); + } + } + }; + }()); - var checkPathProperties = (function () { - var minimumVersion = [5, 7, 15]; + var checkPathProperties = (function () { + var minimumVersion = [5, 7, 15]; - function updateTextLayer(textLayer) { - var pathData = textLayer.t.p; - if (typeof pathData.a === 'number') { - pathData.a = { - a: 0, - k: pathData.a, - }; - } - if (typeof pathData.p === 'number') { - pathData.p = { - a: 0, - k: pathData.p, - }; - } - if (typeof pathData.r === 'number') { - pathData.r = { - a: 0, - k: pathData.r, - }; - } + function updateTextLayer(textLayer) { + var pathData = textLayer.t.p; + if (typeof pathData.a === 'number') { + pathData.a = { + a: 0, + k: pathData.a, + }; + } + if (typeof pathData.p === 'number') { + pathData.p = { + a: 0, + k: pathData.p, + }; + } + if (typeof pathData.r === 'number') { + pathData.r = { + a: 0, + k: pathData.r, + }; + } + } + + function iterateLayers(layers) { + var i; + var len = layers.length; + for (i = 0; i < len; i += 1) { + if (layers[i].ty === 5) { + updateTextLayer(layers[i]); } + } + } - function iterateLayers(layers) { + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { var i; - var len = layers.length; + var len = animationData.assets.length; for (i = 0; i < len; i += 1) { - if (layers[i].ty === 5) { - updateTextLayer(layers[i]); + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); } } } + } + }; + }()); - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { - var i; - var len = animationData.assets.length; - for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); - } - } - } - } - }; - }()); - - var checkColors = (function () { - var minimumVersion = [4, 1, 9]; + var checkColors = (function () { + var minimumVersion = [4, 1, 9]; - function iterateShapes(shapes) { - var i; - var len = shapes.length; - var j; - var jLen; - for (i = 0; i < len; i += 1) { - if (shapes[i].ty === 'gr') { - iterateShapes(shapes[i].it); - } else if (shapes[i].ty === 'fl' || shapes[i].ty === 'st') { - if (shapes[i].c.k && shapes[i].c.k[0].i) { - jLen = shapes[i].c.k.length; - for (j = 0; j < jLen; j += 1) { - if (shapes[i].c.k[j].s) { - shapes[i].c.k[j].s[0] /= 255; - shapes[i].c.k[j].s[1] /= 255; - shapes[i].c.k[j].s[2] /= 255; - shapes[i].c.k[j].s[3] /= 255; - } - if (shapes[i].c.k[j].e) { - shapes[i].c.k[j].e[0] /= 255; - shapes[i].c.k[j].e[1] /= 255; - shapes[i].c.k[j].e[2] /= 255; - shapes[i].c.k[j].e[3] /= 255; - } - } - } else { - shapes[i].c.k[0] /= 255; - shapes[i].c.k[1] /= 255; - shapes[i].c.k[2] /= 255; - shapes[i].c.k[3] /= 255; + function iterateShapes(shapes) { + var i; + var len = shapes.length; + var j; + var jLen; + for (i = 0; i < len; i += 1) { + if (shapes[i].ty === 'gr') { + iterateShapes(shapes[i].it); + } else if (shapes[i].ty === 'fl' || shapes[i].ty === 'st') { + if (shapes[i].c.k && shapes[i].c.k[0].i) { + jLen = shapes[i].c.k.length; + for (j = 0; j < jLen; j += 1) { + if (shapes[i].c.k[j].s) { + shapes[i].c.k[j].s[0] /= 255; + shapes[i].c.k[j].s[1] /= 255; + shapes[i].c.k[j].s[2] /= 255; + shapes[i].c.k[j].s[3] /= 255; + } + if (shapes[i].c.k[j].e) { + shapes[i].c.k[j].e[0] /= 255; + shapes[i].c.k[j].e[1] /= 255; + shapes[i].c.k[j].e[2] /= 255; + shapes[i].c.k[j].e[3] /= 255; } } + } else { + shapes[i].c.k[0] /= 255; + shapes[i].c.k[1] /= 255; + shapes[i].c.k[2] /= 255; + shapes[i].c.k[3] /= 255; } } + } + } + + function iterateLayers(layers) { + var i; + var len = layers.length; + for (i = 0; i < len; i += 1) { + if (layers[i].ty === 4) { + iterateShapes(layers[i].shapes); + } + } + } - function iterateLayers(layers) { + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { var i; - var len = layers.length; + var len = animationData.assets.length; for (i = 0; i < len; i += 1) { - if (layers[i].ty === 4) { - iterateShapes(layers[i].shapes); + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); } } } - - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { - var i; - var len = animationData.assets.length; - for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); - } + } + }; + }()); + + var checkShapes = (function () { + var minimumVersion = [4, 4, 18]; + + function completeClosingShapes(arr) { + var i; + var len = arr.length; + var j; + var jLen; + for (i = len - 1; i >= 0; i -= 1) { + if (arr[i].ty === 'sh') { + if (arr[i].ks.k.i) { + arr[i].ks.k.c = arr[i].closed; + } else { + jLen = arr[i].ks.k.length; + for (j = 0; j < jLen; j += 1) { + if (arr[i].ks.k[j].s) { + arr[i].ks.k[j].s[0].c = arr[i].closed; + } + if (arr[i].ks.k[j].e) { + arr[i].ks.k[j].e[0].c = arr[i].closed; } } } - }; - }()); - - var checkShapes = (function () { - var minimumVersion = [4, 4, 18]; + } else if (arr[i].ty === 'gr') { + completeClosingShapes(arr[i].it); + } + } + } - function completeClosingShapes(arr) { - var i; - var len = arr.length; - var j; - var jLen; - for (i = len - 1; i >= 0; i -= 1) { - if (arr[i].ty === 'sh') { - if (arr[i].ks.k.i) { - arr[i].ks.k.c = arr[i].closed; - } else { - jLen = arr[i].ks.k.length; - for (j = 0; j < jLen; j += 1) { - if (arr[i].ks.k[j].s) { - arr[i].ks.k[j].s[0].c = arr[i].closed; - } - if (arr[i].ks.k[j].e) { - arr[i].ks.k[j].e[0].c = arr[i].closed; - } + function iterateLayers(layers) { + var layerData; + var i; + var len = layers.length; + var j; + var jLen; + var k; + var kLen; + for (i = 0; i < len; i += 1) { + layerData = layers[i]; + if (layerData.hasMask) { + var maskProps = layerData.masksProperties; + jLen = maskProps.length; + for (j = 0; j < jLen; j += 1) { + if (maskProps[j].pt.k.i) { + maskProps[j].pt.k.c = maskProps[j].cl; + } else { + kLen = maskProps[j].pt.k.length; + for (k = 0; k < kLen; k += 1) { + if (maskProps[j].pt.k[k].s) { + maskProps[j].pt.k[k].s[0].c = maskProps[j].cl; + } + if (maskProps[j].pt.k[k].e) { + maskProps[j].pt.k[k].e[0].c = maskProps[j].cl; } } - } else if (arr[i].ty === 'gr') { - completeClosingShapes(arr[i].it); } } } + if (layerData.ty === 4) { + completeClosingShapes(layerData.shapes); + } + } + } - function iterateLayers(layers) { - var layerData; + return function (animationData) { + if (checkVersion(minimumVersion, animationData.v)) { + iterateLayers(animationData.layers); + if (animationData.assets) { var i; - var len = layers.length; - var j; - var jLen; - var k; - var kLen; + var len = animationData.assets.length; for (i = 0; i < len; i += 1) { - layerData = layers[i]; - if (layerData.hasMask) { - var maskProps = layerData.masksProperties; - jLen = maskProps.length; - for (j = 0; j < jLen; j += 1) { - if (maskProps[j].pt.k.i) { - maskProps[j].pt.k.c = maskProps[j].cl; - } else { - kLen = maskProps[j].pt.k.length; - for (k = 0; k < kLen; k += 1) { - if (maskProps[j].pt.k[k].s) { - maskProps[j].pt.k[k].s[0].c = maskProps[j].cl; - } - if (maskProps[j].pt.k[k].e) { - maskProps[j].pt.k[k].e[0].c = maskProps[j].cl; - } - } - } - } - } - if (layerData.ty === 4) { - completeClosingShapes(layerData.shapes); + if (animationData.assets[i].layers) { + iterateLayers(animationData.assets[i].layers); } } } + } + }; + }()); - return function (animationData) { - if (checkVersion(minimumVersion, animationData.v)) { - iterateLayers(animationData.layers); - if (animationData.assets) { - var i; - var len = animationData.assets.length; - for (i = 0; i < len; i += 1) { - if (animationData.assets[i].layers) { - iterateLayers(animationData.assets[i].layers); - } - } - } - } - }; - }()); + function completeData(animationData) { + if (animationData.__complete) { + return; + } + checkColors(animationData); + checkText(animationData); + checkChars(animationData); + checkPathProperties(animationData); + checkShapes(animationData); + completeLayers(animationData.layers, animationData.assets); + completeChars(animationData.chars, animationData.assets); + animationData.__complete = true; + } - function completeData(animationData) { - if (animationData.__complete) { - return; - } - checkColors(animationData); - checkText(animationData); - checkChars(animationData); - checkPathProperties(animationData); - checkShapes(animationData); - completeLayers(animationData.layers, animationData.assets); - completeChars(animationData.chars, animationData.assets); - animationData.__complete = true; - } + function completeText(data) { + if (data.t.a.length === 0 && !('m' in data.t.p)) { + // data.singleShape = true; + } + } - function completeText(data) { - if (data.t.a.length === 0 && !('m' in data.t.p)) { - // data.singleShape = true; - } - } + var moduleOb = {}; + moduleOb.completeData = completeData; + moduleOb.checkColors = checkColors; + moduleOb.checkChars = checkChars; + moduleOb.checkPathProperties = checkPathProperties; + moduleOb.checkShapes = checkShapes; + moduleOb.completeLayers = completeLayers; - var moduleOb = {}; - moduleOb.completeData = completeData; - moduleOb.checkColors = checkColors; - moduleOb.checkChars = checkChars; - moduleOb.checkPathProperties = checkPathProperties; - moduleOb.checkShapes = checkShapes; - moduleOb.completeLayers = completeLayers; + return moduleOb; + } + if (!_workerSelf.dataManager) { + _workerSelf.dataManager = dataFunctionManager(); + } - return moduleOb; - } - if (!_workerSelf.dataManager) { - _workerSelf.dataManager = dataFunctionManager(); + if (!_workerSelf.assetLoader) { + _workerSelf.assetLoader = (function () { + function formatResponse(xhr) { + // using typeof doubles the time of execution of this method, + // so if available, it's better to use the header to validate the type + var contentTypeHeader = xhr.getResponseHeader('content-type'); + if (contentTypeHeader && xhr.responseType === 'json' && contentTypeHeader.indexOf('json') !== -1) { + return xhr.response; + } + if (xhr.response && typeof xhr.response === 'object') { + return xhr.response; + } if (xhr.response && typeof xhr.response === 'string') { + return JSON.parse(xhr.response); + } if (xhr.responseText) { + return JSON.parse(xhr.responseText); + } + return null; } - if (!_workerSelf.assetLoader) { - _workerSelf.assetLoader = (function () { - function formatResponse(xhr) { - // using typeof doubles the time of execution of this method, - // so if available, it's better to use the header to validate the type - var contentTypeHeader = xhr.getResponseHeader('content-type'); - if (contentTypeHeader && xhr.responseType === 'json' && contentTypeHeader.indexOf('json') !== -1) { - return xhr.response; - } - if (xhr.response && typeof xhr.response === 'object') { - return xhr.response; - } if (xhr.response && typeof xhr.response === 'string') { - return JSON.parse(xhr.response); - } if (xhr.responseText) { - return JSON.parse(xhr.responseText); - } - return null; - } - - function loadAsset(path, fullPath, callback, errorCallback) { - var response; - var xhr = new XMLHttpRequest(); - // set responseType after calling open or IE will break. - try { - // This crashes on Android WebView prior to KitKat - xhr.responseType = 'json'; - } catch (err) {} // eslint-disable-line no-empty - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - response = formatResponse(xhr); - callback(response); - } else { - try { - response = formatResponse(xhr); - callback(response); - } catch (err) { - if (errorCallback) { - errorCallback(err); - } - } + function loadAsset(path, fullPath, callback, errorCallback) { + var response; + var xhr = new XMLHttpRequest(); + // set responseType after calling open or IE will break. + try { + // This crashes on Android WebView prior to KitKat + xhr.responseType = 'json'; + } catch (err) {} // eslint-disable-line no-empty + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + response = formatResponse(xhr); + callback(response); + } else { + try { + response = formatResponse(xhr); + callback(response); + } catch (err) { + if (errorCallback) { + errorCallback(err); } } - }; - try { - xhr.open('GET', path, true); - } catch (error) { - xhr.open('GET', fullPath + '/' + path, true); } - xhr.send(); } - return { - load: loadAsset, - }; - }()); + }; + try { + xhr.open('GET', path, true); + } catch (error) { + xhr.open('GET', fullPath + '/' + path, true); + } + xhr.send(); } + return { + load: loadAsset, + }; + }()); + } - if (e.data.type === 'loadAnimation') { - _workerSelf.assetLoader.load( - e.data.path, - e.data.fullPath, - function (data) { - _workerSelf.dataManager.completeData(data); - _workerSelf.postMessage({ - id: e.data.id, - payload: data, - status: 'success', - }); - }, - function () { - _workerSelf.postMessage({ - id: e.data.id, - status: 'error', - }); - } - ); - } else if (e.data.type === 'complete') { - var animation = e.data.animation; - _workerSelf.dataManager.completeData(animation); + if (e.data.type === 'loadAnimation') { + _workerSelf.assetLoader.load( + e.data.path, + e.data.fullPath, + function (data) { + _workerSelf.dataManager.completeData(data); _workerSelf.postMessage({ id: e.data.id, - payload: animation, + payload: data, status: 'success', }); - } else if (e.data.type === 'loadData') { - _workerSelf.assetLoader.load( - e.data.path, - e.data.fullPath, - function (data) { - _workerSelf.postMessage({ - id: e.data.id, - payload: data, - status: 'success', - }); - }, - function () { - _workerSelf.postMessage({ - id: e.data.id, - status: 'error', - }); - } - ); + }, + function () { + _workerSelf.postMessage({ + id: e.data.id, + status: 'error', + }); } + ); + } else if (e.data.type === 'complete') { + var animation = e.data.animation; + _workerSelf.dataManager.completeData(animation); + _workerSelf.postMessage({ + id: e.data.id, + payload: animation, + status: 'success', }); - - workerInstance.onmessage = function (event) { - var data = event.data; - var id = data.id; - var process = processes[id]; - processes[id] = null; - if (data.status === 'success') { - process.onComplete(data.payload); - } else if (process.onError) { - process.onError(); + } else if (e.data.type === 'loadData') { + _workerSelf.assetLoader.load( + e.data.path, + e.data.fullPath, + function (data) { + _workerSelf.postMessage({ + id: e.data.id, + payload: data, + status: 'success', + }); + }, + function () { + _workerSelf.postMessage({ + id: e.data.id, + status: 'error', + }); } - }; + ); } - } + }); - function createProcess(onComplete, onError) { - _counterId += 1; - var id = 'processId_' + _counterId; - processes[id] = { - onComplete: onComplete, - onError: onError, - }; - return id; + workerInstance.onmessage = function (event) { + var data = event.data; + var id = data.id; + var process = processes[id]; + processes[id] = null; + if (data.status === 'success') { + process.onComplete(data.payload); + } else if (process.onError) { + process.onError(); + } + }; + } + } + + function createProcess(onComplete, onError) { + _counterId += 1; + var id = 'processId_' + _counterId; + processes[id] = { + onComplete: onComplete, + onError: onError, + }; + return id; + } + + function loadAnimation(path, onComplete, onError) { + setupWorker(); + var processId = createProcess(onComplete, onError); + workerInstance.postMessage({ + type: 'loadAnimation', + path: path, + fullPath: window.location.origin + window.location.pathname, + id: processId, + }); + } + + function loadData(path, onComplete, onError) { + setupWorker(); + var processId = createProcess(onComplete, onError); + workerInstance.postMessage({ + type: 'loadData', + path: path, + fullPath: window.location.origin + window.location.pathname, + id: processId, + }); + } + + function completeAnimation(anim, onComplete, onError) { + setupWorker(); + var processId = createProcess(onComplete, onError); + workerInstance.postMessage({ + type: 'complete', + animation: anim, + id: processId, + }); + } + + return { + loadAnimation: loadAnimation, + loadData: loadData, + completeAnimation: completeAnimation, + }; +}()); + +const ImagePreloader = (function () { + var proxyImage = (function () { + var canvas = createTag('canvas'); + canvas.width = 1; + canvas.height = 1; + var ctx = canvas.getContext('2d'); + ctx.fillStyle = 'rgba(0,0,0,0)'; + ctx.fillRect(0, 0, 1, 1); + return canvas; + }()); + + function imageLoaded() { + this.loadedAssets += 1; + if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { + if (this.imagesLoadedCb) { + this.imagesLoadedCb(null); } - - function loadAnimation(path, onComplete, onError) { - setupWorker(); - var processId = createProcess(onComplete, onError); - workerInstance.postMessage({ - type: 'loadAnimation', - path: path, - fullPath: window.location.origin + window.location.pathname, - id: processId, - }); + } + } + function footageLoaded() { + this.loadedFootagesCount += 1; + if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { + if (this.imagesLoadedCb) { + this.imagesLoadedCb(null); } - - function loadData(path, onComplete, onError) { - setupWorker(); - var processId = createProcess(onComplete, onError); - workerInstance.postMessage({ - type: 'loadData', - path: path, - fullPath: window.location.origin + window.location.pathname, - id: processId, - }); + } + } + + function getAssetsPath(assetData, assetsPath, originalPath) { + var path = ''; + if (assetData.e) { + path = assetData.p; + } else if (assetsPath) { + var imagePath = assetData.p; + if (imagePath.indexOf('images/') !== -1) { + imagePath = imagePath.split('/')[1]; + } + path = assetsPath + imagePath; + } else { + path = originalPath; + path += assetData.u ? assetData.u : ''; + path += assetData.p; + } + return path; + } + + function testImageLoaded(img) { + var _count = 0; + var intervalId = setInterval(function () { + var box = img.getBBox(); + if (box.width || _count > 500) { + this._imageLoaded(); + clearInterval(intervalId); + } + _count += 1; + }.bind(this), 50); + } + + function createImageData(assetData) { + var path = getAssetsPath(assetData, this.assetsPath, this.path); + var img = createNS('image'); + if (isSafari) { + this.testImageLoaded(img); + } else { + img.addEventListener('load', this._imageLoaded, false); + } + img.addEventListener('error', function () { + ob.img = proxyImage; + this._imageLoaded(); + }.bind(this), false); + img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path); + if (this._elementHelper.append) { + this._elementHelper.append(img); + } else { + this._elementHelper.appendChild(img); + } + var ob = { + img: img, + assetData: assetData, + }; + return ob; + } + + function createImgData(assetData) { + var path = getAssetsPath(assetData, this.assetsPath, this.path); + var img = createTag('img'); + img.crossOrigin = 'anonymous'; + img.addEventListener('load', this._imageLoaded, false); + img.addEventListener('error', function () { + ob.img = proxyImage; + this._imageLoaded(); + }.bind(this), false); + img.src = path; + var ob = { + img: img, + assetData: assetData, + }; + return ob; + } + + function createFootageData(data) { + var ob = { + assetData: data, + }; + var path = getAssetsPath(data, this.assetsPath, this.path); + dataManager.loadData(path, function (footageData) { + ob.img = footageData; + this._footageLoaded(); + }.bind(this), function () { + ob.img = {}; + this._footageLoaded(); + }.bind(this)); + return ob; + } + + function loadAssets(assets, cb) { + this.imagesLoadedCb = cb; + var i; + var len = assets.length; + for (i = 0; i < len; i += 1) { + if (!assets[i].layers) { + if (!assets[i].t || assets[i].t === 'seq') { + this.totalImages += 1; + this.images.push(this._createImageData(assets[i])); + } else if (assets[i].t === 3) { + this.totalFootages += 1; + this.images.push(this.createFootageData(assets[i])); + } } + } + } - function completeAnimation(anim, onComplete, onError) { - setupWorker(); - var processId = createProcess(onComplete, onError); - workerInstance.postMessage({ - type: 'complete', - animation: anim, - id: processId, - }); - } + function setPath(path) { + this.path = path || ''; + } - return { - loadAnimation: loadAnimation, - loadData: loadData, - completeAnimation: completeAnimation, - }; - }()); - - const ImagePreloader = (function () { - var proxyImage = (function () { - var canvas = createTag('canvas'); - canvas.width = 1; - canvas.height = 1; - var ctx = canvas.getContext('2d'); - ctx.fillStyle = 'rgba(0,0,0,0)'; - ctx.fillRect(0, 0, 1, 1); - return canvas; - }()); - - function imageLoaded() { - this.loadedAssets += 1; - if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { - if (this.imagesLoadedCb) { - this.imagesLoadedCb(null); - } - } + function setAssetsPath(path) { + this.assetsPath = path || ''; + } + + function getAsset(assetData) { + var i = 0; + var len = this.images.length; + while (i < len) { + if (this.images[i].assetData === assetData) { + return this.images[i].img; } - function footageLoaded() { - this.loadedFootagesCount += 1; - if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) { - if (this.imagesLoadedCb) { - this.imagesLoadedCb(null); - } - } + i += 1; + } + return null; + } + + function destroy() { + this.imagesLoadedCb = null; + this.images.length = 0; + } + + function loadedImages() { + return this.totalImages === this.loadedAssets; + } + + function loadedFootages() { + return this.totalFootages === this.loadedFootagesCount; + } + + function setCacheType(type, elementHelper) { + if (type === 'svg') { + this._elementHelper = elementHelper; + this._createImageData = this.createImageData.bind(this); + } else { + this._createImageData = this.createImgData.bind(this); + } + } + + function ImagePreloaderFactory() { + this._imageLoaded = imageLoaded.bind(this); + this._footageLoaded = footageLoaded.bind(this); + this.testImageLoaded = testImageLoaded.bind(this); + this.createFootageData = createFootageData.bind(this); + this.assetsPath = ''; + this.path = ''; + this.totalImages = 0; + this.totalFootages = 0; + this.loadedAssets = 0; + this.loadedFootagesCount = 0; + this.imagesLoadedCb = null; + this.images = []; + } + + ImagePreloaderFactory.prototype = { + loadAssets: loadAssets, + setAssetsPath: setAssetsPath, + setPath: setPath, + loadedImages: loadedImages, + loadedFootages: loadedFootages, + destroy: destroy, + getAsset: getAsset, + createImgData: createImgData, + createImageData: createImageData, + imageLoaded: imageLoaded, + footageLoaded: footageLoaded, + setCacheType: setCacheType, + }; + + return ImagePreloaderFactory; +}()); + +function BaseEvent() {} +BaseEvent.prototype = { + triggerEvent: function (eventName, args) { + if (this._cbs[eventName]) { + var callbacks = this._cbs[eventName]; + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i](args); } - - function getAssetsPath(assetData, assetsPath, originalPath) { - var path = ''; - if (assetData.e) { - path = assetData.p; - } else if (assetsPath) { - var imagePath = assetData.p; - if (imagePath.indexOf('images/') !== -1) { - imagePath = imagePath.split('/')[1]; - } - path = assetsPath + imagePath; - } else { - path = originalPath; - path += assetData.u ? assetData.u : ''; - path += assetData.p; + } + }, + addEventListener: function (eventName, callback) { + if (!this._cbs[eventName]) { + this._cbs[eventName] = []; + } + this._cbs[eventName].push(callback); + + return function () { + this.removeEventListener(eventName, callback); + }.bind(this); + }, + removeEventListener: function (eventName, callback) { + if (!callback) { + this._cbs[eventName] = null; + } else if (this._cbs[eventName]) { + var i = 0; + var len = this._cbs[eventName].length; + while (i < len) { + if (this._cbs[eventName][i] === callback) { + this._cbs[eventName].splice(i, 1); + i -= 1; + len -= 1; } - return path; + i += 1; } - - function testImageLoaded(img) { - var _count = 0; - var intervalId = setInterval(function () { - var box = img.getBBox(); - if (box.width || _count > 500) { - this._imageLoaded(); - clearInterval(intervalId); - } - _count += 1; - }.bind(this), 50); + if (!this._cbs[eventName].length) { + this._cbs[eventName] = null; } + } + }, +}; - function createImageData(assetData) { - var path = getAssetsPath(assetData, this.assetsPath, this.path); - var img = createNS('image'); - if (isSafari) { - this.testImageLoaded(img); - } else { - img.addEventListener('load', this._imageLoaded, false); - } - img.addEventListener('error', function () { - ob.img = proxyImage; - this._imageLoaded(); - }.bind(this), false); - img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path); - if (this._elementHelper.append) { - this._elementHelper.append(img); - } else { - this._elementHelper.appendChild(img); +const markerParser = ( + + function () { + function parsePayloadLines(payload) { + var lines = payload.split('\r\n'); + var keys = {}; + var line; + var keysCount = 0; + for (var i = 0; i < lines.length; i += 1) { + line = lines[i].split(':'); + if (line.length === 2) { + keys[line[0]] = line[1].trim(); + keysCount += 1; } - var ob = { - img: img, - assetData: assetData, - }; - return ob; - } - - function createImgData(assetData) { - var path = getAssetsPath(assetData, this.assetsPath, this.path); - var img = createTag('img'); - img.crossOrigin = 'anonymous'; - img.addEventListener('load', this._imageLoaded, false); - img.addEventListener('error', function () { - ob.img = proxyImage; - this._imageLoaded(); - }.bind(this), false); - img.src = path; - var ob = { - img: img, - assetData: assetData, - }; - return ob; } + if (keysCount === 0) { + throw new Error(); + } + return keys; + } - function createFootageData(data) { - var ob = { - assetData: data, + return function (_markers) { + var markers = []; + for (var i = 0; i < _markers.length; i += 1) { + var _marker = _markers[i]; + var markerData = { + time: _marker.tm, + duration: _marker.dr, }; - var path = getAssetsPath(data, this.assetsPath, this.path); - dataManager.loadData(path, function (footageData) { - ob.img = footageData; - this._footageLoaded(); - }.bind(this), function () { - ob.img = {}; - this._footageLoaded(); - }.bind(this)); - return ob; - } - - function loadAssets(assets, cb) { - this.imagesLoadedCb = cb; - var i; - var len = assets.length; - for (i = 0; i < len; i += 1) { - if (!assets[i].layers) { - if (!assets[i].t || assets[i].t === 'seq') { - this.totalImages += 1; - this.images.push(this._createImageData(assets[i])); - } else if (assets[i].t === 3) { - this.totalFootages += 1; - this.images.push(this.createFootageData(assets[i])); - } + try { + markerData.payload = JSON.parse(_markers[i].cm); + } catch (_) { + try { + markerData.payload = parsePayloadLines(_markers[i].cm); + } catch (__) { + markerData.payload = { + name: _markers[i].cm, + }; } } + markers.push(markerData); } + return markers; + }; + }()); - function setPath(path) { - this.path = path || ''; - } - - function setAssetsPath(path) { - this.assetsPath = path || ''; - } +const ProjectInterface = (function () { + function registerComposition(comp) { + this.compositions.push(comp); + } - function getAsset(assetData) { - var i = 0; - var len = this.images.length; - while (i < len) { - if (this.images[i].assetData === assetData) { - return this.images[i].img; + return function () { + function _thisProjectFunction(name) { + var i = 0; + var len = this.compositions.length; + while (i < len) { + if (this.compositions[i].data && this.compositions[i].data.nm === name) { + if (this.compositions[i].prepareFrame && this.compositions[i].data.xt) { + this.compositions[i].prepareFrame(this.currentFrame); } - i += 1; + return this.compositions[i].compInterface; } - return null; + i += 1; } + return null; + } - function destroy() { - this.imagesLoadedCb = null; - this.images.length = 0; - } + _thisProjectFunction.compositions = []; + _thisProjectFunction.currentFrame = 0; + + _thisProjectFunction.registerComposition = registerComposition; + + return _thisProjectFunction; + }; +}()); + +const renderers = {}; + +const registerRenderer = (key, value) => { + renderers[key] = value; +}; + +function getRenderer(key) { + return renderers[key]; +} + +const AnimationItem = function () { + this._cbs = []; + this.name = ''; + this.path = ''; + this.isLoaded = false; + this.currentFrame = 0; + this.currentRawFrame = 0; + this.firstFrame = 0; + this.totalFrames = 0; + this.frameRate = 0; + this.frameMult = 0; + this.playSpeed = 1; + this.playDirection = 1; + this.playCount = 0; + this.animationData = {}; + this.assets = []; + this.isPaused = true; + this.autoplay = false; + this.loop = true; + this.renderer = null; + this.animationID = createElementID(); + this.assetsPath = ''; + this.timeCompleted = 0; + this.segmentPos = 0; + this.isSubframeEnabled = getSubframeEnabled(); + this.segments = []; + this._idle = true; + this._completedLoop = false; + this.projectInterface = ProjectInterface(); + this.imagePreloader = new ImagePreloader(); + this.audioController = audioControllerFactory(); + this.markers = []; + this.configAnimation = this.configAnimation.bind(this); + this.onSetupError = this.onSetupError.bind(this); + this.onSegmentComplete = this.onSegmentComplete.bind(this); + this.drawnFrameEvent = new BMEnterFrameEvent('drawnFrame', 0, 0, 0); +}; + +extendPrototype([BaseEvent], AnimationItem); + +AnimationItem.prototype.setParams = function (params) { + if (params.wrapper || params.container) { + this.wrapper = params.wrapper || params.container; + } + var animType = 'svg'; + if (params.animType) { + animType = params.animType; + } else if (params.renderer) { + animType = params.renderer; + } + const RendererClass = getRenderer(animType); + this.renderer = new RendererClass(this, params.rendererSettings); + this.imagePreloader.setCacheType(animType, this.renderer.globalData.defs); + this.renderer.setProjectInterface(this.projectInterface); + this.animType = animType; + if (params.loop === '' + || params.loop === null + || params.loop === undefined + || params.loop === true) { + this.loop = true; + } else if (params.loop === false) { + this.loop = false; + } else { + this.loop = parseInt(params.loop, 10); + } + this.autoplay = 'autoplay' in params ? params.autoplay : true; + this.name = params.name ? params.name : ''; + this.autoloadSegments = Object.prototype.hasOwnProperty.call(params, 'autoloadSegments') ? params.autoloadSegments : true; + this.assetsPath = params.assetsPath; + this.initialSegment = params.initialSegment; + if (params.audioFactory) { + this.audioController.setAudioFactory(params.audioFactory); + } + if (params.animationData) { + this.setupAnimation(params.animationData); + } else if (params.path) { + if (params.path.lastIndexOf('\\') !== -1) { + this.path = params.path.substr(0, params.path.lastIndexOf('\\') + 1); + } else { + this.path = params.path.substr(0, params.path.lastIndexOf('/') + 1); + } + this.fileName = params.path.substr(params.path.lastIndexOf('/') + 1); + this.fileName = this.fileName.substr(0, this.fileName.lastIndexOf('.json')); + dataManager.loadAnimation( + params.path, + this.configAnimation, + this.onSetupError + ); + } +}; + +AnimationItem.prototype.onSetupError = function () { + this.trigger('data_failed'); +}; + +AnimationItem.prototype.setupAnimation = function (data) { + dataManager.completeAnimation( + data, + this.configAnimation + ); +}; + +AnimationItem.prototype.setData = function (wrapper, animationData) { + if (animationData) { + if (typeof animationData !== 'object') { + animationData = JSON.parse(animationData); + } + } + var params = { + wrapper: wrapper, + animationData: animationData, + }; + var wrapperAttributes = wrapper.attributes; + + params.path = wrapperAttributes.getNamedItem('data-animation-path') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-animation-path').value + : wrapperAttributes.getNamedItem('data-bm-path') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-path').value + : wrapperAttributes.getNamedItem('bm-path') + ? wrapperAttributes.getNamedItem('bm-path').value + : ''; + params.animType = wrapperAttributes.getNamedItem('data-anim-type') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-type').value + : wrapperAttributes.getNamedItem('data-bm-type') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-type').value + : wrapperAttributes.getNamedItem('bm-type') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('bm-type').value + : wrapperAttributes.getNamedItem('data-bm-renderer') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-renderer').value + : wrapperAttributes.getNamedItem('bm-renderer') + ? wrapperAttributes.getNamedItem('bm-renderer').value + : 'canvas'; + + var loop = wrapperAttributes.getNamedItem('data-anim-loop') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-loop').value + : wrapperAttributes.getNamedItem('data-bm-loop') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-loop').value + : wrapperAttributes.getNamedItem('bm-loop') + ? wrapperAttributes.getNamedItem('bm-loop').value + : ''; + if (loop === 'false') { + params.loop = false; + } else if (loop === 'true') { + params.loop = true; + } else if (loop !== '') { + params.loop = parseInt(loop, 10); + } + var autoplay = wrapperAttributes.getNamedItem('data-anim-autoplay') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-autoplay').value + : wrapperAttributes.getNamedItem('data-bm-autoplay') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-autoplay').value + : wrapperAttributes.getNamedItem('bm-autoplay') + ? wrapperAttributes.getNamedItem('bm-autoplay').value + : true; + params.autoplay = autoplay !== 'false'; + + params.name = wrapperAttributes.getNamedItem('data-name') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-name').value + : wrapperAttributes.getNamedItem('data-bm-name') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-name').value + : wrapperAttributes.getNamedItem('bm-name') + ? wrapperAttributes.getNamedItem('bm-name').value + : ''; + var prerender = wrapperAttributes.getNamedItem('data-anim-prerender') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-anim-prerender').value + : wrapperAttributes.getNamedItem('data-bm-prerender') // eslint-disable-line no-nested-ternary + ? wrapperAttributes.getNamedItem('data-bm-prerender').value + : wrapperAttributes.getNamedItem('bm-prerender') + ? wrapperAttributes.getNamedItem('bm-prerender').value + : ''; + + if (prerender === 'false') { + params.prerender = false; + } + this.setParams(params); +}; + +AnimationItem.prototype.includeLayers = function (data) { + if (data.op > this.animationData.op) { + this.animationData.op = data.op; + this.totalFrames = Math.floor(data.op - this.animationData.ip); + } + var layers = this.animationData.layers; + var i; + var len = layers.length; + var newLayers = data.layers; + var j; + var jLen = newLayers.length; + for (j = 0; j < jLen; j += 1) { + i = 0; + while (i < len) { + if (layers[i].id === newLayers[j].id) { + layers[i] = newLayers[j]; + break; + } + i += 1; + } + } + if (data.chars || data.fonts) { + this.renderer.globalData.fontManager.addChars(data.chars); + this.renderer.globalData.fontManager.addFonts(data.fonts, this.renderer.globalData.defs); + } + if (data.assets) { + len = data.assets.length; + for (i = 0; i < len; i += 1) { + this.animationData.assets.push(data.assets[i]); + } + } + this.animationData.__complete = false; + dataManager.completeAnimation( + this.animationData, + this.onSegmentComplete + ); +}; + +AnimationItem.prototype.onSegmentComplete = function (data) { + this.animationData = data; + var expressionsPlugin = getExpressionsPlugin(); + if (expressionsPlugin) { + expressionsPlugin.initExpressions(this); + } + this.loadNextSegment(); +}; + +AnimationItem.prototype.loadNextSegment = function () { + var segments = this.animationData.segments; + if (!segments || segments.length === 0 || !this.autoloadSegments) { + this.trigger('data_ready'); + this.timeCompleted = this.totalFrames; + return; + } + var segment = segments.shift(); + this.timeCompleted = segment.time * this.frameRate; + var segmentPath = this.path + this.fileName + '_' + this.segmentPos + '.json'; + this.segmentPos += 1; + dataManager.loadData(segmentPath, this.includeLayers.bind(this), function () { + this.trigger('data_failed'); + }.bind(this)); +}; + +AnimationItem.prototype.loadSegments = function () { + var segments = this.animationData.segments; + if (!segments) { + this.timeCompleted = this.totalFrames; + } + this.loadNextSegment(); +}; + +AnimationItem.prototype.imagesLoaded = function () { + this.trigger('loaded_images'); + this.checkLoaded(); +}; + +AnimationItem.prototype.preloadImages = function () { + this.imagePreloader.setAssetsPath(this.assetsPath); + this.imagePreloader.setPath(this.path); + this.imagePreloader.loadAssets(this.animationData.assets, this.imagesLoaded.bind(this)); +}; + +AnimationItem.prototype.configAnimation = function (animData) { + if (!this.renderer) { + return; + } + try { + this.animationData = animData; + if (this.initialSegment) { + this.totalFrames = Math.floor(this.initialSegment[1] - this.initialSegment[0]); + this.firstFrame = Math.round(this.initialSegment[0]); + } else { + this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip); + this.firstFrame = Math.round(this.animationData.ip); + } + this.renderer.configAnimation(animData); + if (!animData.assets) { + animData.assets = []; + } - function loadedImages() { - return this.totalImages === this.loadedAssets; + this.assets = this.animationData.assets; + this.frameRate = this.animationData.fr; + this.frameMult = this.animationData.fr / 1000; + this.renderer.searchExtraCompositions(animData.assets); + this.markers = markerParser(animData.markers || []); + this.trigger('config_ready'); + this.preloadImages(); + this.loadSegments(); + this.updaFrameModifier(); + this.waitForFontsLoaded(); + if (this.isPaused) { + this.audioController.pause(); + } + } catch (error) { + this.triggerConfigError(error); + } +}; + +AnimationItem.prototype.waitForFontsLoaded = function () { + if (!this.renderer) { + return; + } + if (this.renderer.globalData.fontManager.isLoaded) { + this.checkLoaded(); + } else { + setTimeout(this.waitForFontsLoaded.bind(this), 20); + } +}; + +AnimationItem.prototype.checkLoaded = function () { + if (!this.isLoaded + && this.renderer.globalData.fontManager.isLoaded + && (this.imagePreloader.loadedImages() || this.renderer.rendererType !== 'canvas') + && (this.imagePreloader.loadedFootages()) + ) { + this.isLoaded = true; + var expressionsPlugin = getExpressionsPlugin(); + if (expressionsPlugin) { + expressionsPlugin.initExpressions(this); + } + this.renderer.initItems(); + setTimeout(function () { + this.trigger('DOMLoaded'); + }.bind(this), 0); + this.gotoFrame(); + if (this.autoplay) { + this.play(); + } + } +}; + +AnimationItem.prototype.resize = function () { + this.renderer.updateContainerSize(); +}; + +AnimationItem.prototype.setSubframe = function (flag) { + this.isSubframeEnabled = !!flag; +}; + +AnimationItem.prototype.gotoFrame = function () { + this.currentFrame = this.isSubframeEnabled ? this.currentRawFrame : ~~this.currentRawFrame; // eslint-disable-line no-bitwise + + if (this.timeCompleted !== this.totalFrames && this.currentFrame > this.timeCompleted) { + this.currentFrame = this.timeCompleted; + } + this.trigger('enterFrame'); + this.renderFrame(); + this.trigger('drawnFrame'); +}; + +AnimationItem.prototype.renderFrame = function () { + if (this.isLoaded === false || !this.renderer) { + return; + } + try { + this.renderer.renderFrame(this.currentFrame + this.firstFrame); + } catch (error) { + this.triggerRenderFrameError(error); + } +}; + +AnimationItem.prototype.play = function (name) { + if (name && this.name !== name) { + return; + } + if (this.isPaused === true) { + this.isPaused = false; + this.trigger('_pause'); + this.audioController.resume(); + if (this._idle) { + this._idle = false; + this.trigger('_active'); + } + } +}; + +AnimationItem.prototype.pause = function (name) { + if (name && this.name !== name) { + return; + } + if (this.isPaused === false) { + this.isPaused = true; + this.trigger('_play'); + this._idle = true; + this.trigger('_idle'); + this.audioController.pause(); + } +}; + +AnimationItem.prototype.togglePause = function (name) { + if (name && this.name !== name) { + return; + } + if (this.isPaused === true) { + this.play(); + } else { + this.pause(); + } +}; + +AnimationItem.prototype.stop = function (name) { + if (name && this.name !== name) { + return; + } + this.pause(); + this.playCount = 0; + this._completedLoop = false; + this.setCurrentRawFrameValue(0); +}; + +AnimationItem.prototype.getMarkerData = function (markerName) { + var marker; + for (var i = 0; i < this.markers.length; i += 1) { + marker = this.markers[i]; + if (marker.payload && marker.payload.name === markerName) { + return marker; + } + } + return null; +}; + +AnimationItem.prototype.goToAndStop = function (value, isFrame, name) { + if (name && this.name !== name) { + return; + } + var numValue = Number(value); + if (isNaN(numValue)) { + var marker = this.getMarkerData(value); + if (marker) { + this.goToAndStop(marker.time, true); + } + } else if (isFrame) { + this.setCurrentRawFrameValue(value); + } else { + this.setCurrentRawFrameValue(value * this.frameModifier); + } + this.pause(); +}; + +AnimationItem.prototype.goToAndPlay = function (value, isFrame, name) { + if (name && this.name !== name) { + return; + } + var numValue = Number(value); + if (isNaN(numValue)) { + var marker = this.getMarkerData(value); + if (marker) { + if (!marker.duration) { + this.goToAndStop(marker.time, true); + } else { + this.playSegments([marker.time, marker.time + marker.duration], true); + } + } + } else { + this.goToAndStop(numValue, isFrame, name); + } + this.play(); +}; + +AnimationItem.prototype.advanceTime = function (value) { + if (this.isPaused === true || this.isLoaded === false) { + return; + } + var nextValue = this.currentRawFrame + value * this.frameModifier; + var _isComplete = false; + // Checking if nextValue > totalFrames - 1 for addressing non looping and looping animations. + // If animation won't loop, it should stop at totalFrames - 1. If it will loop it should complete the last frame and then loop. + if (nextValue >= this.totalFrames - 1 && this.frameModifier > 0) { + if (!this.loop || this.playCount === this.loop) { + if (!this.checkSegments(nextValue > this.totalFrames ? nextValue % this.totalFrames : 0)) { + _isComplete = true; + nextValue = this.totalFrames - 1; + } + } else if (nextValue >= this.totalFrames) { + this.playCount += 1; + if (!this.checkSegments(nextValue % this.totalFrames)) { + this.setCurrentRawFrameValue(nextValue % this.totalFrames); + this._completedLoop = true; + this.trigger('loopComplete'); + } + } else { + this.setCurrentRawFrameValue(nextValue); + } + } else if (nextValue < 0) { + if (!this.checkSegments(nextValue % this.totalFrames)) { + if (this.loop && !(this.playCount-- <= 0 && this.loop !== true)) { // eslint-disable-line no-plusplus + this.setCurrentRawFrameValue(this.totalFrames + (nextValue % this.totalFrames)); + if (!this._completedLoop) { + this._completedLoop = true; + } else { + this.trigger('loopComplete'); + } + } else { + _isComplete = true; + nextValue = 0; + } + } + } else { + this.setCurrentRawFrameValue(nextValue); + } + if (_isComplete) { + this.setCurrentRawFrameValue(nextValue); + this.pause(); + this.trigger('complete'); + } +}; + +AnimationItem.prototype.adjustSegment = function (arr, offset) { + this.playCount = 0; + if (arr[1] < arr[0]) { + if (this.frameModifier > 0) { + if (this.playSpeed < 0) { + this.setSpeed(-this.playSpeed); + } else { + this.setDirection(-1); + } + } + this.totalFrames = arr[0] - arr[1]; + this.timeCompleted = this.totalFrames; + this.firstFrame = arr[1]; + this.setCurrentRawFrameValue(this.totalFrames - 0.001 - offset); + } else if (arr[1] > arr[0]) { + if (this.frameModifier < 0) { + if (this.playSpeed < 0) { + this.setSpeed(-this.playSpeed); + } else { + this.setDirection(1); } + } + this.totalFrames = arr[1] - arr[0]; + this.timeCompleted = this.totalFrames; + this.firstFrame = arr[0]; + this.setCurrentRawFrameValue(0.001 + offset); + } + this.trigger('segmentStart'); +}; +AnimationItem.prototype.setSegment = function (init, end) { + var pendingFrame = -1; + if (this.isPaused) { + if (this.currentRawFrame + this.firstFrame < init) { + pendingFrame = init; + } else if (this.currentRawFrame + this.firstFrame > end) { + pendingFrame = end - init; + } + } + + this.firstFrame = init; + this.totalFrames = end - init; + this.timeCompleted = this.totalFrames; + if (pendingFrame !== -1) { + this.goToAndStop(pendingFrame, true); + } +}; + +AnimationItem.prototype.playSegments = function (arr, forceFlag) { + if (forceFlag) { + this.segments.length = 0; + } + if (typeof arr[0] === 'object') { + var i; + var len = arr.length; + for (i = 0; i < len; i += 1) { + this.segments.push(arr[i]); + } + } else { + this.segments.push(arr); + } + if (this.segments.length && forceFlag) { + this.adjustSegment(this.segments.shift(), 0); + } + if (this.isPaused) { + this.play(); + } +}; + +AnimationItem.prototype.resetSegments = function (forceFlag) { + this.segments.length = 0; + this.segments.push([this.animationData.ip, this.animationData.op]); + if (forceFlag) { + this.checkSegments(0); + } +}; +AnimationItem.prototype.checkSegments = function (offset) { + if (this.segments.length) { + this.adjustSegment(this.segments.shift(), offset); + return true; + } + return false; +}; + +AnimationItem.prototype.destroy = function (name) { + if ((name && this.name !== name) || !this.renderer) { + return; + } + this.renderer.destroy(); + this.imagePreloader.destroy(); + this.trigger('destroy'); + this._cbs = null; + this.onEnterFrame = null; + this.onLoopComplete = null; + this.onComplete = null; + this.onSegmentStart = null; + this.onDestroy = null; + this.renderer = null; + this.renderer = null; + this.imagePreloader = null; + this.projectInterface = null; +}; + +AnimationItem.prototype.setCurrentRawFrameValue = function (value) { + this.currentRawFrame = value; + this.gotoFrame(); +}; + +AnimationItem.prototype.setSpeed = function (val) { + this.playSpeed = val; + this.updaFrameModifier(); +}; + +AnimationItem.prototype.setDirection = function (val) { + this.playDirection = val < 0 ? -1 : 1; + this.updaFrameModifier(); +}; + +AnimationItem.prototype.setVolume = function (val, name) { + if (name && this.name !== name) { + return; + } + this.audioController.setVolume(val); +}; + +AnimationItem.prototype.getVolume = function () { + return this.audioController.getVolume(); +}; + +AnimationItem.prototype.mute = function (name) { + if (name && this.name !== name) { + return; + } + this.audioController.mute(); +}; + +AnimationItem.prototype.unmute = function (name) { + if (name && this.name !== name) { + return; + } + this.audioController.unmute(); +}; + +AnimationItem.prototype.updaFrameModifier = function () { + this.frameModifier = this.frameMult * this.playSpeed * this.playDirection; + this.audioController.setRate(this.playSpeed * this.playDirection); +}; + +AnimationItem.prototype.getPath = function () { + return this.path; +}; + +AnimationItem.prototype.getAssetsPath = function (assetData) { + var path = ''; + if (assetData.e) { + path = assetData.p; + } else if (this.assetsPath) { + var imagePath = assetData.p; + if (imagePath.indexOf('images/') !== -1) { + imagePath = imagePath.split('/')[1]; + } + path = this.assetsPath + imagePath; + } else { + path = this.path; + path += assetData.u ? assetData.u : ''; + path += assetData.p; + } + return path; +}; + +AnimationItem.prototype.getAssetData = function (id) { + var i = 0; + var len = this.assets.length; + while (i < len) { + if (id === this.assets[i].id) { + return this.assets[i]; + } + i += 1; + } + return null; +}; + +AnimationItem.prototype.hide = function () { + this.renderer.hide(); +}; + +AnimationItem.prototype.show = function () { + this.renderer.show(); +}; + +AnimationItem.prototype.getDuration = function (isFrame) { + return isFrame ? this.totalFrames : this.totalFrames / this.frameRate; +}; + +AnimationItem.prototype.updateDocumentData = function (path, documentData, index) { + try { + var element = this.renderer.getElementByPath(path); + element.updateDocumentData(documentData, index); + } catch (error) { + // TODO: decide how to handle catch case + } +}; + +AnimationItem.prototype.trigger = function (name) { + if (this._cbs && this._cbs[name]) { + switch (name) { + case 'enterFrame': + this.triggerEvent(name, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameModifier)); + break; + case 'drawnFrame': + this.drawnFrameEvent.currentTime = this.currentFrame; + this.drawnFrameEvent.totalTime = this.totalFrames; + this.drawnFrameEvent.direction = this.frameModifier; + this.triggerEvent(name, this.drawnFrameEvent); + break; + case 'loopComplete': + this.triggerEvent(name, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); + break; + case 'complete': + this.triggerEvent(name, new BMCompleteEvent(name, this.frameMult)); + break; + case 'segmentStart': + this.triggerEvent(name, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); + break; + case 'destroy': + this.triggerEvent(name, new BMDestroyEvent(name, this)); + break; + default: + this.triggerEvent(name); + } + } + if (name === 'enterFrame' && this.onEnterFrame) { + this.onEnterFrame.call(this, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameMult)); + } + if (name === 'loopComplete' && this.onLoopComplete) { + this.onLoopComplete.call(this, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); + } + if (name === 'complete' && this.onComplete) { + this.onComplete.call(this, new BMCompleteEvent(name, this.frameMult)); + } + if (name === 'segmentStart' && this.onSegmentStart) { + this.onSegmentStart.call(this, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); + } + if (name === 'destroy' && this.onDestroy) { + this.onDestroy.call(this, new BMDestroyEvent(name, this)); + } +}; + +AnimationItem.prototype.triggerRenderFrameError = function (nativeError) { + var error = new BMRenderFrameErrorEvent(nativeError, this.currentFrame); + this.triggerEvent('error', error); + + if (this.onError) { + this.onError.call(this, error); + } +}; + +AnimationItem.prototype.triggerConfigError = function (nativeError) { + var error = new BMConfigErrorEvent(nativeError, this.currentFrame); + this.triggerEvent('error', error); + + if (this.onError) { + this.onError.call(this, error); + } +}; + +const animationManager = (function () { + var moduleOb = {}; + var registeredAnimations = []; + var initTime = 0; + var len = 0; + var playingAnimationsNum = 0; + var _stopped = true; + var _isFrozen = false; + + function removeElement(ev) { + var i = 0; + var animItem = ev.target; + while (i < len) { + if (registeredAnimations[i].animation === animItem) { + registeredAnimations.splice(i, 1); + i -= 1; + len -= 1; + if (!animItem.isPaused) { + subtractPlayingCount(); + } + } + i += 1; + } + } - function loadedFootages() { - return this.totalFootages === this.loadedFootagesCount; + function registerAnimation(element, animationData) { + if (!element) { + return null; + } + var i = 0; + while (i < len) { + if (registeredAnimations[i].elem === element && registeredAnimations[i].elem !== null) { + return registeredAnimations[i].animation; } + i += 1; + } + var animItem = new AnimationItem(); + setupAnimation(animItem, element); + animItem.setData(element, animationData); + return animItem; + } + + function getRegisteredAnimations() { + var i; + var lenAnims = registeredAnimations.length; + var animations = []; + for (i = 0; i < lenAnims; i += 1) { + animations.push(registeredAnimations[i].animation); + } + return animations; + } + + function addPlayingCount() { + playingAnimationsNum += 1; + activate(); + } + + function subtractPlayingCount() { + playingAnimationsNum -= 1; + } + + function setupAnimation(animItem, element) { + animItem.addEventListener('destroy', removeElement); + animItem.addEventListener('_active', addPlayingCount); + animItem.addEventListener('_idle', subtractPlayingCount); + registeredAnimations.push({ elem: element, animation: animItem }); + len += 1; + } + + function loadAnimation(params) { + var animItem = new AnimationItem(); + setupAnimation(animItem, null); + animItem.setParams(params); + return animItem; + } + + function setSpeed(val, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.setSpeed(val, animation); + } + } - function setCacheType(type, elementHelper) { - if (type === 'svg') { - this._elementHelper = elementHelper; - this._createImageData = this.createImageData.bind(this); - } else { - this._createImageData = this.createImgData.bind(this); - } - } - - function ImagePreloaderFactory() { - this._imageLoaded = imageLoaded.bind(this); - this._footageLoaded = footageLoaded.bind(this); - this.testImageLoaded = testImageLoaded.bind(this); - this.createFootageData = createFootageData.bind(this); - this.assetsPath = ''; - this.path = ''; - this.totalImages = 0; - this.totalFootages = 0; - this.loadedAssets = 0; - this.loadedFootagesCount = 0; - this.imagesLoadedCb = null; - this.images = []; - } - - ImagePreloaderFactory.prototype = { - loadAssets: loadAssets, - setAssetsPath: setAssetsPath, - setPath: setPath, - loadedImages: loadedImages, - loadedFootages: loadedFootages, - destroy: destroy, - getAsset: getAsset, - createImgData: createImgData, - createImageData: createImageData, - imageLoaded: imageLoaded, - footageLoaded: footageLoaded, - setCacheType: setCacheType, - }; + function setDirection(val, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.setDirection(val, animation); + } + } - return ImagePreloaderFactory; - }()); + function play(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.play(animation); + } + } + function resume(nowTime) { + var elapsedTime = nowTime - initTime; + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.advanceTime(elapsedTime); + } + initTime = nowTime; + if (playingAnimationsNum && !_isFrozen) { + window.requestAnimationFrame(resume); + } else { + _stopped = true; + } + } - function BaseEvent() {} - BaseEvent.prototype = { - triggerEvent: function (eventName, args) { - if (this._cbs[eventName]) { - var callbacks = this._cbs[eventName]; - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i](args); - } - } - }, - addEventListener: function (eventName, callback) { - if (!this._cbs[eventName]) { - this._cbs[eventName] = []; - } - this._cbs[eventName].push(callback); + function first(nowTime) { + initTime = nowTime; + window.requestAnimationFrame(resume); + } - return function () { - this.removeEventListener(eventName, callback); - }.bind(this); - }, - removeEventListener: function (eventName, callback) { - if (!callback) { - this._cbs[eventName] = null; - } else if (this._cbs[eventName]) { - var i = 0; - var len = this._cbs[eventName].length; - while (i < len) { - if (this._cbs[eventName][i] === callback) { - this._cbs[eventName].splice(i, 1); - i -= 1; - len -= 1; - } - i += 1; - } - if (!this._cbs[eventName].length) { - this._cbs[eventName] = null; - } - } - }, - }; + function pause(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.pause(animation); + } + } - const markerParser = ( - - function () { - function parsePayloadLines(payload) { - var lines = payload.split('\r\n'); - var keys = {}; - var line; - var keysCount = 0; - for (var i = 0; i < lines.length; i += 1) { - line = lines[i].split(':'); - if (line.length === 2) { - keys[line[0]] = line[1].trim(); - keysCount += 1; - } - } - if (keysCount === 0) { - throw new Error(); - } - return keys; - } + function goToAndStop(value, isFrame, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.goToAndStop(value, isFrame, animation); + } + } - return function (_markers) { - var markers = []; - for (var i = 0; i < _markers.length; i += 1) { - var _marker = _markers[i]; - var markerData = { - time: _marker.tm, - duration: _marker.dr, - }; - try { - markerData.payload = JSON.parse(_markers[i].cm); - } catch (_) { - try { - markerData.payload = parsePayloadLines(_markers[i].cm); - } catch (__) { - markerData.payload = { - name: _markers[i].cm, - }; - } - } - markers.push(markerData); - } - return markers; - }; - }()); + function stop(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.stop(animation); + } + } - const ProjectInterface = (function () { - function registerComposition(comp) { - this.compositions.push(comp); - } + function togglePause(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.togglePause(animation); + } + } - return function () { - function _thisProjectFunction(name) { - var i = 0; - var len = this.compositions.length; - while (i < len) { - if (this.compositions[i].data && this.compositions[i].data.nm === name) { - if (this.compositions[i].prepareFrame && this.compositions[i].data.xt) { - this.compositions[i].prepareFrame(this.currentFrame); - } - return this.compositions[i].compInterface; - } - i += 1; - } - return null; - } + function destroy(animation) { + var i; + for (i = (len - 1); i >= 0; i -= 1) { + registeredAnimations[i].animation.destroy(animation); + } + } + + function searchAnimations(animationData, standalone, renderer) { + var animElements = [].concat([].slice.call(document.getElementsByClassName('lottie')), + [].slice.call(document.getElementsByClassName('bodymovin'))); + var i; + var lenAnims = animElements.length; + for (i = 0; i < lenAnims; i += 1) { + if (renderer) { + animElements[i].setAttribute('data-bm-type', renderer); + } + registerAnimation(animElements[i], animationData); + } + if (standalone && lenAnims === 0) { + if (!renderer) { + renderer = 'svg'; + } + var body = document.getElementsByTagName('body')[0]; + body.innerText = ''; + var div = createTag('div'); + div.style.width = '100%'; + div.style.height = '100%'; + div.setAttribute('data-bm-type', renderer); + body.appendChild(div); + registerAnimation(div, animationData); + } + } - _thisProjectFunction.compositions = []; - _thisProjectFunction.currentFrame = 0; + function resize() { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.resize(); + } + } - _thisProjectFunction.registerComposition = registerComposition; + function activate() { + if (!_isFrozen && playingAnimationsNum) { + if (_stopped) { + window.requestAnimationFrame(first); + _stopped = false; + } + } + } - return _thisProjectFunction; - }; - }()); + function freeze() { + _isFrozen = true; + } - const renderers = {}; + function unfreeze() { + _isFrozen = false; + activate(); + } - const registerRenderer = (key, value) => { - renderers[key] = value; - }; + function setVolume(val, animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.setVolume(val, animation); + } + } - function getRenderer(key) { - return renderers[key]; - } - - const AnimationItem = function () { - this._cbs = []; - this.name = ''; - this.path = ''; - this.isLoaded = false; - this.currentFrame = 0; - this.currentRawFrame = 0; - this.firstFrame = 0; - this.totalFrames = 0; - this.frameRate = 0; - this.frameMult = 0; - this.playSpeed = 1; - this.playDirection = 1; - this.playCount = 0; - this.animationData = {}; - this.assets = []; - this.isPaused = true; - this.autoplay = false; - this.loop = true; - this.renderer = null; - this.animationID = createElementID(); - this.assetsPath = ''; - this.timeCompleted = 0; - this.segmentPos = 0; - this.isSubframeEnabled = getSubframeEnabled(); - this.segments = []; - this._idle = true; - this._completedLoop = false; - this.projectInterface = ProjectInterface(); - this.imagePreloader = new ImagePreloader(); - this.audioController = audioControllerFactory(); - this.markers = []; - this.configAnimation = this.configAnimation.bind(this); - this.onSetupError = this.onSetupError.bind(this); - this.onSegmentComplete = this.onSegmentComplete.bind(this); - this.drawnFrameEvent = new BMEnterFrameEvent('drawnFrame', 0, 0, 0); - }; + function mute(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.mute(animation); + } + } - extendPrototype([BaseEvent], AnimationItem); - - AnimationItem.prototype.setParams = function (params) { - if (params.wrapper || params.container) { - this.wrapper = params.wrapper || params.container; - } - var animType = 'svg'; - if (params.animType) { - animType = params.animType; - } else if (params.renderer) { - animType = params.renderer; - } - const RendererClass = getRenderer(animType); - this.renderer = new RendererClass(this, params.rendererSettings); - this.imagePreloader.setCacheType(animType, this.renderer.globalData.defs); - this.renderer.setProjectInterface(this.projectInterface); - this.animType = animType; - if (params.loop === '' - || params.loop === null - || params.loop === undefined - || params.loop === true) { - this.loop = true; - } else if (params.loop === false) { - this.loop = false; + function unmute(animation) { + var i; + for (i = 0; i < len; i += 1) { + registeredAnimations[i].animation.unmute(animation); + } + } + + moduleOb.registerAnimation = registerAnimation; + moduleOb.loadAnimation = loadAnimation; + moduleOb.setSpeed = setSpeed; + moduleOb.setDirection = setDirection; + moduleOb.play = play; + moduleOb.pause = pause; + moduleOb.stop = stop; + moduleOb.togglePause = togglePause; + moduleOb.searchAnimations = searchAnimations; + moduleOb.resize = resize; + // moduleOb.start = start; + moduleOb.goToAndStop = goToAndStop; + moduleOb.destroy = destroy; + moduleOb.freeze = freeze; + moduleOb.unfreeze = unfreeze; + moduleOb.setVolume = setVolume; + moduleOb.mute = mute; + moduleOb.unmute = unmute; + moduleOb.getRegisteredAnimations = getRegisteredAnimations; + return moduleOb; +}()); + +/* eslint-disable */ +const BezierFactory = (function () { + /** + * BezierEasing - use bezier curve for transition easing function + * by Gaëtan Renaudeau 2014 - 2015 – MIT License + * + * Credits: is based on Firefox's nsSMILKeySpline.cpp + * Usage: + * var spline = BezierEasing([ 0.25, 0.1, 0.25, 1.0 ]) + * spline.get(x) => returns the easing value | x must be in [0, 1] range + * + */ + + var ob = {}; + ob.getBezierEasing = getBezierEasing; + var beziers = {}; + + function getBezierEasing(a, b, c, d, nm) { + var str = nm || ('bez_' + a + '_' + b + '_' + c + '_' + d).replace(/\./g, 'p'); + if (beziers[str]) { + return beziers[str]; + } + var bezEasing = new BezierEasing([a, b, c, d]); + beziers[str] = bezEasing; + return bezEasing; + } + + // These values are established by empiricism with tests (tradeoff: performance VS precision) + var NEWTON_ITERATIONS = 4; + var NEWTON_MIN_SLOPE = 0.001; + var SUBDIVISION_PRECISION = 0.0000001; + var SUBDIVISION_MAX_ITERATIONS = 10; + + var kSplineTableSize = 11; + var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); + + var float32ArraySupported = typeof Float32Array === 'function'; + + function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } + function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } + function C(aA1) { return 3.0 * aA1; } + + // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. + function calcBezier(aT, aA1, aA2) { + return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; + } + + // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. + function getSlope(aT, aA1, aA2) { + return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); + } + + function binarySubdivide(aX, aA, aB, mX1, mX2) { + var currentX, + currentT, + i = 0; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { + aB = currentT; } else { - this.loop = parseInt(params.loop, 10); - } - this.autoplay = 'autoplay' in params ? params.autoplay : true; - this.name = params.name ? params.name : ''; - this.autoloadSegments = Object.prototype.hasOwnProperty.call(params, 'autoloadSegments') ? params.autoloadSegments : true; - this.assetsPath = params.assetsPath; - this.initialSegment = params.initialSegment; - if (params.audioFactory) { - this.audioController.setAudioFactory(params.audioFactory); - } - if (params.animationData) { - this.setupAnimation(params.animationData); - } else if (params.path) { - if (params.path.lastIndexOf('\\') !== -1) { - this.path = params.path.substr(0, params.path.lastIndexOf('\\') + 1); - } else { - this.path = params.path.substr(0, params.path.lastIndexOf('/') + 1); - } - this.fileName = params.path.substr(params.path.lastIndexOf('/') + 1); - this.fileName = this.fileName.substr(0, this.fileName.lastIndexOf('.json')); - dataManager.loadAnimation( - params.path, - this.configAnimation, - this.onSetupError - ); - } - }; + aA = currentT; + } + } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); + return currentT; + } + + function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { + for (var i = 0; i < NEWTON_ITERATIONS; ++i) { + var currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) return aGuessT; + var currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; + } + + /** + * points is an array of [ mX1, mY1, mX2, mY2 ] + */ + function BezierEasing(points) { + this._p = points; + this._mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); + this._precomputed = false; + + this.get = this.get.bind(this); + } + + BezierEasing.prototype = { + + get: function (x) { + var mX1 = this._p[0], + mY1 = this._p[1], + mX2 = this._p[2], + mY2 = this._p[3]; + if (!this._precomputed) this._precompute(); + if (mX1 === mY1 && mX2 === mY2) return x; // linear + // Because JavaScript number are imprecise, we should guarantee the extremes are right. + if (x === 0) return 0; + if (x === 1) return 1; + return calcBezier(this._getTForX(x), mY1, mY2); + }, + + // Private part + + _precompute: function () { + var mX1 = this._p[0], + mY1 = this._p[1], + mX2 = this._p[2], + mY2 = this._p[3]; + this._precomputed = true; + if (mX1 !== mY1 || mX2 !== mY2) { this._calcSampleValues(); } + }, + + _calcSampleValues: function () { + var mX1 = this._p[0], + mX2 = this._p[2]; + for (var i = 0; i < kSplineTableSize; ++i) { + this._mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + }, - AnimationItem.prototype.onSetupError = function () { - this.trigger('data_failed'); - }; + /** + * getTForX chose the fastest heuristic to determine the percentage value precisely from a given X projection. + */ + _getTForX: function (aX) { + var mX1 = this._p[0], + mX2 = this._p[2], + mSampleValues = this._mSampleValues; - AnimationItem.prototype.setupAnimation = function (data) { - dataManager.completeAnimation( - data, - this.configAnimation - ); - }; + var intervalStart = 0.0; + var currentSample = 1; + var lastSample = kSplineTableSize - 1; - AnimationItem.prototype.setData = function (wrapper, animationData) { - if (animationData) { - if (typeof animationData !== 'object') { - animationData = JSON.parse(animationData); - } + for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { + intervalStart += kSampleStepSize; } - var params = { - wrapper: wrapper, - animationData: animationData, - }; - var wrapperAttributes = wrapper.attributes; - - params.path = wrapperAttributes.getNamedItem('data-animation-path') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-animation-path').value - : wrapperAttributes.getNamedItem('data-bm-path') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-path').value - : wrapperAttributes.getNamedItem('bm-path') - ? wrapperAttributes.getNamedItem('bm-path').value - : ''; - params.animType = wrapperAttributes.getNamedItem('data-anim-type') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-type').value - : wrapperAttributes.getNamedItem('data-bm-type') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-type').value - : wrapperAttributes.getNamedItem('bm-type') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('bm-type').value - : wrapperAttributes.getNamedItem('data-bm-renderer') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-renderer').value - : wrapperAttributes.getNamedItem('bm-renderer') - ? wrapperAttributes.getNamedItem('bm-renderer').value - : 'canvas'; - - var loop = wrapperAttributes.getNamedItem('data-anim-loop') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-loop').value - : wrapperAttributes.getNamedItem('data-bm-loop') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-loop').value - : wrapperAttributes.getNamedItem('bm-loop') - ? wrapperAttributes.getNamedItem('bm-loop').value - : ''; - if (loop === 'false') { - params.loop = false; - } else if (loop === 'true') { - params.loop = true; - } else if (loop !== '') { - params.loop = parseInt(loop, 10); - } - var autoplay = wrapperAttributes.getNamedItem('data-anim-autoplay') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-autoplay').value - : wrapperAttributes.getNamedItem('data-bm-autoplay') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-autoplay').value - : wrapperAttributes.getNamedItem('bm-autoplay') - ? wrapperAttributes.getNamedItem('bm-autoplay').value - : true; - params.autoplay = autoplay !== 'false'; - - params.name = wrapperAttributes.getNamedItem('data-name') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-name').value - : wrapperAttributes.getNamedItem('data-bm-name') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-name').value - : wrapperAttributes.getNamedItem('bm-name') - ? wrapperAttributes.getNamedItem('bm-name').value - : ''; - var prerender = wrapperAttributes.getNamedItem('data-anim-prerender') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-anim-prerender').value - : wrapperAttributes.getNamedItem('data-bm-prerender') // eslint-disable-line no-nested-ternary - ? wrapperAttributes.getNamedItem('data-bm-prerender').value - : wrapperAttributes.getNamedItem('bm-prerender') - ? wrapperAttributes.getNamedItem('bm-prerender').value - : ''; - - if (prerender === 'false') { - params.prerender = false; - } - this.setParams(params); - }; + --currentSample; - AnimationItem.prototype.includeLayers = function (data) { - if (data.op > this.animationData.op) { - this.animationData.op = data.op; - this.totalFrames = Math.floor(data.op - this.animationData.ip); - } - var layers = this.animationData.layers; - var i; - var len = layers.length; - var newLayers = data.layers; - var j; - var jLen = newLayers.length; - for (j = 0; j < jLen; j += 1) { - i = 0; - while (i < len) { - if (layers[i].id === newLayers[j].id) { - layers[i] = newLayers[j]; - break; - } - i += 1; - } - } - if (data.chars || data.fonts) { - this.renderer.globalData.fontManager.addChars(data.chars); - this.renderer.globalData.fontManager.addFonts(data.fonts, this.renderer.globalData.defs); - } - if (data.assets) { - len = data.assets.length; - for (i = 0; i < len; i += 1) { - this.animationData.assets.push(data.assets[i]); - } - } - this.animationData.__complete = false; - dataManager.completeAnimation( - this.animationData, - this.onSegmentComplete - ); - }; + // Interpolate to provide an initial guess for t + var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]); + var guessForT = intervalStart + dist * kSampleStepSize; - AnimationItem.prototype.onSegmentComplete = function (data) { - this.animationData = data; - var expressionsPlugin = getExpressionsPlugin(); - if (expressionsPlugin) { - expressionsPlugin.initExpressions(this); + var initialSlope = getSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } if (initialSlope === 0.0) { + return guessForT; } - this.loadNextSegment(); - }; + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); + }, + }; - AnimationItem.prototype.loadNextSegment = function () { - var segments = this.animationData.segments; - if (!segments || segments.length === 0 || !this.autoloadSegments) { - this.trigger('data_ready'); - this.timeCompleted = this.totalFrames; - return; - } - var segment = segments.shift(); - this.timeCompleted = segment.time * this.frameRate; - var segmentPath = this.path + this.fileName + '_' + this.segmentPos + '.json'; - this.segmentPos += 1; - dataManager.loadData(segmentPath, this.includeLayers.bind(this), function () { - this.trigger('data_failed'); - }.bind(this)); - }; + return ob; +}()); - AnimationItem.prototype.loadSegments = function () { - var segments = this.animationData.segments; - if (!segments) { - this.timeCompleted = this.totalFrames; - } - this.loadNextSegment(); - }; +const pooling = (function () { + function double(arr) { + return arr.concat(createSizedArray(arr.length)); + } - AnimationItem.prototype.imagesLoaded = function () { - this.trigger('loaded_images'); - this.checkLoaded(); - }; + return { + double: double, + }; +}()); - AnimationItem.prototype.preloadImages = function () { - this.imagePreloader.setAssetsPath(this.assetsPath); - this.imagePreloader.setPath(this.path); - this.imagePreloader.loadAssets(this.animationData.assets, this.imagesLoaded.bind(this)); - }; +const poolFactory = (function () { + return function (initialLength, _create, _release) { + var _length = 0; + var _maxLength = initialLength; + var pool = createSizedArray(_maxLength); - AnimationItem.prototype.configAnimation = function (animData) { - if (!this.renderer) { - return; - } - try { - this.animationData = animData; - if (this.initialSegment) { - this.totalFrames = Math.floor(this.initialSegment[1] - this.initialSegment[0]); - this.firstFrame = Math.round(this.initialSegment[0]); - } else { - this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip); - this.firstFrame = Math.round(this.animationData.ip); - } - this.renderer.configAnimation(animData); - if (!animData.assets) { - animData.assets = []; - } - - this.assets = this.animationData.assets; - this.frameRate = this.animationData.fr; - this.frameMult = this.animationData.fr / 1000; - this.renderer.searchExtraCompositions(animData.assets); - this.markers = markerParser(animData.markers || []); - this.trigger('config_ready'); - this.preloadImages(); - this.loadSegments(); - this.updaFrameModifier(); - this.waitForFontsLoaded(); - if (this.isPaused) { - this.audioController.pause(); - } - } catch (error) { - this.triggerConfigError(error); - } + var ob = { + newElement: newElement, + release: release, }; - AnimationItem.prototype.waitForFontsLoaded = function () { - if (!this.renderer) { - return; - } - if (this.renderer.globalData.fontManager.isLoaded) { - this.checkLoaded(); + function newElement() { + var element; + if (_length) { + _length -= 1; + element = pool[_length]; } else { - setTimeout(this.waitForFontsLoaded.bind(this), 20); + element = _create(); } - }; + return element; + } - AnimationItem.prototype.checkLoaded = function () { - if (!this.isLoaded - && this.renderer.globalData.fontManager.isLoaded - && (this.imagePreloader.loadedImages() || this.renderer.rendererType !== 'canvas') - && (this.imagePreloader.loadedFootages()) - ) { - this.isLoaded = true; - var expressionsPlugin = getExpressionsPlugin(); - if (expressionsPlugin) { - expressionsPlugin.initExpressions(this); - } - this.renderer.initItems(); - setTimeout(function () { - this.trigger('DOMLoaded'); - }.bind(this), 0); - this.gotoFrame(); - if (this.autoplay) { - this.play(); - } + function release(element) { + if (_length === _maxLength) { + pool = pooling.double(pool); + _maxLength *= 2; } - }; + if (_release) { + _release(element); + } + pool[_length] = element; + _length += 1; + } - AnimationItem.prototype.resize = function () { - this.renderer.updateContainerSize(); - }; + return ob; + }; +}()); + +const bezierLengthPool = (function () { + function create() { + return { + addedLength: 0, + percents: createTypedArray('float32', getDefaultCurveSegments()), + lengths: createTypedArray('float32', getDefaultCurveSegments()), + }; + } + return poolFactory(8, create); +}()); + +const segmentsLengthPool = (function () { + function create() { + return { + lengths: [], + totalLength: 0, + }; + } + + function release(element) { + var i; + var len = element.lengths.length; + for (i = 0; i < len; i += 1) { + bezierLengthPool.release(element.lengths[i]); + } + element.lengths.length = 0; + } - AnimationItem.prototype.setSubframe = function (flag) { - this.isSubframeEnabled = !!flag; - }; + return poolFactory(8, create, release); +}()); - AnimationItem.prototype.gotoFrame = function () { - this.currentFrame = this.isSubframeEnabled ? this.currentRawFrame : ~~this.currentRawFrame; // eslint-disable-line no-bitwise +function bezFunction() { + var math = Math; - if (this.timeCompleted !== this.totalFrames && this.currentFrame > this.timeCompleted) { - this.currentFrame = this.timeCompleted; - } - this.trigger('enterFrame'); - this.renderFrame(); - this.trigger('drawnFrame'); - }; + function pointOnLine2D(x1, y1, x2, y2, x3, y3) { + var det1 = (x1 * y2) + (y1 * x3) + (x2 * y3) - (x3 * y2) - (y3 * x1) - (x2 * y1); + return det1 > -0.001 && det1 < 0.001; + } - AnimationItem.prototype.renderFrame = function () { - if (this.isLoaded === false || !this.renderer) { - return; - } - try { - this.renderer.renderFrame(this.currentFrame + this.firstFrame); - } catch (error) { - this.triggerRenderFrameError(error); + function pointOnLine3D(x1, y1, z1, x2, y2, z2, x3, y3, z3) { + if (z1 === 0 && z2 === 0 && z3 === 0) { + return pointOnLine2D(x1, y1, x2, y2, x3, y3); + } + var dist1 = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2)); + var dist2 = math.sqrt(math.pow(x3 - x1, 2) + math.pow(y3 - y1, 2) + math.pow(z3 - z1, 2)); + var dist3 = math.sqrt(math.pow(x3 - x2, 2) + math.pow(y3 - y2, 2) + math.pow(z3 - z2, 2)); + var diffDist; + if (dist1 > dist2) { + if (dist1 > dist3) { + diffDist = dist1 - dist2 - dist3; + } else { + diffDist = dist3 - dist2 - dist1; } - }; + } else if (dist3 > dist2) { + diffDist = dist3 - dist2 - dist1; + } else { + diffDist = dist2 - dist1 - dist3; + } + return diffDist > -0.0001 && diffDist < 0.0001; + } - AnimationItem.prototype.play = function (name) { - if (name && this.name !== name) { - return; - } - if (this.isPaused === true) { - this.isPaused = false; - this.trigger('_pause'); - this.audioController.resume(); - if (this._idle) { - this._idle = false; - this.trigger('_active'); + var getBezierLength = (function () { + return function (pt1, pt2, pt3, pt4) { + var curveSegments = getDefaultCurveSegments(); + var k; + var i; + var len; + var ptCoord; + var perc; + var addedLength = 0; + var ptDistance; + var point = []; + var lastPoint = []; + var lengthData = bezierLengthPool.newElement(); + len = pt3.length; + for (k = 0; k < curveSegments; k += 1) { + perc = k / (curveSegments - 1); + ptDistance = 0; + for (i = 0; i < len; i += 1) { + ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * pt3[i] + 3 * (1 - perc) * bmPow(perc, 2) * pt4[i] + bmPow(perc, 3) * pt2[i]; + point[i] = ptCoord; + if (lastPoint[i] !== null) { + ptDistance += bmPow(point[i] - lastPoint[i], 2); + } + lastPoint[i] = point[i]; + } + if (ptDistance) { + ptDistance = bmSqrt(ptDistance); + addedLength += ptDistance; + } + lengthData.percents[k] = perc; + lengthData.lengths[k] = addedLength; + } + lengthData.addedLength = addedLength; + return lengthData; + }; + }()); + + function getSegmentsLength(shapeData) { + var segmentsLength = segmentsLengthPool.newElement(); + var closed = shapeData.c; + var pathV = shapeData.v; + var pathO = shapeData.o; + var pathI = shapeData.i; + var i; + var len = shapeData._length; + var lengths = segmentsLength.lengths; + var totalLength = 0; + for (i = 0; i < len - 1; i += 1) { + lengths[i] = getBezierLength(pathV[i], pathV[i + 1], pathO[i], pathI[i + 1]); + totalLength += lengths[i].addedLength; + } + if (closed && len) { + lengths[i] = getBezierLength(pathV[i], pathV[0], pathO[i], pathI[0]); + totalLength += lengths[i].addedLength; + } + segmentsLength.totalLength = totalLength; + return segmentsLength; + } + + function BezierData(length) { + this.segmentLength = 0; + this.points = new Array(length); + } + + function PointData(partial, point) { + this.partialLength = partial; + this.point = point; + } + + var buildBezierData = (function () { + var storedData = {}; + + return function (pt1, pt2, pt3, pt4) { + var bezierName = (pt1[0] + '_' + pt1[1] + '_' + pt2[0] + '_' + pt2[1] + '_' + pt3[0] + '_' + pt3[1] + '_' + pt4[0] + '_' + pt4[1]).replace(/\./g, 'p'); + if (!storedData[bezierName]) { + var curveSegments = getDefaultCurveSegments(); + var k; + var i; + var len; + var ptCoord; + var perc; + var addedLength = 0; + var ptDistance; + var point; + var lastPoint = null; + if (pt1.length === 2 && (pt1[0] !== pt2[0] || pt1[1] !== pt2[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt1[0] + pt3[0], pt1[1] + pt3[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt2[0] + pt4[0], pt2[1] + pt4[1])) { + curveSegments = 2; + } + var bezierData = new BezierData(curveSegments); + len = pt3.length; + for (k = 0; k < curveSegments; k += 1) { + point = createSizedArray(len); + perc = k / (curveSegments - 1); + ptDistance = 0; + for (i = 0; i < len; i += 1) { + ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * (pt1[i] + pt3[i]) + 3 * (1 - perc) * bmPow(perc, 2) * (pt2[i] + pt4[i]) + bmPow(perc, 3) * pt2[i]; + point[i] = ptCoord; + if (lastPoint !== null) { + ptDistance += bmPow(point[i] - lastPoint[i], 2); + } + } + ptDistance = bmSqrt(ptDistance); + addedLength += ptDistance; + bezierData.points[k] = new PointData(ptDistance, point); + lastPoint = point; } + bezierData.segmentLength = addedLength; + storedData[bezierName] = bezierData; } + return storedData[bezierName]; }; + }()); - AnimationItem.prototype.pause = function (name) { - if (name && this.name !== name) { - return; - } - if (this.isPaused === false) { - this.isPaused = true; - this.trigger('_play'); - this._idle = true; - this.trigger('_idle'); - this.audioController.pause(); - } - }; - - AnimationItem.prototype.togglePause = function (name) { - if (name && this.name !== name) { - return; - } - if (this.isPaused === true) { - this.play(); + function getDistancePerc(perc, bezierData) { + var percents = bezierData.percents; + var lengths = bezierData.lengths; + var len = percents.length; + var initPos = bmFloor((len - 1) * perc); + var lengthPos = perc * bezierData.addedLength; + var lPerc = 0; + if (initPos === len - 1 || initPos === 0 || lengthPos === lengths[initPos]) { + return percents[initPos]; + } + var dir = lengths[initPos] > lengthPos ? -1 : 1; + var flag = true; + while (flag) { + if (lengths[initPos] <= lengthPos && lengths[initPos + 1] > lengthPos) { + lPerc = (lengthPos - lengths[initPos]) / (lengths[initPos + 1] - lengths[initPos]); + flag = false; } else { - this.pause(); + initPos += dir; } - }; - - AnimationItem.prototype.stop = function (name) { - if (name && this.name !== name) { - return; - } - this.pause(); - this.playCount = 0; - this._completedLoop = false; - this.setCurrentRawFrameValue(0); - }; - - AnimationItem.prototype.getMarkerData = function (markerName) { - var marker; - for (var i = 0; i < this.markers.length; i += 1) { - marker = this.markers[i]; - if (marker.payload && marker.payload.name === markerName) { - return marker; + if (initPos < 0 || initPos >= len - 1) { + // FIX for TypedArrays that don't store floating point values with enough accuracy + if (initPos === len - 1) { + return percents[initPos]; } + flag = false; } - return null; - }; + } + return percents[initPos] + (percents[initPos + 1] - percents[initPos]) * lPerc; + } + + function getPointInSegment(pt1, pt2, pt3, pt4, percent, bezierData) { + var t1 = getDistancePerc(percent, bezierData); + var u1 = 1 - t1; + var ptX = math.round((u1 * u1 * u1 * pt1[0] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[0] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[0] + t1 * t1 * t1 * pt2[0]) * 1000) / 1000; + var ptY = math.round((u1 * u1 * u1 * pt1[1] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[1] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[1] + t1 * t1 * t1 * pt2[1]) * 1000) / 1000; + return [ptX, ptY]; + } + + var bezierSegmentPoints = createTypedArray('float32', 8); + + function getNewSegment(pt1, pt2, pt3, pt4, startPerc, endPerc, bezierData) { + if (startPerc < 0) { + startPerc = 0; + } else if (startPerc > 1) { + startPerc = 1; + } + var t0 = getDistancePerc(startPerc, bezierData); + endPerc = endPerc > 1 ? 1 : endPerc; + var t1 = getDistancePerc(endPerc, bezierData); + var i; + var len = pt1.length; + var u0 = 1 - t0; + var u1 = 1 - t1; + var u0u0u0 = u0 * u0 * u0; + var t0u0u0_3 = t0 * u0 * u0 * 3; // eslint-disable-line camelcase + var t0t0u0_3 = t0 * t0 * u0 * 3; // eslint-disable-line camelcase + var t0t0t0 = t0 * t0 * t0; + // + var u0u0u1 = u0 * u0 * u1; + var t0u0u1_3 = t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1; // eslint-disable-line camelcase + var t0t0u1_3 = t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1; // eslint-disable-line camelcase + var t0t0t1 = t0 * t0 * t1; + // + var u0u1u1 = u0 * u1 * u1; + var t0u1u1_3 = t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1; // eslint-disable-line camelcase + var t0t1u1_3 = t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1; // eslint-disable-line camelcase + var t0t1t1 = t0 * t1 * t1; + // + var u1u1u1 = u1 * u1 * u1; + var t1u1u1_3 = t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1; // eslint-disable-line camelcase + var t1t1u1_3 = t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1; // eslint-disable-line camelcase + var t1t1t1 = t1 * t1 * t1; + for (i = 0; i < len; i += 1) { + bezierSegmentPoints[i * 4] = math.round((u0u0u0 * pt1[i] + t0u0u0_3 * pt3[i] + t0t0u0_3 * pt4[i] + t0t0t0 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + bezierSegmentPoints[i * 4 + 1] = math.round((u0u0u1 * pt1[i] + t0u0u1_3 * pt3[i] + t0t0u1_3 * pt4[i] + t0t0t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + bezierSegmentPoints[i * 4 + 2] = math.round((u0u1u1 * pt1[i] + t0u1u1_3 * pt3[i] + t0t1u1_3 * pt4[i] + t0t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + bezierSegmentPoints[i * 4 + 3] = math.round((u1u1u1 * pt1[i] + t1u1u1_3 * pt3[i] + t1t1u1_3 * pt4[i] + t1t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase + } - AnimationItem.prototype.goToAndStop = function (value, isFrame, name) { - if (name && this.name !== name) { - return; - } - var numValue = Number(value); - if (isNaN(numValue)) { - var marker = this.getMarkerData(value); - if (marker) { - this.goToAndStop(marker.time, true); - } - } else if (isFrame) { - this.setCurrentRawFrameValue(value); + return bezierSegmentPoints; + } + + return { + getSegmentsLength: getSegmentsLength, + getNewSegment: getNewSegment, + getPointInSegment: getPointInSegment, + buildBezierData: buildBezierData, + pointOnLine2D: pointOnLine2D, + pointOnLine3D: pointOnLine3D, + }; +} + +const bez = bezFunction(); + +const PropertyFactory = (function () { + var initFrame = initialDefaultFrame; + var mathAbs = Math.abs; + + function interpolateValue(frameNum, caching) { + var offsetTime = this.offsetTime; + var newValue; + if (this.propType === 'multidimensional') { + newValue = createTypedArray('float32', this.pv.length); + } + var iterationIndex = caching.lastIndex; + var i = iterationIndex; + var len = this.keyframes.length - 1; + var flag = true; + var keyData; + var nextKeyData; + var keyframeMetadata; + + while (flag) { + keyData = this.keyframes[i]; + nextKeyData = this.keyframes[i + 1]; + if (i === len - 1 && frameNum >= nextKeyData.t - offsetTime) { + if (keyData.h) { + keyData = nextKeyData; + } + iterationIndex = 0; + break; + } + if ((nextKeyData.t - offsetTime) > frameNum) { + iterationIndex = i; + break; + } + if (i < len - 1) { + i += 1; } else { - this.setCurrentRawFrameValue(value * this.frameModifier); - } - this.pause(); - }; - - AnimationItem.prototype.goToAndPlay = function (value, isFrame, name) { - if (name && this.name !== name) { - return; + iterationIndex = 0; + flag = false; } - var numValue = Number(value); - if (isNaN(numValue)) { - var marker = this.getMarkerData(value); - if (marker) { - if (!marker.duration) { - this.goToAndStop(marker.time, true); - } else { - this.playSegments([marker.time, marker.time + marker.duration], true); - } - } + } + keyframeMetadata = this.keyframesMetadata[i] || {}; + + var k; + var kLen; + var perc; + var jLen; + var j; + var fnc; + var nextKeyTime = nextKeyData.t - offsetTime; + var keyTime = keyData.t - offsetTime; + var endValue; + if (keyData.to) { + if (!keyframeMetadata.bezierData) { + keyframeMetadata.bezierData = bez.buildBezierData(keyData.s, nextKeyData.s || keyData.e, keyData.to, keyData.ti); + } + var bezierData = keyframeMetadata.bezierData; + if (frameNum >= nextKeyTime || frameNum < keyTime) { + var ind = frameNum >= nextKeyTime ? bezierData.points.length - 1 : 0; + kLen = bezierData.points[ind].point.length; + for (k = 0; k < kLen; k += 1) { + newValue[k] = bezierData.points[ind].point[k]; + } + // caching._lastKeyframeIndex = -1; } else { - this.goToAndStop(numValue, isFrame, name); - } - this.play(); - }; + if (keyframeMetadata.__fnct) { + fnc = keyframeMetadata.__fnct; + } else { + fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y, keyData.n).get; + keyframeMetadata.__fnct = fnc; + } + perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); + var distanceInLine = bezierData.segmentLength * perc; - AnimationItem.prototype.advanceTime = function (value) { - if (this.isPaused === true || this.isLoaded === false) { - return; - } - var nextValue = this.currentRawFrame + value * this.frameModifier; - var _isComplete = false; - // Checking if nextValue > totalFrames - 1 for addressing non looping and looping animations. - // If animation won't loop, it should stop at totalFrames - 1. If it will loop it should complete the last frame and then loop. - if (nextValue >= this.totalFrames - 1 && this.frameModifier > 0) { - if (!this.loop || this.playCount === this.loop) { - if (!this.checkSegments(nextValue > this.totalFrames ? nextValue % this.totalFrames : 0)) { - _isComplete = true; - nextValue = this.totalFrames - 1; + var segmentPerc; + var addedLength = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastAddedLength : 0; + j = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastPoint : 0; + flag = true; + jLen = bezierData.points.length; + while (flag) { + addedLength += bezierData.points[j].partialLength; + if (distanceInLine === 0 || perc === 0 || j === bezierData.points.length - 1) { + kLen = bezierData.points[j].point.length; + for (k = 0; k < kLen; k += 1) { + newValue[k] = bezierData.points[j].point[k]; + } + break; + } else if (distanceInLine >= addedLength && distanceInLine < addedLength + bezierData.points[j + 1].partialLength) { + segmentPerc = (distanceInLine - addedLength) / bezierData.points[j + 1].partialLength; + kLen = bezierData.points[j].point.length; + for (k = 0; k < kLen; k += 1) { + newValue[k] = bezierData.points[j].point[k] + (bezierData.points[j + 1].point[k] - bezierData.points[j].point[k]) * segmentPerc; + } + break; } - } else if (nextValue >= this.totalFrames) { - this.playCount += 1; - if (!this.checkSegments(nextValue % this.totalFrames)) { - this.setCurrentRawFrameValue(nextValue % this.totalFrames); - this._completedLoop = true; - this.trigger('loopComplete'); + if (j < jLen - 1) { + j += 1; + } else { + flag = false; } + } + caching._lastPoint = j; + caching._lastAddedLength = addedLength - bezierData.points[j].partialLength; + caching._lastKeyframeIndex = i; + } + } else { + var outX; + var outY; + var inX; + var inY; + var keyValue; + len = keyData.s.length; + endValue = nextKeyData.s || keyData.e; + if (this.sh && keyData.h !== 1) { + if (frameNum >= nextKeyTime) { + newValue[0] = endValue[0]; + newValue[1] = endValue[1]; + newValue[2] = endValue[2]; + } else if (frameNum <= keyTime) { + newValue[0] = keyData.s[0]; + newValue[1] = keyData.s[1]; + newValue[2] = keyData.s[2]; } else { - this.setCurrentRawFrameValue(nextValue); - } - } else if (nextValue < 0) { - if (!this.checkSegments(nextValue % this.totalFrames)) { - if (this.loop && !(this.playCount-- <= 0 && this.loop !== true)) { // eslint-disable-line no-plusplus - this.setCurrentRawFrameValue(this.totalFrames + (nextValue % this.totalFrames)); - if (!this._completedLoop) { - this._completedLoop = true; + var quatStart = createQuaternion(keyData.s); + var quatEnd = createQuaternion(endValue); + var time = (frameNum - keyTime) / (nextKeyTime - keyTime); + quaternionToEuler(newValue, slerp(quatStart, quatEnd, time)); + } + } else { + for (i = 0; i < len; i += 1) { + if (keyData.h !== 1) { + if (frameNum >= nextKeyTime) { + perc = 1; + } else if (frameNum < keyTime) { + perc = 0; } else { - this.trigger('loopComplete'); + if (keyData.o.x.constructor === Array) { + if (!keyframeMetadata.__fnct) { + keyframeMetadata.__fnct = []; + } + if (!keyframeMetadata.__fnct[i]) { + outX = keyData.o.x[i] === undefined ? keyData.o.x[0] : keyData.o.x[i]; + outY = keyData.o.y[i] === undefined ? keyData.o.y[0] : keyData.o.y[i]; + inX = keyData.i.x[i] === undefined ? keyData.i.x[0] : keyData.i.x[i]; + inY = keyData.i.y[i] === undefined ? keyData.i.y[0] : keyData.i.y[i]; + fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; + keyframeMetadata.__fnct[i] = fnc; + } else { + fnc = keyframeMetadata.__fnct[i]; + } + } else if (!keyframeMetadata.__fnct) { + outX = keyData.o.x; + outY = keyData.o.y; + inX = keyData.i.x; + inY = keyData.i.y; + fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; + keyData.keyframeMetadata = fnc; + } else { + fnc = keyframeMetadata.__fnct; + } + perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); } - } else { - _isComplete = true; - nextValue = 0; } - } - } else { - this.setCurrentRawFrameValue(nextValue); - } - if (_isComplete) { - this.setCurrentRawFrameValue(nextValue); - this.pause(); - this.trigger('complete'); - } - }; - AnimationItem.prototype.adjustSegment = function (arr, offset) { - this.playCount = 0; - if (arr[1] < arr[0]) { - if (this.frameModifier > 0) { - if (this.playSpeed < 0) { - this.setSpeed(-this.playSpeed); - } else { - this.setDirection(-1); - } - } - this.totalFrames = arr[0] - arr[1]; - this.timeCompleted = this.totalFrames; - this.firstFrame = arr[1]; - this.setCurrentRawFrameValue(this.totalFrames - 0.001 - offset); - } else if (arr[1] > arr[0]) { - if (this.frameModifier < 0) { - if (this.playSpeed < 0) { - this.setSpeed(-this.playSpeed); + endValue = nextKeyData.s || keyData.e; + keyValue = keyData.h === 1 ? keyData.s[i] : keyData.s[i] + (endValue[i] - keyData.s[i]) * perc; + + if (this.propType === 'multidimensional') { + newValue[i] = keyValue; } else { - this.setDirection(1); + newValue = keyValue; } } - this.totalFrames = arr[1] - arr[0]; - this.timeCompleted = this.totalFrames; - this.firstFrame = arr[0]; - this.setCurrentRawFrameValue(0.001 + offset); } - this.trigger('segmentStart'); - }; - AnimationItem.prototype.setSegment = function (init, end) { - var pendingFrame = -1; - if (this.isPaused) { - if (this.currentRawFrame + this.firstFrame < init) { - pendingFrame = init; - } else if (this.currentRawFrame + this.firstFrame > end) { - pendingFrame = end - init; + } + caching.lastIndex = iterationIndex; + return newValue; + } + + // based on @Toji's https://github.com/toji/gl-matrix/ + function slerp(a, b, t) { + var out = []; + var ax = a[0]; + var ay = a[1]; + var az = a[2]; + var aw = a[3]; + var bx = b[0]; + var by = b[1]; + var bz = b[2]; + var bw = b[3]; + + var omega; + var cosom; + var sinom; + var scale0; + var scale1; + + cosom = ax * bx + ay * by + az * bz + aw * bw; + if (cosom < 0.0) { + cosom = -cosom; + bx = -bx; + by = -by; + bz = -bz; + bw = -bw; + } + if ((1.0 - cosom) > 0.000001) { + omega = Math.acos(cosom); + sinom = Math.sin(omega); + scale0 = Math.sin((1.0 - t) * omega) / sinom; + scale1 = Math.sin(t * omega) / sinom; + } else { + scale0 = 1.0 - t; + scale1 = t; + } + out[0] = scale0 * ax + scale1 * bx; + out[1] = scale0 * ay + scale1 * by; + out[2] = scale0 * az + scale1 * bz; + out[3] = scale0 * aw + scale1 * bw; + + return out; + } + + function quaternionToEuler(out, quat) { + var qx = quat[0]; + var qy = quat[1]; + var qz = quat[2]; + var qw = quat[3]; + var heading = Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy * qy - 2 * qz * qz); + var attitude = Math.asin(2 * qx * qy + 2 * qz * qw); + var bank = Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx * qx - 2 * qz * qz); + out[0] = heading / degToRads; + out[1] = attitude / degToRads; + out[2] = bank / degToRads; + } + + function createQuaternion(values) { + var heading = values[0] * degToRads; + var attitude = values[1] * degToRads; + var bank = values[2] * degToRads; + var c1 = Math.cos(heading / 2); + var c2 = Math.cos(attitude / 2); + var c3 = Math.cos(bank / 2); + var s1 = Math.sin(heading / 2); + var s2 = Math.sin(attitude / 2); + var s3 = Math.sin(bank / 2); + var w = c1 * c2 * c3 - s1 * s2 * s3; + var x = s1 * s2 * c3 + c1 * c2 * s3; + var y = s1 * c2 * c3 + c1 * s2 * s3; + var z = c1 * s2 * c3 - s1 * c2 * s3; + + return [x, y, z, w]; + } + + function getValueAtCurrentTime() { + var frameNum = this.comp.renderedFrame - this.offsetTime; + var initTime = this.keyframes[0].t - this.offsetTime; + var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; + if (!(frameNum === this._caching.lastFrame || (this._caching.lastFrame !== initFrame && ((this._caching.lastFrame >= endTime && frameNum >= endTime) || (this._caching.lastFrame < initTime && frameNum < initTime))))) { + if (this._caching.lastFrame >= frameNum) { + this._caching._lastKeyframeIndex = -1; + this._caching.lastIndex = 0; + } + + var renderResult = this.interpolateValue(frameNum, this._caching); + this.pv = renderResult; + } + this._caching.lastFrame = frameNum; + return this.pv; + } + + function setVValue(val) { + var multipliedValue; + if (this.propType === 'unidimensional') { + multipliedValue = val * this.mult; + if (mathAbs(this.v - multipliedValue) > 0.00001) { + this.v = multipliedValue; + this._mdf = true; + } + } else { + var i = 0; + var len = this.v.length; + while (i < len) { + multipliedValue = val[i] * this.mult; + if (mathAbs(this.v[i] - multipliedValue) > 0.00001) { + this.v[i] = multipliedValue; + this._mdf = true; } + i += 1; } + } + } - this.firstFrame = init; - this.totalFrames = end - init; - this.timeCompleted = this.totalFrames; - if (pendingFrame !== -1) { - this.goToAndStop(pendingFrame, true); - } - }; - - AnimationItem.prototype.playSegments = function (arr, forceFlag) { - if (forceFlag) { - this.segments.length = 0; - } - if (typeof arr[0] === 'object') { - var i; - var len = arr.length; - for (i = 0; i < len; i += 1) { - this.segments.push(arr[i]); + function processEffectsSequence() { + if (this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) { + return; + } + if (this.lock) { + this.setVValue(this.pv); + return; + } + this.lock = true; + this._mdf = this._isFirstFrame; + var i; + var len = this.effectsSequence.length; + var finalValue = this.kf ? this.pv : this.data.k; + for (i = 0; i < len; i += 1) { + finalValue = this.effectsSequence[i](finalValue); + } + this.setVValue(finalValue); + this._isFirstFrame = false; + this.lock = false; + this.frameId = this.elem.globalData.frameId; + } + + function addEffect(effectFunction) { + this.effectsSequence.push(effectFunction); + this.container.addDynamicProperty(this); + } + + function ValueProperty(elem, data, mult, container) { + this.propType = 'unidimensional'; + this.mult = mult || 1; + this.data = data; + this.v = mult ? data.k * mult : data.k; + this.pv = data.k; + this._mdf = false; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.k = false; + this.kf = false; + this.vel = 0; + this.effectsSequence = []; + this._isFirstFrame = true; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.addEffect = addEffect; + } + + function MultiDimensionalProperty(elem, data, mult, container) { + this.propType = 'multidimensional'; + this.mult = mult || 1; + this.data = data; + this._mdf = false; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.k = false; + this.kf = false; + this.frameId = -1; + var i; + var len = data.k.length; + this.v = createTypedArray('float32', len); + this.pv = createTypedArray('float32', len); + this.vel = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + this.v[i] = data.k[i] * this.mult; + this.pv[i] = data.k[i]; + } + this._isFirstFrame = true; + this.effectsSequence = []; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.addEffect = addEffect; + } + + function KeyframedValueProperty(elem, data, mult, container) { + this.propType = 'unidimensional'; + this.keyframes = data.k; + this.keyframesMetadata = []; + this.offsetTime = elem.data.st; + this.frameId = -1; + this._caching = { + lastFrame: initFrame, lastIndex: 0, value: 0, _lastKeyframeIndex: -1, + }; + this.k = true; + this.kf = true; + this.data = data; + this.mult = mult || 1; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.v = initFrame; + this.pv = initFrame; + this._isFirstFrame = true; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.interpolateValue = interpolateValue; + this.effectsSequence = [getValueAtCurrentTime.bind(this)]; + this.addEffect = addEffect; + } + + function KeyframedMultidimensionalProperty(elem, data, mult, container) { + this.propType = 'multidimensional'; + var i; + var len = data.k.length; + var s; + var e; + var to; + var ti; + for (i = 0; i < len - 1; i += 1) { + if (data.k[i].to && data.k[i].s && data.k[i + 1] && data.k[i + 1].s) { + s = data.k[i].s; + e = data.k[i + 1].s; + to = data.k[i].to; + ti = data.k[i].ti; + if ((s.length === 2 && !(s[0] === e[0] && s[1] === e[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], s[0] + to[0], s[1] + to[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], e[0] + ti[0], e[1] + ti[1])) || (s.length === 3 && !(s[0] === e[0] && s[1] === e[1] && s[2] === e[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], s[0] + to[0], s[1] + to[1], s[2] + to[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], e[0] + ti[0], e[1] + ti[1], e[2] + ti[2]))) { + data.k[i].to = null; + data.k[i].ti = null; + } + if (s[0] === e[0] && s[1] === e[1] && to[0] === 0 && to[1] === 0 && ti[0] === 0 && ti[1] === 0) { + if (s.length === 2 || (s[2] === e[2] && to[2] === 0 && ti[2] === 0)) { + data.k[i].to = null; + data.k[i].ti = null; + } } - } else { - this.segments.push(arr); } - if (this.segments.length && forceFlag) { - this.adjustSegment(this.segments.shift(), 0); + } + this.effectsSequence = [getValueAtCurrentTime.bind(this)]; + this.data = data; + this.keyframes = data.k; + this.keyframesMetadata = []; + this.offsetTime = elem.data.st; + this.k = true; + this.kf = true; + this._isFirstFrame = true; + this.mult = mult || 1; + this.elem = elem; + this.container = container; + this.comp = elem.comp; + this.getValue = processEffectsSequence; + this.setVValue = setVValue; + this.interpolateValue = interpolateValue; + this.frameId = -1; + var arrLen = data.k[0].s.length; + this.v = createTypedArray('float32', arrLen); + this.pv = createTypedArray('float32', arrLen); + for (i = 0; i < arrLen; i += 1) { + this.v[i] = initFrame; + this.pv[i] = initFrame; + } + this._caching = { lastFrame: initFrame, lastIndex: 0, value: createTypedArray('float32', arrLen) }; + this.addEffect = addEffect; + } + + function getProp(elem, data, type, mult, container) { + var p; + if (!data.k.length) { + p = new ValueProperty(elem, data, mult, container); + } else if (typeof (data.k[0]) === 'number') { + p = new MultiDimensionalProperty(elem, data, mult, container); + } else { + switch (type) { + case 0: + p = new KeyframedValueProperty(elem, data, mult, container); + break; + case 1: + p = new KeyframedMultidimensionalProperty(elem, data, mult, container); + break; + default: + break; } - if (this.isPaused) { - this.play(); + } + if (p.effectsSequence.length) { + container.addDynamicProperty(p); + } + return p; + } + + var ob = { + getProp: getProp, + }; + return ob; +}()); + +function DynamicPropertyContainer() {} +DynamicPropertyContainer.prototype = { + addDynamicProperty: function (prop) { + if (this.dynamicProperties.indexOf(prop) === -1) { + this.dynamicProperties.push(prop); + this.container.addDynamicProperty(this); + this._isAnimated = true; + } + }, + iterateDynamicProperties: function () { + this._mdf = false; + var i; + var len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + this.dynamicProperties[i].getValue(); + if (this.dynamicProperties[i]._mdf) { + this._mdf = true; } - }; + } + }, + initDynamicPropertyContainer: function (container) { + this.container = container; + this.dynamicProperties = []; + this._mdf = false; + this._isAnimated = false; + }, +}; + +const pointPool = (function () { + function create() { + return createTypedArray('float32', 2); + } + return poolFactory(8, create); +}()); + +function ShapePath() { + this.c = false; + this._length = 0; + this._maxLength = 8; + this.v = createSizedArray(this._maxLength); + this.o = createSizedArray(this._maxLength); + this.i = createSizedArray(this._maxLength); +} + +ShapePath.prototype.setPathData = function (closed, len) { + this.c = closed; + this.setLength(len); + var i = 0; + while (i < len) { + this.v[i] = pointPool.newElement(); + this.o[i] = pointPool.newElement(); + this.i[i] = pointPool.newElement(); + i += 1; + } +}; + +ShapePath.prototype.setLength = function (len) { + while (this._maxLength < len) { + this.doubleArrayLength(); + } + this._length = len; +}; + +ShapePath.prototype.doubleArrayLength = function () { + this.v = this.v.concat(createSizedArray(this._maxLength)); + this.i = this.i.concat(createSizedArray(this._maxLength)); + this.o = this.o.concat(createSizedArray(this._maxLength)); + this._maxLength *= 2; +}; + +ShapePath.prototype.setXYAt = function (x, y, type, pos, replace) { + var arr; + this._length = Math.max(this._length, pos + 1); + if (this._length >= this._maxLength) { + this.doubleArrayLength(); + } + switch (type) { + case 'v': + arr = this.v; + break; + case 'i': + arr = this.i; + break; + case 'o': + arr = this.o; + break; + default: + arr = []; + break; + } + if (!arr[pos] || (arr[pos] && !replace)) { + arr[pos] = pointPool.newElement(); + } + arr[pos][0] = x; + arr[pos][1] = y; +}; + +ShapePath.prototype.setTripleAt = function (vX, vY, oX, oY, iX, iY, pos, replace) { + this.setXYAt(vX, vY, 'v', pos, replace); + this.setXYAt(oX, oY, 'o', pos, replace); + this.setXYAt(iX, iY, 'i', pos, replace); +}; + +ShapePath.prototype.reverse = function () { + var newPath = new ShapePath(); + newPath.setPathData(this.c, this._length); + var vertices = this.v; + var outPoints = this.o; + var inPoints = this.i; + var init = 0; + if (this.c) { + newPath.setTripleAt(vertices[0][0], vertices[0][1], inPoints[0][0], inPoints[0][1], outPoints[0][0], outPoints[0][1], 0, false); + init = 1; + } + var cnt = this._length - 1; + var len = this._length; + + var i; + for (i = init; i < len; i += 1) { + newPath.setTripleAt(vertices[cnt][0], vertices[cnt][1], inPoints[cnt][0], inPoints[cnt][1], outPoints[cnt][0], outPoints[cnt][1], i, false); + cnt -= 1; + } + return newPath; +}; + +const shapePool = (function () { + function create() { + return new ShapePath(); + } + + function release(shapePath) { + var len = shapePath._length; + var i; + for (i = 0; i < len; i += 1) { + pointPool.release(shapePath.v[i]); + pointPool.release(shapePath.i[i]); + pointPool.release(shapePath.o[i]); + shapePath.v[i] = null; + shapePath.i[i] = null; + shapePath.o[i] = null; + } + shapePath._length = 0; + shapePath.c = false; + } + + function clone(shape) { + var cloned = factory.newElement(); + var i; + var len = shape._length === undefined ? shape.v.length : shape._length; + cloned.setLength(len); + cloned.c = shape.c; + + for (i = 0; i < len; i += 1) { + cloned.setTripleAt(shape.v[i][0], shape.v[i][1], shape.o[i][0], shape.o[i][1], shape.i[i][0], shape.i[i][1], i); + } + return cloned; + } + + var factory = poolFactory(4, create, release); + factory.clone = clone; + + return factory; +}()); + +function ShapeCollection() { + this._length = 0; + this._maxLength = 4; + this.shapes = createSizedArray(this._maxLength); +} + +ShapeCollection.prototype.addShape = function (shapeData) { + if (this._length === this._maxLength) { + this.shapes = this.shapes.concat(createSizedArray(this._maxLength)); + this._maxLength *= 2; + } + this.shapes[this._length] = shapeData; + this._length += 1; +}; + +ShapeCollection.prototype.releaseShapes = function () { + var i; + for (i = 0; i < this._length; i += 1) { + shapePool.release(this.shapes[i]); + } + this._length = 0; +}; + +const shapeCollectionPool = (function () { + var ob = { + newShapeCollection: newShapeCollection, + release: release, + }; + + var _length = 0; + var _maxLength = 4; + var pool = createSizedArray(_maxLength); + + function newShapeCollection() { + var shapeCollection; + if (_length) { + _length -= 1; + shapeCollection = pool[_length]; + } else { + shapeCollection = new ShapeCollection(); + } + return shapeCollection; + } + + function release(shapeCollection) { + var i; + var len = shapeCollection._length; + for (i = 0; i < len; i += 1) { + shapePool.release(shapeCollection.shapes[i]); + } + shapeCollection._length = 0; - AnimationItem.prototype.resetSegments = function (forceFlag) { - this.segments.length = 0; - this.segments.push([this.animationData.ip, this.animationData.op]); - if (forceFlag) { - this.checkSegments(0); + if (_length === _maxLength) { + pool = pooling.double(pool); + _maxLength *= 2; + } + pool[_length] = shapeCollection; + _length += 1; + } + + return ob; +}()); + +const ShapePropertyFactory = (function () { + var initFrame = -999999; + + function interpolateShape(frameNum, previousValue, caching) { + var iterationIndex = caching.lastIndex; + var keyPropS; + var keyPropE; + var isHold; + var j; + var k; + var jLen; + var kLen; + var perc; + var vertexValue; + var kf = this.keyframes; + if (frameNum < kf[0].t - this.offsetTime) { + keyPropS = kf[0].s[0]; + isHold = true; + iterationIndex = 0; + } else if (frameNum >= kf[kf.length - 1].t - this.offsetTime) { + keyPropS = kf[kf.length - 1].s ? kf[kf.length - 1].s[0] : kf[kf.length - 2].e[0]; + /* if(kf[kf.length - 1].s){ + keyPropS = kf[kf.length - 1].s[0]; + }else{ + keyPropS = kf[kf.length - 2].e[0]; + } */ + isHold = true; + } else { + var i = iterationIndex; + var len = kf.length - 1; + var flag = true; + var keyData; + var nextKeyData; + var keyframeMetadata; + while (flag) { + keyData = kf[i]; + nextKeyData = kf[i + 1]; + if ((nextKeyData.t - this.offsetTime) > frameNum) { + break; + } + if (i < len - 1) { + i += 1; + } else { + flag = false; + } } - }; - AnimationItem.prototype.checkSegments = function (offset) { - if (this.segments.length) { - this.adjustSegment(this.segments.shift(), offset); - return true; + keyframeMetadata = this.keyframesMetadata[i] || {}; + isHold = keyData.h === 1; + iterationIndex = i; + if (!isHold) { + if (frameNum >= nextKeyData.t - this.offsetTime) { + perc = 1; + } else if (frameNum < keyData.t - this.offsetTime) { + perc = 0; + } else { + var fnc; + if (keyframeMetadata.__fnct) { + fnc = keyframeMetadata.__fnct; + } else { + fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y).get; + keyframeMetadata.__fnct = fnc; + } + perc = fnc((frameNum - (keyData.t - this.offsetTime)) / ((nextKeyData.t - this.offsetTime) - (keyData.t - this.offsetTime))); + } + keyPropE = nextKeyData.s ? nextKeyData.s[0] : keyData.e[0]; } - return false; - }; - - AnimationItem.prototype.destroy = function (name) { - if ((name && this.name !== name) || !this.renderer) { - return; + keyPropS = keyData.s[0]; + } + jLen = previousValue._length; + kLen = keyPropS.i[0].length; + caching.lastIndex = iterationIndex; + + for (j = 0; j < jLen; j += 1) { + for (k = 0; k < kLen; k += 1) { + vertexValue = isHold ? keyPropS.i[j][k] : keyPropS.i[j][k] + (keyPropE.i[j][k] - keyPropS.i[j][k]) * perc; + previousValue.i[j][k] = vertexValue; + vertexValue = isHold ? keyPropS.o[j][k] : keyPropS.o[j][k] + (keyPropE.o[j][k] - keyPropS.o[j][k]) * perc; + previousValue.o[j][k] = vertexValue; + vertexValue = isHold ? keyPropS.v[j][k] : keyPropS.v[j][k] + (keyPropE.v[j][k] - keyPropS.v[j][k]) * perc; + previousValue.v[j][k] = vertexValue; } - this.renderer.destroy(); - this.imagePreloader.destroy(); - this.trigger('destroy'); - this._cbs = null; - this.onEnterFrame = null; - this.onLoopComplete = null; - this.onComplete = null; - this.onSegmentStart = null; - this.onDestroy = null; - this.renderer = null; - this.renderer = null; - this.imagePreloader = null; - this.projectInterface = null; - }; - - AnimationItem.prototype.setCurrentRawFrameValue = function (value) { - this.currentRawFrame = value; - this.gotoFrame(); - }; - - AnimationItem.prototype.setSpeed = function (val) { - this.playSpeed = val; - this.updaFrameModifier(); - }; + } + } + + function interpolateShapeCurrentTime() { + var frameNum = this.comp.renderedFrame - this.offsetTime; + var initTime = this.keyframes[0].t - this.offsetTime; + var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; + var lastFrame = this._caching.lastFrame; + if (!(lastFrame !== initFrame && ((lastFrame < initTime && frameNum < initTime) || (lastFrame > endTime && frameNum > endTime)))) { + /// / + this._caching.lastIndex = lastFrame < frameNum ? this._caching.lastIndex : 0; + this.interpolateShape(frameNum, this.pv, this._caching); + /// / + } + this._caching.lastFrame = frameNum; + return this.pv; + } - AnimationItem.prototype.setDirection = function (val) { - this.playDirection = val < 0 ? -1 : 1; - this.updaFrameModifier(); - }; + function resetShape() { + this.paths = this.localShapeCollection; + } - AnimationItem.prototype.setVolume = function (val, name) { - if (name && this.name !== name) { - return; + function shapesEqual(shape1, shape2) { + if (shape1._length !== shape2._length || shape1.c !== shape2.c) { + return false; + } + var i; + var len = shape1._length; + for (i = 0; i < len; i += 1) { + if (shape1.v[i][0] !== shape2.v[i][0] + || shape1.v[i][1] !== shape2.v[i][1] + || shape1.o[i][0] !== shape2.o[i][0] + || shape1.o[i][1] !== shape2.o[i][1] + || shape1.i[i][0] !== shape2.i[i][0] + || shape1.i[i][1] !== shape2.i[i][1]) { + return false; } - this.audioController.setVolume(val); - }; - - AnimationItem.prototype.getVolume = function () { - return this.audioController.getVolume(); - }; + } + return true; + } + + function setVValue(newPath) { + if (!shapesEqual(this.v, newPath)) { + this.v = shapePool.clone(newPath); + this.localShapeCollection.releaseShapes(); + this.localShapeCollection.addShape(this.v); + this._mdf = true; + this.paths = this.localShapeCollection; + } + } - AnimationItem.prototype.mute = function (name) { - if (name && this.name !== name) { - return; + function processEffectsSequence() { + if (this.elem.globalData.frameId === this.frameId) { + return; + } if (!this.effectsSequence.length) { + this._mdf = false; + return; + } + if (this.lock) { + this.setVValue(this.pv); + return; + } + this.lock = true; + this._mdf = false; + var finalValue; + if (this.kf) { + finalValue = this.pv; + } else if (this.data.ks) { + finalValue = this.data.ks.k; + } else { + finalValue = this.data.pt.k; + } + var i; + var len = this.effectsSequence.length; + for (i = 0; i < len; i += 1) { + finalValue = this.effectsSequence[i](finalValue); + } + this.setVValue(finalValue); + this.lock = false; + this.frameId = this.elem.globalData.frameId; + } + + function ShapeProperty(elem, data, type) { + this.propType = 'shape'; + this.comp = elem.comp; + this.container = elem; + this.elem = elem; + this.data = data; + this.k = false; + this.kf = false; + this._mdf = false; + var pathData = type === 3 ? data.pt.k : data.ks.k; + this.v = shapePool.clone(pathData); + this.pv = shapePool.clone(this.v); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.paths = this.localShapeCollection; + this.paths.addShape(this.v); + this.reset = resetShape; + this.effectsSequence = []; + } + + function addEffect(effectFunction) { + this.effectsSequence.push(effectFunction); + this.container.addDynamicProperty(this); + } + + ShapeProperty.prototype.interpolateShape = interpolateShape; + ShapeProperty.prototype.getValue = processEffectsSequence; + ShapeProperty.prototype.setVValue = setVValue; + ShapeProperty.prototype.addEffect = addEffect; + + function KeyframedShapeProperty(elem, data, type) { + this.propType = 'shape'; + this.comp = elem.comp; + this.elem = elem; + this.container = elem; + this.offsetTime = elem.data.st; + this.keyframes = type === 3 ? data.pt.k : data.ks.k; + this.keyframesMetadata = []; + this.k = true; + this.kf = true; + var len = this.keyframes[0].s[0].i.length; + this.v = shapePool.newElement(); + this.v.setPathData(this.keyframes[0].s[0].c, len); + this.pv = shapePool.clone(this.v); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.paths = this.localShapeCollection; + this.paths.addShape(this.v); + this.lastFrame = initFrame; + this.reset = resetShape; + this._caching = { lastFrame: initFrame, lastIndex: 0 }; + this.effectsSequence = [interpolateShapeCurrentTime.bind(this)]; + } + KeyframedShapeProperty.prototype.getValue = processEffectsSequence; + KeyframedShapeProperty.prototype.interpolateShape = interpolateShape; + KeyframedShapeProperty.prototype.setVValue = setVValue; + KeyframedShapeProperty.prototype.addEffect = addEffect; + + var EllShapeProperty = (function () { + var cPoint = roundCorner; + + function EllShapePropertyFactory(elem, data) { + this.v = shapePool.newElement(); + this.v.setPathData(true, 4); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.paths = this.localShapeCollection; + this.localShapeCollection.addShape(this.v); + this.d = data.d; + this.elem = elem; + this.comp = elem.comp; + this.frameId = -1; + this.initDynamicPropertyContainer(elem); + this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); + this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.k = false; + this.convertEllToPath(); } - this.audioController.mute(); - }; + } - AnimationItem.prototype.unmute = function (name) { - if (name && this.name !== name) { - return; - } - this.audioController.unmute(); - }; + EllShapePropertyFactory.prototype = { + reset: resetShape, + getValue: function () { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); - AnimationItem.prototype.updaFrameModifier = function () { - this.frameModifier = this.frameMult * this.playSpeed * this.playDirection; - this.audioController.setRate(this.playSpeed * this.playDirection); + if (this._mdf) { + this.convertEllToPath(); + } + }, + convertEllToPath: function () { + var p0 = this.p.v[0]; + var p1 = this.p.v[1]; + var s0 = this.s.v[0] / 2; + var s1 = this.s.v[1] / 2; + var _cw = this.d !== 3; + var _v = this.v; + _v.v[0][0] = p0; + _v.v[0][1] = p1 - s1; + _v.v[1][0] = _cw ? p0 + s0 : p0 - s0; + _v.v[1][1] = p1; + _v.v[2][0] = p0; + _v.v[2][1] = p1 + s1; + _v.v[3][0] = _cw ? p0 - s0 : p0 + s0; + _v.v[3][1] = p1; + _v.i[0][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; + _v.i[0][1] = p1 - s1; + _v.i[1][0] = _cw ? p0 + s0 : p0 - s0; + _v.i[1][1] = p1 - s1 * cPoint; + _v.i[2][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; + _v.i[2][1] = p1 + s1; + _v.i[3][0] = _cw ? p0 - s0 : p0 + s0; + _v.i[3][1] = p1 + s1 * cPoint; + _v.o[0][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; + _v.o[0][1] = p1 - s1; + _v.o[1][0] = _cw ? p0 + s0 : p0 - s0; + _v.o[1][1] = p1 + s1 * cPoint; + _v.o[2][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; + _v.o[2][1] = p1 + s1; + _v.o[3][0] = _cw ? p0 - s0 : p0 + s0; + _v.o[3][1] = p1 - s1 * cPoint; + }, }; - AnimationItem.prototype.getPath = function () { - return this.path; - }; + extendPrototype([DynamicPropertyContainer], EllShapePropertyFactory); - AnimationItem.prototype.getAssetsPath = function (assetData) { - var path = ''; - if (assetData.e) { - path = assetData.p; - } else if (this.assetsPath) { - var imagePath = assetData.p; - if (imagePath.indexOf('images/') !== -1) { - imagePath = imagePath.split('/')[1]; - } - path = this.assetsPath + imagePath; + return EllShapePropertyFactory; + }()); + + var StarShapeProperty = (function () { + function StarShapePropertyFactory(elem, data) { + this.v = shapePool.newElement(); + this.v.setPathData(true, 0); + this.elem = elem; + this.comp = elem.comp; + this.data = data; + this.frameId = -1; + this.d = data.d; + this.initDynamicPropertyContainer(elem); + if (data.sy === 1) { + this.ir = PropertyFactory.getProp(elem, data.ir, 0, 0, this); + this.is = PropertyFactory.getProp(elem, data.is, 0, 0.01, this); + this.convertToPath = this.convertStarToPath; + } else { + this.convertToPath = this.convertPolygonToPath; + } + this.pt = PropertyFactory.getProp(elem, data.pt, 0, 0, this); + this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); + this.r = PropertyFactory.getProp(elem, data.r, 0, degToRads, this); + this.or = PropertyFactory.getProp(elem, data.or, 0, 0, this); + this.os = PropertyFactory.getProp(elem, data.os, 0, 0.01, this); + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.localShapeCollection.addShape(this.v); + this.paths = this.localShapeCollection; + if (this.dynamicProperties.length) { + this.k = true; } else { - path = this.path; - path += assetData.u ? assetData.u : ''; - path += assetData.p; + this.k = false; + this.convertToPath(); } - return path; - }; + } - AnimationItem.prototype.getAssetData = function (id) { - var i = 0; - var len = this.assets.length; - while (i < len) { - if (id === this.assets[i].id) { - return this.assets[i]; + StarShapePropertyFactory.prototype = { + reset: resetShape, + getValue: function () { + if (this.elem.globalData.frameId === this.frameId) { + return; } - i += 1; - } - return null; - }; - - AnimationItem.prototype.hide = function () { - this.renderer.hide(); - }; + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + if (this._mdf) { + this.convertToPath(); + } + }, + convertStarToPath: function () { + var numPts = Math.floor(this.pt.v) * 2; + var angle = (Math.PI * 2) / numPts; + /* this.v.v.length = numPts; + this.v.i.length = numPts; + this.v.o.length = numPts; */ + var longFlag = true; + var longRad = this.or.v; + var shortRad = this.ir.v; + var longRound = this.os.v; + var shortRound = this.is.v; + var longPerimSegment = (2 * Math.PI * longRad) / (numPts * 2); + var shortPerimSegment = (2 * Math.PI * shortRad) / (numPts * 2); + var i; + var rad; + var roundness; + var perimSegment; + var currentAng = -Math.PI / 2; + currentAng += this.r.v; + var dir = this.data.d === 3 ? -1 : 1; + this.v._length = 0; + for (i = 0; i < numPts; i += 1) { + rad = longFlag ? longRad : shortRad; + roundness = longFlag ? longRound : shortRound; + perimSegment = longFlag ? longPerimSegment : shortPerimSegment; + var x = rad * Math.cos(currentAng); + var y = rad * Math.sin(currentAng); + var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); + var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); + x += +this.p.v[0]; + y += +this.p.v[1]; + this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); + + /* this.v.v[i] = [x,y]; + this.v.i[i] = [x+ox*perimSegment*roundness*dir,y+oy*perimSegment*roundness*dir]; + this.v.o[i] = [x-ox*perimSegment*roundness*dir,y-oy*perimSegment*roundness*dir]; + this.v._length = numPts; */ + longFlag = !longFlag; + currentAng += angle * dir; + } + }, + convertPolygonToPath: function () { + var numPts = Math.floor(this.pt.v); + var angle = (Math.PI * 2) / numPts; + var rad = this.or.v; + var roundness = this.os.v; + var perimSegment = (2 * Math.PI * rad) / (numPts * 4); + var i; + var currentAng = -Math.PI * 0.5; + var dir = this.data.d === 3 ? -1 : 1; + currentAng += this.r.v; + this.v._length = 0; + for (i = 0; i < numPts; i += 1) { + var x = rad * Math.cos(currentAng); + var y = rad * Math.sin(currentAng); + var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); + var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); + x += +this.p.v[0]; + y += +this.p.v[1]; + this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); + currentAng += angle * dir; + } + this.paths.length = 0; + this.paths[0] = this.v; + }, - AnimationItem.prototype.show = function () { - this.renderer.show(); }; + extendPrototype([DynamicPropertyContainer], StarShapePropertyFactory); - AnimationItem.prototype.getDuration = function (isFrame) { - return isFrame ? this.totalFrames : this.totalFrames / this.frameRate; - }; + return StarShapePropertyFactory; + }()); - AnimationItem.prototype.updateDocumentData = function (path, documentData, index) { - try { - var element = this.renderer.getElementByPath(path); - element.updateDocumentData(documentData, index); - } catch (error) { - // TODO: decide how to handle catch case + var RectShapeProperty = (function () { + function RectShapePropertyFactory(elem, data) { + this.v = shapePool.newElement(); + this.v.c = true; + this.localShapeCollection = shapeCollectionPool.newShapeCollection(); + this.localShapeCollection.addShape(this.v); + this.paths = this.localShapeCollection; + this.elem = elem; + this.comp = elem.comp; + this.frameId = -1; + this.d = data.d; + this.initDynamicPropertyContainer(elem); + this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); + this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); + this.r = PropertyFactory.getProp(elem, data.r, 0, 0, this); + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.k = false; + this.convertRectToPath(); } - }; + } - AnimationItem.prototype.trigger = function (name) { - if (this._cbs && this._cbs[name]) { - switch (name) { - case 'enterFrame': - this.triggerEvent(name, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameModifier)); - break; - case 'drawnFrame': - this.drawnFrameEvent.currentTime = this.currentFrame; - this.drawnFrameEvent.totalTime = this.totalFrames; - this.drawnFrameEvent.direction = this.frameModifier; - this.triggerEvent(name, this.drawnFrameEvent); - break; - case 'loopComplete': - this.triggerEvent(name, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); - break; - case 'complete': - this.triggerEvent(name, new BMCompleteEvent(name, this.frameMult)); - break; - case 'segmentStart': - this.triggerEvent(name, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); - break; - case 'destroy': - this.triggerEvent(name, new BMDestroyEvent(name, this)); - break; - default: - this.triggerEvent(name); + RectShapePropertyFactory.prototype = { + convertRectToPath: function () { + var p0 = this.p.v[0]; + var p1 = this.p.v[1]; + var v0 = this.s.v[0] / 2; + var v1 = this.s.v[1] / 2; + var round = bmMin(v0, v1, this.r.v); + var cPoint = round * (1 - roundCorner); + this.v._length = 0; + + if (this.d === 2 || this.d === 1) { + this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, 0, true); + this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, p0 + v0, p1 + v1 - round, 1, true); + if (round !== 0) { + this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, 2, true); + this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0 + round, p1 + v1, 3, true); + this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, 4, true); + this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1 + round, 5, true); + this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, 6, true); + this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, p0 + v0 - round, p1 - v1, 7, true); + } else { + this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0, p1 + v1, 2); + this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1, 3); + } + } else { + this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, p0 + v0, p1 - v1 + round, 0, true); + if (round !== 0) { + this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, 1, true); + this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0 + round, p1 - v1, 2, true); + this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, 3, true); + this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1 - round, 4, true); + this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, 5, true); + this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0 - round, p1 + v1, 6, true); + this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, 7, true); + } else { + this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0, p1 - v1, 1, true); + this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1, 2, true); + this.v.setTripleAt(p0 + v0, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0, p1 + v1, 3, true); + } } - } - if (name === 'enterFrame' && this.onEnterFrame) { - this.onEnterFrame.call(this, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameMult)); - } - if (name === 'loopComplete' && this.onLoopComplete) { - this.onLoopComplete.call(this, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult)); - } - if (name === 'complete' && this.onComplete) { - this.onComplete.call(this, new BMCompleteEvent(name, this.frameMult)); - } - if (name === 'segmentStart' && this.onSegmentStart) { - this.onSegmentStart.call(this, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames)); - } - if (name === 'destroy' && this.onDestroy) { - this.onDestroy.call(this, new BMDestroyEvent(name, this)); - } + }, + getValue: function () { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + if (this._mdf) { + this.convertRectToPath(); + } + }, + reset: resetShape, }; + extendPrototype([DynamicPropertyContainer], RectShapePropertyFactory); + + return RectShapePropertyFactory; + }()); + + function getShapeProp(elem, data, type) { + var prop; + if (type === 3 || type === 4) { + var dataProp = type === 3 ? data.pt : data.ks; + var keys = dataProp.k; + if (keys.length) { + prop = new KeyframedShapeProperty(elem, data, type); + } else { + prop = new ShapeProperty(elem, data, type); + } + } else if (type === 5) { + prop = new RectShapeProperty(elem, data); + } else if (type === 6) { + prop = new EllShapeProperty(elem, data); + } else if (type === 7) { + prop = new StarShapeProperty(elem, data); + } + if (prop.k) { + elem.addDynamicProperty(prop); + } + return prop; + } + + function getConstructorFunction() { + return ShapeProperty; + } + + function getKeyframedConstructorFunction() { + return KeyframedShapeProperty; + } + + var ob = {}; + ob.getShapeProp = getShapeProp; + ob.getConstructorFunction = getConstructorFunction; + ob.getKeyframedConstructorFunction = getKeyframedConstructorFunction; + return ob; +}()); + +/*! + Transformation Matrix v2.0 + (c) Epistemex 2014-2015 + www.epistemex.com + By Ken Fyrstenberg + Contributions by leeoniya. + License: MIT, header required. + */ + +/** + * 2D transformation matrix object initialized with identity matrix. + * + * The matrix can synchronize a canvas context by supplying the context + * as an argument, or later apply current absolute transform to an + * existing context. + * + * All values are handled as floating point values. + * + * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix + * @prop {number} a - scale x + * @prop {number} b - shear y + * @prop {number} c - shear x + * @prop {number} d - scale y + * @prop {number} e - translate x + * @prop {number} f - translate y + * @prop {CanvasRenderingContext2D|null} [context=null] - set or get current canvas context + * @constructor + */ + +const Matrix = (function () { + var _cos = Math.cos; + var _sin = Math.sin; + var _tan = Math.tan; + var _rnd = Math.round; + + function reset() { + this.props[0] = 1; + this.props[1] = 0; + this.props[2] = 0; + this.props[3] = 0; + this.props[4] = 0; + this.props[5] = 1; + this.props[6] = 0; + this.props[7] = 0; + this.props[8] = 0; + this.props[9] = 0; + this.props[10] = 1; + this.props[11] = 0; + this.props[12] = 0; + this.props[13] = 0; + this.props[14] = 0; + this.props[15] = 1; + return this; + } + + function rotate(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + function rotateX(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(1, 0, 0, 0, 0, mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1); + } + + function rotateY(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, 0, mSin, 0, 0, 1, 0, 0, -mSin, 0, mCos, 0, 0, 0, 0, 1); + } + + function rotateZ(angle) { + if (angle === 0) { + return this; + } + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + function shear(sx, sy) { + return this._t(1, sy, sx, 1, 0, 0); + } + + function skew(ax, ay) { + return this.shear(_tan(ax), _tan(ay)); + } + + function skewFromAxis(ax, angle) { + var mCos = _cos(angle); + var mSin = _sin(angle); + return this._t(mCos, mSin, 0, 0, -mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) + ._t(1, 0, 0, 0, _tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) + ._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + // return this._t(mCos, mSin, -mSin, mCos, 0, 0)._t(1, 0, _tan(ax), 1, 0, 0)._t(mCos, -mSin, mSin, mCos, 0, 0); + } + + function scale(sx, sy, sz) { + if (!sz && sz !== 0) { + sz = 1; + } + if (sx === 1 && sy === 1 && sz === 1) { + return this; + } + return this._t(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1); + } + + function setTransform(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { + this.props[0] = a; + this.props[1] = b; + this.props[2] = c; + this.props[3] = d; + this.props[4] = e; + this.props[5] = f; + this.props[6] = g; + this.props[7] = h; + this.props[8] = i; + this.props[9] = j; + this.props[10] = k; + this.props[11] = l; + this.props[12] = m; + this.props[13] = n; + this.props[14] = o; + this.props[15] = p; + return this; + } + + function translate(tx, ty, tz) { + tz = tz || 0; + if (tx !== 0 || ty !== 0 || tz !== 0) { + return this._t(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1); + } + return this; + } + + function transform(a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2) { + var _p = this.props; + + if (a2 === 1 && b2 === 0 && c2 === 0 && d2 === 0 && e2 === 0 && f2 === 1 && g2 === 0 && h2 === 0 && i2 === 0 && j2 === 0 && k2 === 1 && l2 === 0) { + // NOTE: commenting this condition because TurboFan deoptimizes code when present + // if(m2 !== 0 || n2 !== 0 || o2 !== 0){ + _p[12] = _p[12] * a2 + _p[15] * m2; + _p[13] = _p[13] * f2 + _p[15] * n2; + _p[14] = _p[14] * k2 + _p[15] * o2; + _p[15] *= p2; + // } + this._identityCalculated = false; + return this; + } - AnimationItem.prototype.triggerRenderFrameError = function (nativeError) { - var error = new BMRenderFrameErrorEvent(nativeError, this.currentFrame); - this.triggerEvent('error', error); + var a1 = _p[0]; + var b1 = _p[1]; + var c1 = _p[2]; + var d1 = _p[3]; + var e1 = _p[4]; + var f1 = _p[5]; + var g1 = _p[6]; + var h1 = _p[7]; + var i1 = _p[8]; + var j1 = _p[9]; + var k1 = _p[10]; + var l1 = _p[11]; + var m1 = _p[12]; + var n1 = _p[13]; + var o1 = _p[14]; + var p1 = _p[15]; + + /* matrix order (canvas compatible): + * ace + * bdf + * 001 + */ + _p[0] = a1 * a2 + b1 * e2 + c1 * i2 + d1 * m2; + _p[1] = a1 * b2 + b1 * f2 + c1 * j2 + d1 * n2; + _p[2] = a1 * c2 + b1 * g2 + c1 * k2 + d1 * o2; + _p[3] = a1 * d2 + b1 * h2 + c1 * l2 + d1 * p2; + + _p[4] = e1 * a2 + f1 * e2 + g1 * i2 + h1 * m2; + _p[5] = e1 * b2 + f1 * f2 + g1 * j2 + h1 * n2; + _p[6] = e1 * c2 + f1 * g2 + g1 * k2 + h1 * o2; + _p[7] = e1 * d2 + f1 * h2 + g1 * l2 + h1 * p2; + + _p[8] = i1 * a2 + j1 * e2 + k1 * i2 + l1 * m2; + _p[9] = i1 * b2 + j1 * f2 + k1 * j2 + l1 * n2; + _p[10] = i1 * c2 + j1 * g2 + k1 * k2 + l1 * o2; + _p[11] = i1 * d2 + j1 * h2 + k1 * l2 + l1 * p2; + + _p[12] = m1 * a2 + n1 * e2 + o1 * i2 + p1 * m2; + _p[13] = m1 * b2 + n1 * f2 + o1 * j2 + p1 * n2; + _p[14] = m1 * c2 + n1 * g2 + o1 * k2 + p1 * o2; + _p[15] = m1 * d2 + n1 * h2 + o1 * l2 + p1 * p2; + + this._identityCalculated = false; + return this; + } + + function isIdentity() { + if (!this._identityCalculated) { + this._identity = !(this.props[0] !== 1 || this.props[1] !== 0 || this.props[2] !== 0 || this.props[3] !== 0 || this.props[4] !== 0 || this.props[5] !== 1 || this.props[6] !== 0 || this.props[7] !== 0 || this.props[8] !== 0 || this.props[9] !== 0 || this.props[10] !== 1 || this.props[11] !== 0 || this.props[12] !== 0 || this.props[13] !== 0 || this.props[14] !== 0 || this.props[15] !== 1); + this._identityCalculated = true; + } + return this._identity; + } - if (this.onError) { - this.onError.call(this, error); + function equals(matr) { + var i = 0; + while (i < 16) { + if (matr.props[i] !== this.props[i]) { + return false; } - }; + i += 1; + } + return true; + } + + function clone(matr) { + var i; + for (i = 0; i < 16; i += 1) { + matr.props[i] = this.props[i]; + } + return matr; + } + + function cloneFromProps(props) { + var i; + for (i = 0; i < 16; i += 1) { + this.props[i] = props[i]; + } + } + + function applyToPoint(x, y, z) { + return { + x: x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], + y: x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], + z: x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], + }; + /* return { + x: x * me.a + y * me.c + me.e, + y: x * me.b + y * me.d + me.f + }; */ + } + function applyToX(x, y, z) { + return x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12]; + } + function applyToY(x, y, z) { + return x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13]; + } + function applyToZ(x, y, z) { + return x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14]; + } + + function getInverseMatrix() { + var determinant = this.props[0] * this.props[5] - this.props[1] * this.props[4]; + var a = this.props[5] / determinant; + var b = -this.props[1] / determinant; + var c = -this.props[4] / determinant; + var d = this.props[0] / determinant; + var e = (this.props[4] * this.props[13] - this.props[5] * this.props[12]) / determinant; + var f = -(this.props[0] * this.props[13] - this.props[1] * this.props[12]) / determinant; + var inverseMatrix = new Matrix(); + inverseMatrix.props[0] = a; + inverseMatrix.props[1] = b; + inverseMatrix.props[4] = c; + inverseMatrix.props[5] = d; + inverseMatrix.props[12] = e; + inverseMatrix.props[13] = f; + return inverseMatrix; + } + + function inversePoint(pt) { + var inverseMatrix = this.getInverseMatrix(); + return inverseMatrix.applyToPointArray(pt[0], pt[1], pt[2] || 0); + } + + function inversePoints(pts) { + var i; + var len = pts.length; + var retPts = []; + for (i = 0; i < len; i += 1) { + retPts[i] = inversePoint(pts[i]); + } + return retPts; + } + + function applyToTriplePoints(pt1, pt2, pt3) { + var arr = createTypedArray('float32', 6); + if (this.isIdentity()) { + arr[0] = pt1[0]; + arr[1] = pt1[1]; + arr[2] = pt2[0]; + arr[3] = pt2[1]; + arr[4] = pt3[0]; + arr[5] = pt3[1]; + } else { + var p0 = this.props[0]; + var p1 = this.props[1]; + var p4 = this.props[4]; + var p5 = this.props[5]; + var p12 = this.props[12]; + var p13 = this.props[13]; + arr[0] = pt1[0] * p0 + pt1[1] * p4 + p12; + arr[1] = pt1[0] * p1 + pt1[1] * p5 + p13; + arr[2] = pt2[0] * p0 + pt2[1] * p4 + p12; + arr[3] = pt2[0] * p1 + pt2[1] * p5 + p13; + arr[4] = pt3[0] * p0 + pt3[1] * p4 + p12; + arr[5] = pt3[0] * p1 + pt3[1] * p5 + p13; + } + return arr; + } + + function applyToPointArray(x, y, z) { + var arr; + if (this.isIdentity()) { + arr = [x, y, z]; + } else { + arr = [ + x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], + x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], + x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], + ]; + } + return arr; + } + + function applyToPointStringified(x, y) { + if (this.isIdentity()) { + return x + ',' + y; + } + var _p = this.props; + return Math.round((x * _p[0] + y * _p[4] + _p[12]) * 100) / 100 + ',' + Math.round((x * _p[1] + y * _p[5] + _p[13]) * 100) / 100; + } + + function toCSS() { + // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. + /* if(this.isIdentity()) { + return ''; + } */ + var i = 0; + var props = this.props; + var cssValue = 'matrix3d('; + var v = 10000; + while (i < 16) { + cssValue += _rnd(props[i] * v) / v; + cssValue += i === 15 ? ')' : ','; + i += 1; + } + return cssValue; + } - AnimationItem.prototype.triggerConfigError = function (nativeError) { - var error = new BMConfigErrorEvent(nativeError, this.currentFrame); - this.triggerEvent('error', error); + function roundMatrixProperty(val) { + var v = 10000; + if ((val < 0.000001 && val > 0) || (val > -0.000001 && val < 0)) { + return _rnd(val * v) / v; + } + return val; + } - if (this.onError) { - this.onError.call(this, error); + function to2dCSS() { + // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. + /* if(this.isIdentity()) { + return ''; + } */ + var props = this.props; + var _a = roundMatrixProperty(props[0]); + var _b = roundMatrixProperty(props[1]); + var _c = roundMatrixProperty(props[4]); + var _d = roundMatrixProperty(props[5]); + var _e = roundMatrixProperty(props[12]); + var _f = roundMatrixProperty(props[13]); + return 'matrix(' + _a + ',' + _b + ',' + _c + ',' + _d + ',' + _e + ',' + _f + ')'; + } + + return function () { + this.reset = reset; + this.rotate = rotate; + this.rotateX = rotateX; + this.rotateY = rotateY; + this.rotateZ = rotateZ; + this.skew = skew; + this.skewFromAxis = skewFromAxis; + this.shear = shear; + this.scale = scale; + this.setTransform = setTransform; + this.translate = translate; + this.transform = transform; + this.applyToPoint = applyToPoint; + this.applyToX = applyToX; + this.applyToY = applyToY; + this.applyToZ = applyToZ; + this.applyToPointArray = applyToPointArray; + this.applyToTriplePoints = applyToTriplePoints; + this.applyToPointStringified = applyToPointStringified; + this.toCSS = toCSS; + this.to2dCSS = to2dCSS; + this.clone = clone; + this.cloneFromProps = cloneFromProps; + this.equals = equals; + this.inversePoints = inversePoints; + this.inversePoint = inversePoint; + this.getInverseMatrix = getInverseMatrix; + this._t = this.transform; + this.isIdentity = isIdentity; + this._identity = true; + this._identityCalculated = false; + + this.props = createTypedArray('float32', 16); + this.reset(); + }; +}()); + +const lottie = {}; +var standalone = '__[STANDALONE]__'; +var animationData = '__[ANIMATIONDATA]__'; +var renderer = ''; + +function setLocation(href) { + setLocationHref(href); +} + +function searchAnimations() { + if (standalone === true) { + animationManager.searchAnimations(animationData, standalone, renderer); + } else { + animationManager.searchAnimations(); + } +} + +function setSubframeRendering(flag) { + setSubframeEnabled(flag); +} + +function setPrefix(prefix) { + setIdPrefix(prefix); +} + +function loadAnimation(params) { + if (standalone === true) { + params.animationData = JSON.parse(animationData); + } + return animationManager.loadAnimation(params); +} + +function setQuality(value) { + if (typeof value === 'string') { + switch (value) { + case 'high': + setDefaultCurveSegments(200); + break; + default: + case 'medium': + setDefaultCurveSegments(50); + break; + case 'low': + setDefaultCurveSegments(10); + break; + } + } else if (!isNaN(value) && value > 1) { + setDefaultCurveSegments(value); + } + if (getDefaultCurveSegments() >= 50) { + roundValues(false); + } else { + roundValues(true); + } +} + +function inBrowser() { + return typeof navigator !== 'undefined'; +} + +function installPlugin(type, plugin) { + if (type === 'expressions') { + setExpressionsPlugin(plugin); + } +} + +function getFactory(name) { + switch (name) { + case 'propertyFactory': + return PropertyFactory; + case 'shapePropertyFactory': + return ShapePropertyFactory; + case 'matrix': + return Matrix; + default: + return null; + } +} + +lottie.play = animationManager.play; +lottie.pause = animationManager.pause; +lottie.setLocationHref = setLocation; +lottie.togglePause = animationManager.togglePause; +lottie.setSpeed = animationManager.setSpeed; +lottie.setDirection = animationManager.setDirection; +lottie.stop = animationManager.stop; +lottie.searchAnimations = searchAnimations; +lottie.registerAnimation = animationManager.registerAnimation; +lottie.loadAnimation = loadAnimation; +lottie.setSubframeRendering = setSubframeRendering; +lottie.resize = animationManager.resize; +// lottie.start = start; +lottie.goToAndStop = animationManager.goToAndStop; +lottie.destroy = animationManager.destroy; +lottie.setQuality = setQuality; +lottie.inBrowser = inBrowser; +lottie.installPlugin = installPlugin; +lottie.freeze = animationManager.freeze; +lottie.unfreeze = animationManager.unfreeze; +lottie.setVolume = animationManager.setVolume; +lottie.mute = animationManager.mute; +lottie.unmute = animationManager.unmute; +lottie.getRegisteredAnimations = animationManager.getRegisteredAnimations; +lottie.useWebWorker = setWebWorker; +lottie.setIDPrefix = setPrefix; +lottie.__getFactory = getFactory; +lottie.version = '[[BM_VERSION]]'; + +function checkReady() { + if (document.readyState === 'complete') { + clearInterval(readyStateCheckInterval); + searchAnimations(); + } +} + +function getQueryVariable(variable) { + var vars = queryString.split('&'); + for (var i = 0; i < vars.length; i += 1) { + var pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) == variable) { // eslint-disable-line eqeqeq + return decodeURIComponent(pair[1]); + } + } + return null; +} +var queryString = ''; +if (standalone) { + var scripts = document.getElementsByTagName('script'); + var index = scripts.length - 1; + var myScript = scripts[index] || { + src: '', + }; + queryString = myScript.src ? myScript.src.replace(/^[^\?]+\??/, '') : ''; // eslint-disable-line no-useless-escape + renderer = getQueryVariable('renderer'); +} +var readyStateCheckInterval = setInterval(checkReady, 100); + +// this adds bodymovin to the window object for backwards compatibility +try { + if (!(typeof exports === 'object' && typeof module !== 'undefined') + && !(typeof define === 'function' && define.amd) // eslint-disable-line no-undef + ) { + window.bodymovin = lottie; + } +} catch (err) { + // +} + +const ShapeModifiers = (function () { + var ob = {}; + var modifiers = {}; + ob.registerModifier = registerModifier; + ob.getModifier = getModifier; + + function registerModifier(nm, factory) { + if (!modifiers[nm]) { + modifiers[nm] = factory; + } + } + + function getModifier(nm, elem, data) { + return new modifiers[nm](elem, data); + } + + return ob; +}()); + +function ShapeModifier() {} +ShapeModifier.prototype.initModifierProperties = function () {}; +ShapeModifier.prototype.addShapeToModifier = function () {}; +ShapeModifier.prototype.addShape = function (data) { + if (!this.closed) { + // Adding shape to dynamic properties. It covers the case where a shape has no effects applied, to reset it's _mdf state on every tick. + data.sh.container.addDynamicProperty(data.sh); + var shapeData = { shape: data.sh, data: data, localShapeCollection: shapeCollectionPool.newShapeCollection() }; + this.shapes.push(shapeData); + this.addShapeToModifier(shapeData); + if (this._isAnimated) { + data.setAsAnimated(); + } + } +}; +ShapeModifier.prototype.init = function (elem, data) { + this.shapes = []; + this.elem = elem; + this.initDynamicPropertyContainer(elem); + this.initModifierProperties(elem, data); + this.frameId = initialDefaultFrame; + this.closed = false; + this.k = false; + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.getValue(true); + } +}; +ShapeModifier.prototype.processKeys = function () { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); +}; + +extendPrototype([DynamicPropertyContainer], ShapeModifier); + +function TrimModifier() { +} +extendPrototype([ShapeModifier], TrimModifier); +TrimModifier.prototype.initModifierProperties = function (elem, data) { + this.s = PropertyFactory.getProp(elem, data.s, 0, 0.01, this); + this.e = PropertyFactory.getProp(elem, data.e, 0, 0.01, this); + this.o = PropertyFactory.getProp(elem, data.o, 0, 0, this); + this.sValue = 0; + this.eValue = 0; + this.getValue = this.processKeys; + this.m = data.m; + this._isAnimated = !!this.s.effectsSequence.length || !!this.e.effectsSequence.length || !!this.o.effectsSequence.length; +}; + +TrimModifier.prototype.addShapeToModifier = function (shapeData) { + shapeData.pathsData = []; +}; + +TrimModifier.prototype.calculateShapeEdges = function (s, e, shapeLength, addedLength, totalModifierLength) { + var segments = []; + if (e <= 1) { + segments.push({ + s: s, + e: e, + }); + } else if (s >= 1) { + segments.push({ + s: s - 1, + e: e - 1, + }); + } else { + segments.push({ + s: s, + e: 1, + }); + segments.push({ + s: 0, + e: e - 1, + }); + } + var shapeSegments = []; + var i; + var len = segments.length; + var segmentOb; + for (i = 0; i < len; i += 1) { + segmentOb = segments[i]; + if (!(segmentOb.e * totalModifierLength < addedLength || segmentOb.s * totalModifierLength > addedLength + shapeLength)) { + var shapeS; + var shapeE; + if (segmentOb.s * totalModifierLength <= addedLength) { + shapeS = 0; + } else { + shapeS = (segmentOb.s * totalModifierLength - addedLength) / shapeLength; } - }; + if (segmentOb.e * totalModifierLength >= addedLength + shapeLength) { + shapeE = 1; + } else { + shapeE = ((segmentOb.e * totalModifierLength - addedLength) / shapeLength); + } + shapeSegments.push([shapeS, shapeE]); + } + } + if (!shapeSegments.length) { + shapeSegments.push([0, 0]); + } + return shapeSegments; +}; + +TrimModifier.prototype.releasePathsData = function (pathsData) { + var i; + var len = pathsData.length; + for (i = 0; i < len; i += 1) { + segmentsLengthPool.release(pathsData[i]); + } + pathsData.length = 0; + return pathsData; +}; + +TrimModifier.prototype.processShapes = function (_isFirstFrame) { + var s; + var e; + if (this._mdf || _isFirstFrame) { + var o = (this.o.v % 360) / 360; + if (o < 0) { + o += 1; + } + if (this.s.v > 1) { + s = 1 + o; + } else if (this.s.v < 0) { + s = 0 + o; + } else { + s = this.s.v + o; + } + if (this.e.v > 1) { + e = 1 + o; + } else if (this.e.v < 0) { + e = 0 + o; + } else { + e = this.e.v + o; + } - const animationManager = (function () { - var moduleOb = {}; - var registeredAnimations = []; - var initTime = 0; - var len = 0; - var playingAnimationsNum = 0; - var _stopped = true; - var _isFrozen = false; - - function removeElement(ev) { - var i = 0; - var animItem = ev.target; - while (i < len) { - if (registeredAnimations[i].animation === animItem) { - registeredAnimations.splice(i, 1); - i -= 1; - len -= 1; - if (!animItem.isPaused) { - subtractPlayingCount(); - } + if (s > e) { + var _s = s; + s = e; + e = _s; + } + s = Math.round(s * 10000) * 0.0001; + e = Math.round(e * 10000) * 0.0001; + this.sValue = s; + this.eValue = e; + } else { + s = this.sValue; + e = this.eValue; + } + var shapePaths; + var i; + var len = this.shapes.length; + var j; + var jLen; + var pathsData; + var pathData; + var totalShapeLength; + var totalModifierLength = 0; + + if (e === s) { + for (i = 0; i < len; i += 1) { + this.shapes[i].localShapeCollection.releaseShapes(); + this.shapes[i].shape._mdf = true; + this.shapes[i].shape.paths = this.shapes[i].localShapeCollection; + if (this._mdf) { + this.shapes[i].pathsData.length = 0; + } + } + } else if (!((e === 1 && s === 0) || (e === 0 && s === 1))) { + var segments = []; + var shapeData; + var localShapeCollection; + for (i = 0; i < len; i += 1) { + shapeData = this.shapes[i]; + // if shape hasn't changed and trim properties haven't changed, cached previous path can be used + if (!shapeData.shape._mdf && !this._mdf && !_isFirstFrame && this.m !== 2) { + shapeData.shape.paths = shapeData.localShapeCollection; + } else { + shapePaths = shapeData.shape.paths; + jLen = shapePaths._length; + totalShapeLength = 0; + if (!shapeData.shape._mdf && shapeData.pathsData.length) { + totalShapeLength = shapeData.totalShapeLength; + } else { + pathsData = this.releasePathsData(shapeData.pathsData); + for (j = 0; j < jLen; j += 1) { + pathData = bez.getSegmentsLength(shapePaths.shapes[j]); + pathsData.push(pathData); + totalShapeLength += pathData.totalLength; } - i += 1; + shapeData.totalShapeLength = totalShapeLength; + shapeData.pathsData = pathsData; } - } - function registerAnimation(element, animationData) { - if (!element) { - return null; + totalModifierLength += totalShapeLength; + shapeData.shape._mdf = true; + } + } + var shapeS = s; + var shapeE = e; + var addedLength = 0; + var edges; + for (i = len - 1; i >= 0; i -= 1) { + shapeData = this.shapes[i]; + if (shapeData.shape._mdf) { + localShapeCollection = shapeData.localShapeCollection; + localShapeCollection.releaseShapes(); + // if m === 2 means paths are trimmed individually so edges need to be found for this specific shape relative to whoel group + if (this.m === 2 && len > 1) { + edges = this.calculateShapeEdges(s, e, shapeData.totalShapeLength, addedLength, totalModifierLength); + addedLength += shapeData.totalShapeLength; + } else { + edges = [[shapeS, shapeE]]; } - var i = 0; - while (i < len) { - if (registeredAnimations[i].elem === element && registeredAnimations[i].elem !== null) { - return registeredAnimations[i].animation; + jLen = edges.length; + for (j = 0; j < jLen; j += 1) { + shapeS = edges[j][0]; + shapeE = edges[j][1]; + segments.length = 0; + if (shapeE <= 1) { + segments.push({ + s: shapeData.totalShapeLength * shapeS, + e: shapeData.totalShapeLength * shapeE, + }); + } else if (shapeS >= 1) { + segments.push({ + s: shapeData.totalShapeLength * (shapeS - 1), + e: shapeData.totalShapeLength * (shapeE - 1), + }); + } else { + segments.push({ + s: shapeData.totalShapeLength * shapeS, + e: shapeData.totalShapeLength, + }); + segments.push({ + s: 0, + e: shapeData.totalShapeLength * (shapeE - 1), + }); + } + var newShapesData = this.addShapes(shapeData, segments[0]); + if (segments[0].s !== segments[0].e) { + if (segments.length > 1) { + var lastShapeInCollection = shapeData.shape.paths.shapes[shapeData.shape.paths._length - 1]; + if (lastShapeInCollection.c) { + var lastShape = newShapesData.pop(); + this.addPaths(newShapesData, localShapeCollection); + newShapesData = this.addShapes(shapeData, segments[1], lastShape); + } else { + this.addPaths(newShapesData, localShapeCollection); + newShapesData = this.addShapes(shapeData, segments[1]); + } + } + this.addPaths(newShapesData, localShapeCollection); } - i += 1; } - var animItem = new AnimationItem(); - setupAnimation(animItem, element); - animItem.setData(element, animationData); - return animItem; + shapeData.shape.paths = localShapeCollection; } - - function getRegisteredAnimations() { - var i; - var lenAnims = registeredAnimations.length; - var animations = []; - for (i = 0; i < lenAnims; i += 1) { - animations.push(registeredAnimations[i].animation); + } + } else if (this._mdf) { + for (i = 0; i < len; i += 1) { + // Releasign Trim Cached paths data when no trim applied in case shapes are modified inbetween. + // Don't remove this even if it's losing cached info. + this.shapes[i].pathsData.length = 0; + this.shapes[i].shape._mdf = true; + } + } +}; + +TrimModifier.prototype.addPaths = function (newPaths, localShapeCollection) { + var i; + var len = newPaths.length; + for (i = 0; i < len; i += 1) { + localShapeCollection.addShape(newPaths[i]); + } +}; + +TrimModifier.prototype.addSegment = function (pt1, pt2, pt3, pt4, shapePath, pos, newShape) { + shapePath.setXYAt(pt2[0], pt2[1], 'o', pos); + shapePath.setXYAt(pt3[0], pt3[1], 'i', pos + 1); + if (newShape) { + shapePath.setXYAt(pt1[0], pt1[1], 'v', pos); + } + shapePath.setXYAt(pt4[0], pt4[1], 'v', pos + 1); +}; + +TrimModifier.prototype.addSegmentFromArray = function (points, shapePath, pos, newShape) { + shapePath.setXYAt(points[1], points[5], 'o', pos); + shapePath.setXYAt(points[2], points[6], 'i', pos + 1); + if (newShape) { + shapePath.setXYAt(points[0], points[4], 'v', pos); + } + shapePath.setXYAt(points[3], points[7], 'v', pos + 1); +}; + +TrimModifier.prototype.addShapes = function (shapeData, shapeSegment, shapePath) { + var pathsData = shapeData.pathsData; + var shapePaths = shapeData.shape.paths.shapes; + var i; + var len = shapeData.shape.paths._length; + var j; + var jLen; + var addedLength = 0; + var currentLengthData; + var segmentCount; + var lengths; + var segment; + var shapes = []; + var initPos; + var newShape = true; + if (!shapePath) { + shapePath = shapePool.newElement(); + segmentCount = 0; + initPos = 0; + } else { + segmentCount = shapePath._length; + initPos = shapePath._length; + } + shapes.push(shapePath); + for (i = 0; i < len; i += 1) { + lengths = pathsData[i].lengths; + shapePath.c = shapePaths[i].c; + jLen = shapePaths[i].c ? lengths.length : lengths.length + 1; + for (j = 1; j < jLen; j += 1) { + currentLengthData = lengths[j - 1]; + if (addedLength + currentLengthData.addedLength < shapeSegment.s) { + addedLength += currentLengthData.addedLength; + shapePath.c = false; + } else if (addedLength > shapeSegment.e) { + shapePath.c = false; + break; + } else { + if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + currentLengthData.addedLength) { + this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[j], shapePaths[i].v[j], shapePath, segmentCount, newShape); + newShape = false; + } else { + segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[j], shapePaths[i].o[j - 1], shapePaths[i].i[j], (shapeSegment.s - addedLength) / currentLengthData.addedLength, (shapeSegment.e - addedLength) / currentLengthData.addedLength, lengths[j - 1]); + this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); + // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); + newShape = false; + shapePath.c = false; } - return animations; + addedLength += currentLengthData.addedLength; + segmentCount += 1; } - - function addPlayingCount() { - playingAnimationsNum += 1; - activate(); + } + if (shapePaths[i].c && lengths.length) { + currentLengthData = lengths[j - 1]; + if (addedLength <= shapeSegment.e) { + var segmentLength = lengths[j - 1].addedLength; + if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + segmentLength) { + this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[0], shapePaths[i].v[0], shapePath, segmentCount, newShape); + newShape = false; + } else { + segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[0], shapePaths[i].o[j - 1], shapePaths[i].i[0], (shapeSegment.s - addedLength) / segmentLength, (shapeSegment.e - addedLength) / segmentLength, lengths[j - 1]); + this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); + // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); + newShape = false; + shapePath.c = false; + } + } else { + shapePath.c = false; } - - function subtractPlayingCount() { - playingAnimationsNum -= 1; + addedLength += currentLengthData.addedLength; + segmentCount += 1; + } + if (shapePath._length) { + shapePath.setXYAt(shapePath.v[initPos][0], shapePath.v[initPos][1], 'i', initPos); + shapePath.setXYAt(shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1], 'o', shapePath._length - 1); + } + if (addedLength > shapeSegment.e) { + break; + } + if (i < len - 1) { + shapePath = shapePool.newElement(); + newShape = true; + shapes.push(shapePath); + segmentCount = 0; + } + } + return shapes; +}; + +function PuckerAndBloatModifier() {} +extendPrototype([ShapeModifier], PuckerAndBloatModifier); +PuckerAndBloatModifier.prototype.initModifierProperties = function (elem, data) { + this.getValue = this.processKeys; + this.amount = PropertyFactory.getProp(elem, data.a, 0, null, this); + this._isAnimated = !!this.amount.effectsSequence.length; +}; + +PuckerAndBloatModifier.prototype.processPath = function (path, amount) { + var percent = amount / 100; + var centerPoint = [0, 0]; + var pathLength = path._length; + var i = 0; + for (i = 0; i < pathLength; i += 1) { + centerPoint[0] += path.v[i][0]; + centerPoint[1] += path.v[i][1]; + } + centerPoint[0] /= pathLength; + centerPoint[1] /= pathLength; + var clonedPath = shapePool.newElement(); + clonedPath.c = path.c; + var vX; + var vY; + var oX; + var oY; + var iX; + var iY; + for (i = 0; i < pathLength; i += 1) { + vX = path.v[i][0] + (centerPoint[0] - path.v[i][0]) * percent; + vY = path.v[i][1] + (centerPoint[1] - path.v[i][1]) * percent; + oX = path.o[i][0] + (centerPoint[0] - path.o[i][0]) * -percent; + oY = path.o[i][1] + (centerPoint[1] - path.o[i][1]) * -percent; + iX = path.i[i][0] + (centerPoint[0] - path.i[i][0]) * -percent; + iY = path.i[i][1] + (centerPoint[1] - path.i[i][1]) * -percent; + clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, i); + } + return clonedPath; +}; + +PuckerAndBloatModifier.prototype.processShapes = function (_isFirstFrame) { + var shapePaths; + var i; + var len = this.shapes.length; + var j; + var jLen; + var amount = this.amount.v; + + if (amount !== 0) { + var shapeData; + var localShapeCollection; + for (i = 0; i < len; i += 1) { + shapeData = this.shapes[i]; + localShapeCollection = shapeData.localShapeCollection; + if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { + localShapeCollection.releaseShapes(); + shapeData.shape._mdf = true; + shapePaths = shapeData.shape.paths.shapes; + jLen = shapeData.shape.paths._length; + for (j = 0; j < jLen; j += 1) { + localShapeCollection.addShape(this.processPath(shapePaths[j], amount)); + } } - - function setupAnimation(animItem, element) { - animItem.addEventListener('destroy', removeElement); - animItem.addEventListener('_active', addPlayingCount); - animItem.addEventListener('_idle', subtractPlayingCount); - registeredAnimations.push({ elem: element, animation: animItem }); - len += 1; + shapeData.shape.paths = shapeData.localShapeCollection; + } + } + if (!this.dynamicProperties.length) { + this._mdf = false; + } +}; + +const TransformPropertyFactory = (function () { + var defaultVector = [0, 0]; + + function applyToMatrix(mat) { + var _mdf = this._mdf; + this.iterateDynamicProperties(); + this._mdf = this._mdf || _mdf; + if (this.a) { + mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); + } + if (this.s) { + mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]); + } + if (this.sk) { + mat.skewFromAxis(-this.sk.v, this.sa.v); + } + if (this.r) { + mat.rotate(-this.r.v); + } else { + mat.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) + .rotateY(this.or.v[1]) + .rotateX(this.or.v[0]); + } + if (this.data.p.s) { + if (this.data.p.z) { + mat.translate(this.px.v, this.py.v, -this.pz.v); + } else { + mat.translate(this.px.v, this.py.v, 0); } + } else { + mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); + } + } + function processKeys(forceRender) { + if (this.elem.globalData.frameId === this.frameId) { + return; + } + if (this._isDirty) { + this.precalculateMatrix(); + this._isDirty = false; + } - function loadAnimation(params) { - var animItem = new AnimationItem(); - setupAnimation(animItem, null); - animItem.setParams(params); - return animItem; + this.iterateDynamicProperties(); + + if (this._mdf || forceRender) { + var frameRate; + this.v.cloneFromProps(this.pre.props); + if (this.appliedTransformations < 1) { + this.v.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); + } + if (this.appliedTransformations < 2) { + this.v.scale(this.s.v[0], this.s.v[1], this.s.v[2]); + } + if (this.sk && this.appliedTransformations < 3) { + this.v.skewFromAxis(-this.sk.v, this.sa.v); + } + if (this.r && this.appliedTransformations < 4) { + this.v.rotate(-this.r.v); + } else if (!this.r && this.appliedTransformations < 4) { + this.v.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) + .rotateY(this.or.v[1]) + .rotateX(this.or.v[0]); + } + if (this.autoOriented) { + var v1; + var v2; + frameRate = this.elem.globalData.frameRate; + if (this.p && this.p.keyframes && this.p.getValueAtTime) { + if (this.p._caching.lastFrame + this.p.offsetTime <= this.p.keyframes[0].t) { + v1 = this.p.getValueAtTime((this.p.keyframes[0].t + 0.01) / frameRate, 0); + v2 = this.p.getValueAtTime(this.p.keyframes[0].t / frameRate, 0); + } else if (this.p._caching.lastFrame + this.p.offsetTime >= this.p.keyframes[this.p.keyframes.length - 1].t) { + v1 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t / frameRate), 0); + v2 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t - 0.05) / frameRate, 0); + } else { + v1 = this.p.pv; + v2 = this.p.getValueAtTime((this.p._caching.lastFrame + this.p.offsetTime - 0.01) / frameRate, this.p.offsetTime); + } + } else if (this.px && this.px.keyframes && this.py.keyframes && this.px.getValueAtTime && this.py.getValueAtTime) { + v1 = []; + v2 = []; + var px = this.px; + var py = this.py; + if (px._caching.lastFrame + px.offsetTime <= px.keyframes[0].t) { + v1[0] = px.getValueAtTime((px.keyframes[0].t + 0.01) / frameRate, 0); + v1[1] = py.getValueAtTime((py.keyframes[0].t + 0.01) / frameRate, 0); + v2[0] = px.getValueAtTime((px.keyframes[0].t) / frameRate, 0); + v2[1] = py.getValueAtTime((py.keyframes[0].t) / frameRate, 0); + } else if (px._caching.lastFrame + px.offsetTime >= px.keyframes[px.keyframes.length - 1].t) { + v1[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t / frameRate), 0); + v1[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t / frameRate), 0); + v2[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t - 0.01) / frameRate, 0); + v2[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t - 0.01) / frameRate, 0); + } else { + v1 = [px.pv, py.pv]; + v2[0] = px.getValueAtTime((px._caching.lastFrame + px.offsetTime - 0.01) / frameRate, px.offsetTime); + v2[1] = py.getValueAtTime((py._caching.lastFrame + py.offsetTime - 0.01) / frameRate, py.offsetTime); + } + } else { + v2 = defaultVector; + v1 = v2; + } + this.v.rotate(-Math.atan2(v1[1] - v2[1], v1[0] - v2[0])); } - - function setSpeed(val, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.setSpeed(val, animation); + if (this.data.p && this.data.p.s) { + if (this.data.p.z) { + this.v.translate(this.px.v, this.py.v, -this.pz.v); + } else { + this.v.translate(this.px.v, this.py.v, 0); } + } else { + this.v.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); + } + } + this.frameId = this.elem.globalData.frameId; + } + + function precalculateMatrix() { + if (!this.a.k) { + this.pre.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); + this.appliedTransformations = 1; + } else { + return; + } + if (!this.s.effectsSequence.length) { + this.pre.scale(this.s.v[0], this.s.v[1], this.s.v[2]); + this.appliedTransformations = 2; + } else { + return; + } + if (this.sk) { + if (!this.sk.effectsSequence.length && !this.sa.effectsSequence.length) { + this.pre.skewFromAxis(-this.sk.v, this.sa.v); + this.appliedTransformations = 3; + } else { + return; } + } + if (this.r) { + if (!this.r.effectsSequence.length) { + this.pre.rotate(-this.r.v); + this.appliedTransformations = 4; + } + } else if (!this.rz.effectsSequence.length && !this.ry.effectsSequence.length && !this.rx.effectsSequence.length && !this.or.effectsSequence.length) { + this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) + .rotateY(this.or.v[1]) + .rotateX(this.or.v[0]); + this.appliedTransformations = 4; + } + } - function setDirection(val, animation) { + function autoOrient() { + // + // var prevP = this.getValueAtTime(); + } + + function addDynamicProperty(prop) { + this._addDynamicProperty(prop); + this.elem.addDynamicProperty(prop); + this._isDirty = true; + } + + function TransformProperty(elem, data, container) { + this.elem = elem; + this.frameId = -1; + this.propType = 'transform'; + this.data = data; + this.v = new Matrix(); + // Precalculated matrix with non animated properties + this.pre = new Matrix(); + this.appliedTransformations = 0; + this.initDynamicPropertyContainer(container || elem); + if (data.p && data.p.s) { + this.px = PropertyFactory.getProp(elem, data.p.x, 0, 0, this); + this.py = PropertyFactory.getProp(elem, data.p.y, 0, 0, this); + if (data.p.z) { + this.pz = PropertyFactory.getProp(elem, data.p.z, 0, 0, this); + } + } else { + this.p = PropertyFactory.getProp(elem, data.p || { k: [0, 0, 0] }, 1, 0, this); + } + if (data.rx) { + this.rx = PropertyFactory.getProp(elem, data.rx, 0, degToRads, this); + this.ry = PropertyFactory.getProp(elem, data.ry, 0, degToRads, this); + this.rz = PropertyFactory.getProp(elem, data.rz, 0, degToRads, this); + if (data.or.k[0].ti) { var i; + var len = data.or.k.length; for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.setDirection(val, animation); + data.or.k[i].to = null; + data.or.k[i].ti = null; } } + this.or = PropertyFactory.getProp(elem, data.or, 1, degToRads, this); + // sh Indicates it needs to be capped between -180 and 180 + this.or.sh = true; + } else { + this.r = PropertyFactory.getProp(elem, data.r || { k: 0 }, 0, degToRads, this); + } + if (data.sk) { + this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this); + this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this); + } + this.a = PropertyFactory.getProp(elem, data.a || { k: [0, 0, 0] }, 1, 0, this); + this.s = PropertyFactory.getProp(elem, data.s || { k: [100, 100, 100] }, 1, 0.01, this); + // Opacity is not part of the transform properties, that's why it won't use this.dynamicProperties. That way transforms won't get updated if opacity changes. + if (data.o) { + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, elem); + } else { + this.o = { _mdf: false, v: 1 }; + } + this._isDirty = true; + if (!this.dynamicProperties.length) { + this.getValue(true); + } + } + + TransformProperty.prototype = { + applyToMatrix: applyToMatrix, + getValue: processKeys, + precalculateMatrix: precalculateMatrix, + autoOrient: autoOrient, + }; + + extendPrototype([DynamicPropertyContainer], TransformProperty); + TransformProperty.prototype.addDynamicProperty = addDynamicProperty; + TransformProperty.prototype._addDynamicProperty = DynamicPropertyContainer.prototype.addDynamicProperty; + + function getTransformProperty(elem, data, container) { + return new TransformProperty(elem, data, container); + } + + return { + getTransformProperty: getTransformProperty, + }; +}()); + +function RepeaterModifier() {} +extendPrototype([ShapeModifier], RepeaterModifier); + +RepeaterModifier.prototype.initModifierProperties = function (elem, data) { + this.getValue = this.processKeys; + this.c = PropertyFactory.getProp(elem, data.c, 0, null, this); + this.o = PropertyFactory.getProp(elem, data.o, 0, null, this); + this.tr = TransformPropertyFactory.getTransformProperty(elem, data.tr, this); + this.so = PropertyFactory.getProp(elem, data.tr.so, 0, 0.01, this); + this.eo = PropertyFactory.getProp(elem, data.tr.eo, 0, 0.01, this); + this.data = data; + if (!this.dynamicProperties.length) { + this.getValue(true); + } + this._isAnimated = !!this.dynamicProperties.length; + this.pMatrix = new Matrix(); + this.rMatrix = new Matrix(); + this.sMatrix = new Matrix(); + this.tMatrix = new Matrix(); + this.matrix = new Matrix(); +}; + +RepeaterModifier.prototype.applyTransforms = function (pMatrix, rMatrix, sMatrix, transform, perc, inv) { + var dir = inv ? -1 : 1; + var scaleX = transform.s.v[0] + (1 - transform.s.v[0]) * (1 - perc); + var scaleY = transform.s.v[1] + (1 - transform.s.v[1]) * (1 - perc); + pMatrix.translate(transform.p.v[0] * dir * perc, transform.p.v[1] * dir * perc, transform.p.v[2]); + rMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); + rMatrix.rotate(-transform.r.v * dir * perc); + rMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); + sMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); + sMatrix.scale(inv ? 1 / scaleX : scaleX, inv ? 1 / scaleY : scaleY); + sMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); +}; + +RepeaterModifier.prototype.init = function (elem, arr, pos, elemsData) { + this.elem = elem; + this.arr = arr; + this.pos = pos; + this.elemsData = elemsData; + this._currentCopies = 0; + this._elements = []; + this._groups = []; + this.frameId = -1; + this.initDynamicPropertyContainer(elem); + this.initModifierProperties(elem, arr[pos]); + while (pos > 0) { + pos -= 1; + // this._elements.unshift(arr.splice(pos,1)[0]); + this._elements.unshift(arr[pos]); + } + if (this.dynamicProperties.length) { + this.k = true; + } else { + this.getValue(true); + } +}; + +RepeaterModifier.prototype.resetElements = function (elements) { + var i; + var len = elements.length; + for (i = 0; i < len; i += 1) { + elements[i]._processed = false; + if (elements[i].ty === 'gr') { + this.resetElements(elements[i].it); + } + } +}; + +RepeaterModifier.prototype.cloneElements = function (elements) { + var newElements = JSON.parse(JSON.stringify(elements)); + this.resetElements(newElements); + return newElements; +}; + +RepeaterModifier.prototype.changeGroupRender = function (elements, renderFlag) { + var i; + var len = elements.length; + for (i = 0; i < len; i += 1) { + elements[i]._render = renderFlag; + if (elements[i].ty === 'gr') { + this.changeGroupRender(elements[i].it, renderFlag); + } + } +}; + +RepeaterModifier.prototype.processShapes = function (_isFirstFrame) { + var items; + var itemsTransform; + var i; + var dir; + var cont; + var hasReloaded = false; + if (this._mdf || _isFirstFrame) { + var copies = Math.ceil(this.c.v); + if (this._groups.length < copies) { + while (this._groups.length < copies) { + var group = { + it: this.cloneElements(this._elements), + ty: 'gr', + }; + group.it.push({ + a: { a: 0, ix: 1, k: [0, 0] }, nm: 'Transform', o: { a: 0, ix: 7, k: 100 }, p: { a: 0, ix: 2, k: [0, 0] }, r: { a: 1, ix: 6, k: [{ s: 0, e: 0, t: 0 }, { s: 0, e: 0, t: 1 }] }, s: { a: 0, ix: 3, k: [100, 100] }, sa: { a: 0, ix: 5, k: 0 }, sk: { a: 0, ix: 4, k: 0 }, ty: 'tr', + }); - function play(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.play(animation); - } + this.arr.splice(0, 0, group); + this._groups.splice(0, 0, group); + this._currentCopies += 1; } - function resume(nowTime) { - var elapsedTime = nowTime - initTime; - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.advanceTime(elapsedTime); - } - initTime = nowTime; - if (playingAnimationsNum && !_isFrozen) { - window.requestAnimationFrame(resume); + this.elem.reloadShapes(); + hasReloaded = true; + } + cont = 0; + var renderFlag; + for (i = 0; i <= this._groups.length - 1; i += 1) { + renderFlag = cont < copies; + this._groups[i]._render = renderFlag; + this.changeGroupRender(this._groups[i].it, renderFlag); + if (!renderFlag) { + var elems = this.elemsData[i].it; + var transformData = elems[elems.length - 1]; + if (transformData.transform.op.v !== 0) { + transformData.transform.op._mdf = true; + transformData.transform.op.v = 0; } else { - _stopped = true; + transformData.transform.op._mdf = false; } } + cont += 1; + } - function first(nowTime) { - initTime = nowTime; - window.requestAnimationFrame(resume); + this._currentCopies = copies; + /// / + + var offset = this.o.v; + var offsetModulo = offset % 1; + var roundOffset = offset > 0 ? Math.floor(offset) : Math.ceil(offset); + var pProps = this.pMatrix.props; + var rProps = this.rMatrix.props; + var sProps = this.sMatrix.props; + this.pMatrix.reset(); + this.rMatrix.reset(); + this.sMatrix.reset(); + this.tMatrix.reset(); + this.matrix.reset(); + var iteration = 0; + + if (offset > 0) { + while (iteration < roundOffset) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); + iteration += 1; + } + if (offsetModulo) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, offsetModulo, false); + iteration += offsetModulo; + } + } else if (offset < 0) { + while (iteration > roundOffset) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, true); + iteration -= 1; + } + if (offsetModulo) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, -offsetModulo, true); + iteration -= offsetModulo; } + } + i = this.data.m === 1 ? 0 : this._currentCopies - 1; + dir = this.data.m === 1 ? 1 : -1; + cont = this._currentCopies; + var j; + var jLen; + while (cont) { + items = this.elemsData[i].it; + itemsTransform = items[items.length - 1].transform.mProps.v.props; + jLen = itemsTransform.length; + items[items.length - 1].transform.mProps._mdf = true; + items[items.length - 1].transform.op._mdf = true; + items[items.length - 1].transform.op.v = this._currentCopies === 1 + ? this.so.v + : this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1)); + + if (iteration !== 0) { + if ((i !== 0 && dir === 1) || (i !== this._currentCopies - 1 && dir === -1)) { + this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); + } + this.matrix.transform(rProps[0], rProps[1], rProps[2], rProps[3], rProps[4], rProps[5], rProps[6], rProps[7], rProps[8], rProps[9], rProps[10], rProps[11], rProps[12], rProps[13], rProps[14], rProps[15]); + this.matrix.transform(sProps[0], sProps[1], sProps[2], sProps[3], sProps[4], sProps[5], sProps[6], sProps[7], sProps[8], sProps[9], sProps[10], sProps[11], sProps[12], sProps[13], sProps[14], sProps[15]); + this.matrix.transform(pProps[0], pProps[1], pProps[2], pProps[3], pProps[4], pProps[5], pProps[6], pProps[7], pProps[8], pProps[9], pProps[10], pProps[11], pProps[12], pProps[13], pProps[14], pProps[15]); - function pause(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.pause(animation); + for (j = 0; j < jLen; j += 1) { + itemsTransform[j] = this.matrix.props[j]; + } + this.matrix.reset(); + } else { + this.matrix.reset(); + for (j = 0; j < jLen; j += 1) { + itemsTransform[j] = this.matrix.props[j]; } } - - function goToAndStop(value, isFrame, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.goToAndStop(value, isFrame, animation); + iteration += 1; + cont -= 1; + i += dir; + } + } else { + cont = this._currentCopies; + i = 0; + dir = 1; + while (cont) { + items = this.elemsData[i].it; + itemsTransform = items[items.length - 1].transform.mProps.v.props; + items[items.length - 1].transform.mProps._mdf = false; + items[items.length - 1].transform.op._mdf = false; + cont -= 1; + i += dir; + } + } + return hasReloaded; +}; + +RepeaterModifier.prototype.addShape = function () {}; + +function RoundCornersModifier() {} +extendPrototype([ShapeModifier], RoundCornersModifier); +RoundCornersModifier.prototype.initModifierProperties = function (elem, data) { + this.getValue = this.processKeys; + this.rd = PropertyFactory.getProp(elem, data.r, 0, null, this); + this._isAnimated = !!this.rd.effectsSequence.length; +}; + +RoundCornersModifier.prototype.processPath = function (path, round) { + var clonedPath = shapePool.newElement(); + clonedPath.c = path.c; + var i; + var len = path._length; + var currentV; + var currentI; + var currentO; + var closerV; + var distance; + var newPosPerc; + var index = 0; + var vX; + var vY; + var oX; + var oY; + var iX; + var iY; + for (i = 0; i < len; i += 1) { + currentV = path.v[i]; + currentO = path.o[i]; + currentI = path.i[i]; + if (currentV[0] === currentO[0] && currentV[1] === currentO[1] && currentV[0] === currentI[0] && currentV[1] === currentI[1]) { + if ((i === 0 || i === len - 1) && !path.c) { + clonedPath.setTripleAt(currentV[0], currentV[1], currentO[0], currentO[1], currentI[0], currentI[1], index); + /* clonedPath.v[index] = currentV; + clonedPath.o[index] = currentO; + clonedPath.i[index] = currentI; */ + index += 1; + } else { + if (i === 0) { + closerV = path.v[len - 1]; + } else { + closerV = path.v[i - 1]; + } + distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); + newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; + iX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; + vX = iX; + iY = currentV[1] - (currentV[1] - closerV[1]) * newPosPerc; + vY = iY; + oX = vX - (vX - currentV[0]) * roundCorner; + oY = vY - (vY - currentV[1]) * roundCorner; + clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); + index += 1; + + if (i === len - 1) { + closerV = path.v[0]; + } else { + closerV = path.v[i + 1]; + } + distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); + newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; + oX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; + vX = oX; + oY = currentV[1] + (closerV[1] - currentV[1]) * newPosPerc; + vY = oY; + iX = vX - (vX - currentV[0]) * roundCorner; + iY = vY - (vY - currentV[1]) * roundCorner; + clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); + index += 1; + } + } else { + clonedPath.setTripleAt(path.v[i][0], path.v[i][1], path.o[i][0], path.o[i][1], path.i[i][0], path.i[i][1], index); + index += 1; + } + } + return clonedPath; +}; + +RoundCornersModifier.prototype.processShapes = function (_isFirstFrame) { + var shapePaths; + var i; + var len = this.shapes.length; + var j; + var jLen; + var rd = this.rd.v; + + if (rd !== 0) { + var shapeData; + var localShapeCollection; + for (i = 0; i < len; i += 1) { + shapeData = this.shapes[i]; + localShapeCollection = shapeData.localShapeCollection; + if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { + localShapeCollection.releaseShapes(); + shapeData.shape._mdf = true; + shapePaths = shapeData.shape.paths.shapes; + jLen = shapeData.shape.paths._length; + for (j = 0; j < jLen; j += 1) { + localShapeCollection.addShape(this.processPath(shapePaths[j], rd)); } } - - function stop(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.stop(animation); + shapeData.shape.paths = shapeData.localShapeCollection; + } + } + if (!this.dynamicProperties.length) { + this._mdf = false; + } +}; + +function getFontProperties(fontData) { + var styles = fontData.fStyle ? fontData.fStyle.split(' ') : []; + + var fWeight = 'normal'; var + fStyle = 'normal'; + var len = styles.length; + var styleName; + for (var i = 0; i < len; i += 1) { + styleName = styles[i].toLowerCase(); + switch (styleName) { + case 'italic': + fStyle = 'italic'; + break; + case 'bold': + fWeight = '700'; + break; + case 'black': + fWeight = '900'; + break; + case 'medium': + fWeight = '500'; + break; + case 'regular': + case 'normal': + fWeight = '400'; + break; + case 'light': + case 'thin': + fWeight = '200'; + break; + default: + break; + } + } + + return { + style: fStyle, + weight: fontData.fWeight || fWeight, + }; +} + +const FontManager = (function () { + var maxWaitingTime = 5000; + var emptyChar = { + w: 0, + size: 0, + shapes: [], + data: { + shapes: [], + }, + }; + var combinedCharacters = []; + // Hindi characters + combinedCharacters = combinedCharacters.concat([2304, 2305, 2306, 2307, 2362, 2363, 2364, 2364, 2366, + 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, + 2380, 2381, 2382, 2383, 2387, 2388, 2389, 2390, 2391, 2402, 2403]); + + var surrogateModifiers = [ + 'd83cdffb', + 'd83cdffc', + 'd83cdffd', + 'd83cdffe', + 'd83cdfff', + ]; + + var zeroWidthJoiner = [65039, 8205]; + + function trimFontOptions(font) { + var familyArray = font.split(','); + var i; + var len = familyArray.length; + var enabledFamilies = []; + for (i = 0; i < len; i += 1) { + if (familyArray[i] !== 'sans-serif' && familyArray[i] !== 'monospace') { + enabledFamilies.push(familyArray[i]); + } + } + return enabledFamilies.join(','); + } + + function setUpNode(font, family) { + var parentNode = createTag('span'); + // Node is invisible to screen readers. + parentNode.setAttribute('aria-hidden', true); + parentNode.style.fontFamily = family; + var node = createTag('span'); + // Characters that vary significantly among different fonts + node.innerText = 'giItT1WQy@!-/#'; + // Visible - so we can measure it - but not on the screen + parentNode.style.position = 'absolute'; + parentNode.style.left = '-10000px'; + parentNode.style.top = '-10000px'; + // Large font size makes even subtle changes obvious + parentNode.style.fontSize = '300px'; + // Reset any font properties + parentNode.style.fontVariant = 'normal'; + parentNode.style.fontStyle = 'normal'; + parentNode.style.fontWeight = 'normal'; + parentNode.style.letterSpacing = '0'; + parentNode.appendChild(node); + document.body.appendChild(parentNode); + + // Remember width with no applied web font + var width = node.offsetWidth; + node.style.fontFamily = trimFontOptions(font) + ', ' + family; + return { node: node, w: width, parent: parentNode }; + } + + function checkLoadedFonts() { + var i; + var len = this.fonts.length; + var node; + var w; + var loadedCount = len; + for (i = 0; i < len; i += 1) { + if (this.fonts[i].loaded) { + loadedCount -= 1; + } else if (this.fonts[i].fOrigin === 'n' || this.fonts[i].origin === 0) { + this.fonts[i].loaded = true; + } else { + node = this.fonts[i].monoCase.node; + w = this.fonts[i].monoCase.w; + if (node.offsetWidth !== w) { + loadedCount -= 1; + this.fonts[i].loaded = true; + } else { + node = this.fonts[i].sansCase.node; + w = this.fonts[i].sansCase.w; + if (node.offsetWidth !== w) { + loadedCount -= 1; + this.fonts[i].loaded = true; + } + } + if (this.fonts[i].loaded) { + this.fonts[i].sansCase.parent.parentNode.removeChild(this.fonts[i].sansCase.parent); + this.fonts[i].monoCase.parent.parentNode.removeChild(this.fonts[i].monoCase.parent); } } + } - function togglePause(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.togglePause(animation); - } + if (loadedCount !== 0 && Date.now() - this.initTime < maxWaitingTime) { + setTimeout(this.checkLoadedFontsBinded, 20); + } else { + setTimeout(this.setIsLoadedBinded, 10); + } + } + + function createHelper(fontData, def) { + var engine = (document.body && def) ? 'svg' : 'canvas'; + var helper; + var fontProps = getFontProperties(fontData); + if (engine === 'svg') { + var tHelper = createNS('text'); + tHelper.style.fontSize = '100px'; + // tHelper.style.fontFamily = fontData.fFamily; + tHelper.setAttribute('font-family', fontData.fFamily); + tHelper.setAttribute('font-style', fontProps.style); + tHelper.setAttribute('font-weight', fontProps.weight); + tHelper.textContent = '1'; + if (fontData.fClass) { + tHelper.style.fontFamily = 'inherit'; + tHelper.setAttribute('class', fontData.fClass); + } else { + tHelper.style.fontFamily = fontData.fFamily; + } + def.appendChild(tHelper); + helper = tHelper; + } else { + var tCanvasHelper = new OffscreenCanvas(500, 500).getContext('2d'); + tCanvasHelper.font = fontProps.style + ' ' + fontProps.weight + ' 100px ' + fontData.fFamily; + helper = tCanvasHelper; + } + function measure(text) { + if (engine === 'svg') { + helper.textContent = text; + return helper.getComputedTextLength(); } + return helper.measureText(text).width; + } + return { + measureText: measure, + }; + } - function destroy(animation) { - var i; - for (i = (len - 1); i >= 0; i -= 1) { - registeredAnimations[i].animation.destroy(animation); + function addFonts(fontData, defs) { + if (!fontData) { + this.isLoaded = true; + return; + } + if (this.chars) { + this.isLoaded = true; + this.fonts = fontData.list; + return; + } + if (!document.body) { + this.isLoaded = true; + fontData.list.forEach((data) => { + data.helper = createHelper(data); + data.cache = {}; + }); + this.fonts = fontData.list; + return; + } + + var fontArr = fontData.list; + var i; + var len = fontArr.length; + var _pendingFonts = len; + for (i = 0; i < len; i += 1) { + var shouldLoadFont = true; + var loadedSelector; + var j; + fontArr[i].loaded = false; + fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace'); + fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif'); + if (!fontArr[i].fPath) { + fontArr[i].loaded = true; + _pendingFonts -= 1; + } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) { + loadedSelector = document.querySelectorAll('style[f-forigin="p"][f-family="' + fontArr[i].fFamily + '"], style[f-origin="3"][f-family="' + fontArr[i].fFamily + '"]'); + + if (loadedSelector.length > 0) { + shouldLoadFont = false; } - } - function searchAnimations(animationData, standalone, renderer) { - var animElements = [].concat([].slice.call(document.getElementsByClassName('lottie')), - [].slice.call(document.getElementsByClassName('bodymovin'))); - var i; - var lenAnims = animElements.length; - for (i = 0; i < lenAnims; i += 1) { - if (renderer) { - animElements[i].setAttribute('data-bm-type', renderer); - } - registerAnimation(animElements[i], animationData); + if (shouldLoadFont) { + var s = createTag('style'); + s.setAttribute('f-forigin', fontArr[i].fOrigin); + s.setAttribute('f-origin', fontArr[i].origin); + s.setAttribute('f-family', fontArr[i].fFamily); + s.type = 'text/css'; + s.innerText = '@font-face {font-family: ' + fontArr[i].fFamily + "; font-style: normal; src: url('" + fontArr[i].fPath + "');}"; + defs.appendChild(s); } - if (standalone && lenAnims === 0) { - if (!renderer) { - renderer = 'svg'; + } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) { + loadedSelector = document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'); + + for (j = 0; j < loadedSelector.length; j += 1) { + if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) { + // Font is already loaded + shouldLoadFont = false; } - var body = document.getElementsByTagName('body')[0]; - body.innerText = ''; - var div = createTag('div'); - div.style.width = '100%'; - div.style.height = '100%'; - div.setAttribute('data-bm-type', renderer); - body.appendChild(div); - registerAnimation(div, animationData); } - } - function resize() { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.resize(); + if (shouldLoadFont) { + var l = createTag('link'); + l.setAttribute('f-forigin', fontArr[i].fOrigin); + l.setAttribute('f-origin', fontArr[i].origin); + l.type = 'text/css'; + l.rel = 'stylesheet'; + l.href = fontArr[i].fPath; + document.body.appendChild(l); } - } + } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) { + loadedSelector = document.querySelectorAll('script[f-forigin="t"], script[f-origin="2"]'); - function activate() { - if (!_isFrozen && playingAnimationsNum) { - if (_stopped) { - window.requestAnimationFrame(first); - _stopped = false; + for (j = 0; j < loadedSelector.length; j += 1) { + if (fontArr[i].fPath === loadedSelector[j].src) { + // Font is already loaded + shouldLoadFont = false; } } - } - function freeze() { - _isFrozen = true; + if (shouldLoadFont) { + var sc = createTag('link'); + sc.setAttribute('f-forigin', fontArr[i].fOrigin); + sc.setAttribute('f-origin', fontArr[i].origin); + sc.setAttribute('rel', 'stylesheet'); + sc.setAttribute('href', fontArr[i].fPath); + defs.appendChild(sc); + } } + fontArr[i].helper = createHelper(fontArr[i], defs); + fontArr[i].cache = {}; + this.fonts.push(fontArr[i]); + } + if (_pendingFonts === 0) { + this.isLoaded = true; + } else { + // On some cases even if the font is loaded, it won't load correctly when measuring text on canvas. + // Adding this timeout seems to fix it + setTimeout(this.checkLoadedFonts.bind(this), 100); + } + } - function unfreeze() { - _isFrozen = false; - activate(); + function addChars(chars) { + if (!chars) { + return; + } + if (!this.chars) { + this.chars = []; + } + var i; + var len = chars.length; + var j; + var jLen = this.chars.length; + var found; + for (i = 0; i < len; i += 1) { + j = 0; + found = false; + while (j < jLen) { + if (this.chars[j].style === chars[i].style && this.chars[j].fFamily === chars[i].fFamily && this.chars[j].ch === chars[i].ch) { + found = true; + } + j += 1; + } + if (!found) { + this.chars.push(chars[i]); + jLen += 1; } + } + } - function setVolume(val, animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.setVolume(val, animation); - } + function getCharData(char, style, font) { + var i = 0; + var len = this.chars.length; + while (i < len) { + if (this.chars[i].ch === char && this.chars[i].style === style && this.chars[i].fFamily === font) { + return this.chars[i]; } - - function mute(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.mute(animation); - } + i += 1; + } + if (((typeof char === 'string' && char.charCodeAt(0) !== 13) || !char) + && console + && console.warn // eslint-disable-line no-console + && !this._warned + ) { + this._warned = true; + console.warn('Missing character from exported characters list: ', char, style, font); // eslint-disable-line no-console + } + return emptyChar; + } + + function measureText(char, fontName, size) { + var fontData = this.getFontByName(fontName); + var index = char.charCodeAt(0); + if (!fontData.cache[index + 1]) { + var tHelper = fontData.helper; + if (char === ' ') { + var doubleSize = tHelper.measureText('|' + char + '|'); + var singleSize = tHelper.measureText('||'); + fontData.cache[index + 1] = (doubleSize - singleSize) / 100; + } else { + fontData.cache[index + 1] = tHelper.measureText(char) / 100; } + } + return fontData.cache[index + 1] * size; + } - function unmute(animation) { - var i; - for (i = 0; i < len; i += 1) { - registeredAnimations[i].animation.unmute(animation); - } - } - - moduleOb.registerAnimation = registerAnimation; - moduleOb.loadAnimation = loadAnimation; - moduleOb.setSpeed = setSpeed; - moduleOb.setDirection = setDirection; - moduleOb.play = play; - moduleOb.pause = pause; - moduleOb.stop = stop; - moduleOb.togglePause = togglePause; - moduleOb.searchAnimations = searchAnimations; - moduleOb.resize = resize; - // moduleOb.start = start; - moduleOb.goToAndStop = goToAndStop; - moduleOb.destroy = destroy; - moduleOb.freeze = freeze; - moduleOb.unfreeze = unfreeze; - moduleOb.setVolume = setVolume; - moduleOb.mute = mute; - moduleOb.unmute = unmute; - moduleOb.getRegisteredAnimations = getRegisteredAnimations; - return moduleOb; - }()); - - /* eslint-disable */ - const BezierFactory = (function () { - /** - * BezierEasing - use bezier curve for transition easing function - * by Gaëtan Renaudeau 2014 - 2015 – MIT License - * - * Credits: is based on Firefox's nsSMILKeySpline.cpp - * Usage: - * var spline = BezierEasing([ 0.25, 0.1, 0.25, 1.0 ]) - * spline.get(x) => returns the easing value | x must be in [0, 1] range - * - */ - - var ob = {}; - ob.getBezierEasing = getBezierEasing; - var beziers = {}; - - function getBezierEasing(a, b, c, d, nm) { - var str = nm || ('bez_' + a + '_' + b + '_' + c + '_' + d).replace(/\./g, 'p'); - if (beziers[str]) { - return beziers[str]; - } - var bezEasing = new BezierEasing([a, b, c, d]); - beziers[str] = bezEasing; - return bezEasing; + function getFontByName(name) { + var i = 0; + var len = this.fonts.length; + while (i < len) { + if (this.fonts[i].fName === name) { + return this.fonts[i]; } + i += 1; + } + return this.fonts[0]; + } - // These values are established by empiricism with tests (tradeoff: performance VS precision) - var NEWTON_ITERATIONS = 4; - var NEWTON_MIN_SLOPE = 0.001; - var SUBDIVISION_PRECISION = 0.0000001; - var SUBDIVISION_MAX_ITERATIONS = 10; - - var kSplineTableSize = 11; - var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); - - var float32ArraySupported = typeof Float32Array === 'function'; - - function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } - function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } - function C(aA1) { return 3.0 * aA1; } + function isModifier(firstCharCode, secondCharCode) { + var sum = firstCharCode.toString(16) + secondCharCode.toString(16); + return surrogateModifiers.indexOf(sum) !== -1; + } - // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. - function calcBezier(aT, aA1, aA2) { - return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; - } + function isZeroWidthJoiner(firstCharCode, secondCharCode) { + if (!secondCharCode) { + return firstCharCode === zeroWidthJoiner[1]; + } + return firstCharCode === zeroWidthJoiner[0] && secondCharCode === zeroWidthJoiner[1]; + } + + function isCombinedCharacter(char) { + return combinedCharacters.indexOf(char) !== -1; + } + + function setIsLoaded() { + this.isLoaded = true; + } + + var Font = function () { + this.fonts = []; + this.chars = null; + this.typekitLoaded = 0; + this.isLoaded = false; + this._warned = false; + this.initTime = Date.now(); + this.setIsLoadedBinded = this.setIsLoaded.bind(this); + this.checkLoadedFontsBinded = this.checkLoadedFonts.bind(this); + }; + Font.isModifier = isModifier; + Font.isZeroWidthJoiner = isZeroWidthJoiner; + Font.isCombinedCharacter = isCombinedCharacter; + + var fontPrototype = { + addChars: addChars, + addFonts: addFonts, + getCharData: getCharData, + getFontByName: getFontByName, + measureText: measureText, + checkLoadedFonts: checkLoadedFonts, + setIsLoaded: setIsLoaded, + }; + + Font.prototype = fontPrototype; + + return Font; +}()); + +function RenderableElement() { + +} + +RenderableElement.prototype = { + initRenderable: function () { + // layer's visibility related to inpoint and outpoint. Rename isVisible to isInRange + this.isInRange = false; + // layer's display state + this.hidden = false; + // If layer's transparency equals 0, it can be hidden + this.isTransparent = false; + // list of animated components + this.renderableComponents = []; + }, + addRenderableComponent: function (component) { + if (this.renderableComponents.indexOf(component) === -1) { + this.renderableComponents.push(component); + } + }, + removeRenderableComponent: function (component) { + if (this.renderableComponents.indexOf(component) !== -1) { + this.renderableComponents.splice(this.renderableComponents.indexOf(component), 1); + } + }, + prepareRenderableFrame: function (num) { + this.checkLayerLimits(num); + }, + checkTransparency: function () { + if (this.finalTransform.mProp.o.v <= 0) { + if (!this.isTransparent && this.globalData.renderConfig.hideOnTransparent) { + this.isTransparent = true; + this.hide(); + } + } else if (this.isTransparent) { + this.isTransparent = false; + this.show(); + } + }, + /** + * @function + * Initializes frame related properties. + * + * @param {number} num + * current frame number in Layer's time + * + */ + checkLayerLimits: function (num) { + if (this.data.ip - this.data.st <= num && this.data.op - this.data.st > num) { + if (this.isInRange !== true) { + this.globalData._mdf = true; + this._mdf = true; + this.isInRange = true; + this.show(); + } + } else if (this.isInRange !== false) { + this.globalData._mdf = true; + this.isInRange = false; + this.hide(); + } + }, + renderRenderable: function () { + var i; + var len = this.renderableComponents.length; + for (i = 0; i < len; i += 1) { + this.renderableComponents[i].renderFrame(this._isFirstFrame); + } + /* this.maskManager.renderFrame(this.finalTransform.mat); + this.renderableEffectsManager.renderFrame(this._isFirstFrame); */ + }, + sourceRectAtTime: function () { + return { + top: 0, + left: 0, + width: 100, + height: 100, + }; + }, + getLayerSize: function () { + if (this.data.ty === 5) { + return { w: this.data.textData.width, h: this.data.textData.height }; + } + return { w: this.data.width, h: this.data.height }; + }, +}; + +const MaskManagerInterface = (function () { + function MaskInterface(mask, data) { + this._mask = mask; + this._data = data; + } + Object.defineProperty(MaskInterface.prototype, 'maskPath', { + get: function () { + if (this._mask.prop.k) { + this._mask.prop.getValue(); + } + return this._mask.prop; + }, + }); + Object.defineProperty(MaskInterface.prototype, 'maskOpacity', { + get: function () { + if (this._mask.op.k) { + this._mask.op.getValue(); + } + return this._mask.op.v * 100; + }, + }); + + var MaskManager = function (maskManager) { + var _masksInterfaces = createSizedArray(maskManager.viewData.length); + var i; + var len = maskManager.viewData.length; + for (i = 0; i < len; i += 1) { + _masksInterfaces[i] = new MaskInterface(maskManager.viewData[i], maskManager.masksProperties[i]); + } - // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. - function getSlope(aT, aA1, aA2) { - return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); + var maskFunction = function (name) { + i = 0; + while (i < len) { + if (maskManager.masksProperties[i].nm === name) { + return _masksInterfaces[i]; + } + i += 1; } + return null; + }; + return maskFunction; + }; + return MaskManager; +}()); - function binarySubdivide(aX, aA, aB, mX1, mX2) { - var currentX, - currentT, - i = 0; - do { - currentT = aA + (aB - aA) / 2.0; - currentX = calcBezier(currentT, mX1, mX2) - aX; - if (currentX > 0.0) { - aB = currentT; - } else { - aA = currentT; - } - } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); - return currentT; - } +const ExpressionPropertyInterface = (function () { + var defaultUnidimensionalValue = { pv: 0, v: 0, mult: 1 }; + var defaultMultidimensionalValue = { pv: [0, 0, 0], v: [0, 0, 0], mult: 1 }; - function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { - for (var i = 0; i < NEWTON_ITERATIONS; ++i) { - var currentSlope = getSlope(aGuessT, mX1, mX2); - if (currentSlope === 0.0) return aGuessT; - var currentX = calcBezier(aGuessT, mX1, mX2) - aX; - aGuessT -= currentX / currentSlope; - } - return aGuessT; + function completeProperty(expressionValue, property, type) { + Object.defineProperty(expressionValue, 'velocity', { + get: function () { + return property.getVelocityAtTime(property.comp.currentFrame); + }, + }); + expressionValue.numKeys = property.keyframes ? property.keyframes.length : 0; + expressionValue.key = function (pos) { + if (!expressionValue.numKeys) { + return 0; + } + var value = ''; + if ('s' in property.keyframes[pos - 1]) { + value = property.keyframes[pos - 1].s; + } else if ('e' in property.keyframes[pos - 2]) { + value = property.keyframes[pos - 2].e; + } else { + value = property.keyframes[pos - 2].s; + } + var valueProp = type === 'unidimensional' ? new Number(value) : Object.assign({}, value); // eslint-disable-line no-new-wrappers + valueProp.time = property.keyframes[pos - 1].t / property.elem.comp.globalData.frameRate; + valueProp.value = type === 'unidimensional' ? value[0] : value; + return valueProp; + }; + expressionValue.valueAtTime = property.getValueAtTime; + expressionValue.speedAtTime = property.getSpeedAtTime; + expressionValue.velocityAtTime = property.getVelocityAtTime; + expressionValue.propertyGroup = property.propertyGroup; + } + + function UnidimensionalPropertyInterface(property) { + if (!property || !('pv' in property)) { + property = defaultUnidimensionalValue; + } + var mult = 1 / property.mult; + var val = property.pv * mult; + var expressionValue = new Number(val); // eslint-disable-line no-new-wrappers + expressionValue.value = val; + completeProperty(expressionValue, property, 'unidimensional'); + + return function () { + if (property.k) { + property.getValue(); + } + val = property.v * mult; + if (expressionValue.value !== val) { + expressionValue = new Number(val); // eslint-disable-line no-new-wrappers + expressionValue.value = val; + completeProperty(expressionValue, property, 'unidimensional'); } + return expressionValue; + }; + } - /** - * points is an array of [ mX1, mY1, mX2, mY2 ] - */ - function BezierEasing(points) { - this._p = points; - this._mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); - this._precomputed = false; - - this.get = this.get.bind(this); - } - - BezierEasing.prototype = { - - get: function (x) { - var mX1 = this._p[0], - mY1 = this._p[1], - mX2 = this._p[2], - mY2 = this._p[3]; - if (!this._precomputed) this._precompute(); - if (mX1 === mY1 && mX2 === mY2) return x; // linear - // Because JavaScript number are imprecise, we should guarantee the extremes are right. - if (x === 0) return 0; - if (x === 1) return 1; - return calcBezier(this._getTForX(x), mY1, mY2); - }, + function MultidimensionalPropertyInterface(property) { + if (!property || !('pv' in property)) { + property = defaultMultidimensionalValue; + } + var mult = 1 / property.mult; + var len = (property.data && property.data.l) || property.pv.length; + var expressionValue = createTypedArray('float32', len); + var arrValue = createTypedArray('float32', len); + expressionValue.value = arrValue; + completeProperty(expressionValue, property, 'multidimensional'); + + return function () { + if (property.k) { + property.getValue(); + } + for (var i = 0; i < len; i += 1) { + arrValue[i] = property.v[i] * mult; + expressionValue[i] = arrValue[i]; + } + return expressionValue; + }; + } - // Private part + // TODO: try to avoid using this getter + function defaultGetter() { + return defaultUnidimensionalValue; + } - _precompute: function () { - var mX1 = this._p[0], - mY1 = this._p[1], - mX2 = this._p[2], - mY2 = this._p[3]; - this._precomputed = true; - if (mX1 !== mY1 || mX2 !== mY2) { this._calcSampleValues(); } - }, + return function (property) { + if (!property) { + return defaultGetter; + } if (property.propType === 'unidimensional') { + return UnidimensionalPropertyInterface(property); + } + return MultidimensionalPropertyInterface(property); + }; +}()); - _calcSampleValues: function () { - var mX1 = this._p[0], - mX2 = this._p[2]; - for (var i = 0; i < kSplineTableSize; ++i) { - this._mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); - } - }, +const TransformExpressionInterface = (function () { + return function (transform) { + function _thisFunction(name) { + switch (name) { + case 'scale': + case 'Scale': + case 'ADBE Scale': + case 6: + return _thisFunction.scale; + case 'rotation': + case 'Rotation': + case 'ADBE Rotation': + case 'ADBE Rotate Z': + case 10: + return _thisFunction.rotation; + case 'ADBE Rotate X': + return _thisFunction.xRotation; + case 'ADBE Rotate Y': + return _thisFunction.yRotation; + case 'position': + case 'Position': + case 'ADBE Position': + case 2: + return _thisFunction.position; + case 'ADBE Position_0': + return _thisFunction.xPosition; + case 'ADBE Position_1': + return _thisFunction.yPosition; + case 'ADBE Position_2': + return _thisFunction.zPosition; + case 'anchorPoint': + case 'AnchorPoint': + case 'Anchor Point': + case 'ADBE AnchorPoint': + case 1: + return _thisFunction.anchorPoint; + case 'opacity': + case 'Opacity': + case 11: + return _thisFunction.opacity; + default: + return null; + } + } + Object.defineProperty(_thisFunction, 'rotation', { + get: ExpressionPropertyInterface(transform.r || transform.rz), + }); + + Object.defineProperty(_thisFunction, 'zRotation', { + get: ExpressionPropertyInterface(transform.rz || transform.r), + }); + + Object.defineProperty(_thisFunction, 'xRotation', { + get: ExpressionPropertyInterface(transform.rx), + }); + + Object.defineProperty(_thisFunction, 'yRotation', { + get: ExpressionPropertyInterface(transform.ry), + }); + Object.defineProperty(_thisFunction, 'scale', { + get: ExpressionPropertyInterface(transform.s), + }); + var _px; + var _py; + var _pz; + var _transformFactory; + if (transform.p) { + _transformFactory = ExpressionPropertyInterface(transform.p); + } else { + _px = ExpressionPropertyInterface(transform.px); + _py = ExpressionPropertyInterface(transform.py); + if (transform.pz) { + _pz = ExpressionPropertyInterface(transform.pz); + } + } + Object.defineProperty(_thisFunction, 'position', { + get: function () { + if (transform.p) { + return _transformFactory(); + } + return [ + _px(), + _py(), + _pz ? _pz() : 0]; + }, + }); + + Object.defineProperty(_thisFunction, 'xPosition', { + get: ExpressionPropertyInterface(transform.px), + }); + + Object.defineProperty(_thisFunction, 'yPosition', { + get: ExpressionPropertyInterface(transform.py), + }); + + Object.defineProperty(_thisFunction, 'zPosition', { + get: ExpressionPropertyInterface(transform.pz), + }); + + Object.defineProperty(_thisFunction, 'anchorPoint', { + get: ExpressionPropertyInterface(transform.a), + }); + + Object.defineProperty(_thisFunction, 'opacity', { + get: ExpressionPropertyInterface(transform.o), + }); + + Object.defineProperty(_thisFunction, 'skew', { + get: ExpressionPropertyInterface(transform.sk), + }); + + Object.defineProperty(_thisFunction, 'skewAxis', { + get: ExpressionPropertyInterface(transform.sa), + }); + + Object.defineProperty(_thisFunction, 'orientation', { + get: ExpressionPropertyInterface(transform.or), + }); + + return _thisFunction; + }; +}()); + +const LayerExpressionInterface = (function () { + function getMatrix(time) { + var toWorldMat = new Matrix(); + if (time !== undefined) { + var propMatrix = this._elem.finalTransform.mProp.getValueAtTime(time); + propMatrix.clone(toWorldMat); + } else { + var transformMat = this._elem.finalTransform.mProp; + transformMat.applyToMatrix(toWorldMat); + } + return toWorldMat; + } + + function toWorldVec(arr, time) { + var toWorldMat = this.getMatrix(time); + toWorldMat.props[12] = 0; + toWorldMat.props[13] = 0; + toWorldMat.props[14] = 0; + return this.applyPoint(toWorldMat, arr); + } + + function toWorld(arr, time) { + var toWorldMat = this.getMatrix(time); + return this.applyPoint(toWorldMat, arr); + } + + function fromWorldVec(arr, time) { + var toWorldMat = this.getMatrix(time); + toWorldMat.props[12] = 0; + toWorldMat.props[13] = 0; + toWorldMat.props[14] = 0; + return this.invertPoint(toWorldMat, arr); + } + + function fromWorld(arr, time) { + var toWorldMat = this.getMatrix(time); + return this.invertPoint(toWorldMat, arr); + } + + function applyPoint(matrix, arr) { + if (this._elem.hierarchy && this._elem.hierarchy.length) { + var i; + var len = this._elem.hierarchy.length; + for (i = 0; i < len; i += 1) { + this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); + } + } + return matrix.applyToPointArray(arr[0], arr[1], arr[2] || 0); + } - /** - * getTForX chose the fastest heuristic to determine the percentage value precisely from a given X projection. - */ - _getTForX: function (aX) { - var mX1 = this._p[0], - mX2 = this._p[2], - mSampleValues = this._mSampleValues; + function invertPoint(matrix, arr) { + if (this._elem.hierarchy && this._elem.hierarchy.length) { + var i; + var len = this._elem.hierarchy.length; + for (i = 0; i < len; i += 1) { + this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); + } + } + return matrix.inversePoint(arr); + } + + function fromComp(arr) { + var toWorldMat = new Matrix(); + toWorldMat.reset(); + this._elem.finalTransform.mProp.applyToMatrix(toWorldMat); + if (this._elem.hierarchy && this._elem.hierarchy.length) { + var i; + var len = this._elem.hierarchy.length; + for (i = 0; i < len; i += 1) { + this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(toWorldMat); + } + return toWorldMat.inversePoint(arr); + } + return toWorldMat.inversePoint(arr); + } - var intervalStart = 0.0; - var currentSample = 1; - var lastSample = kSplineTableSize - 1; + function sampleImage() { + return [1, 1, 1, 1]; + } - for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { - intervalStart += kSampleStepSize; - } - --currentSample; + return function (elem) { + var transformInterface; - // Interpolate to provide an initial guess for t - var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]); - var guessForT = intervalStart + dist * kSampleStepSize; + function _registerMaskInterface(maskManager) { + _thisLayerFunction.mask = new MaskManagerInterface(maskManager, elem); + } + function _registerEffectsInterface(effects) { + _thisLayerFunction.effect = effects; + } - var initialSlope = getSlope(guessForT, mX1, mX2); - if (initialSlope >= NEWTON_MIN_SLOPE) { - return newtonRaphsonIterate(aX, guessForT, mX1, mX2); - } if (initialSlope === 0.0) { - return guessForT; - } - return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); + function _thisLayerFunction(name) { + switch (name) { + case 'ADBE Root Vectors Group': + case 'Contents': + case 2: + return _thisLayerFunction.shapeInterface; + case 1: + case 6: + case 'Transform': + case 'transform': + case 'ADBE Transform Group': + return transformInterface; + case 4: + case 'ADBE Effect Parade': + case 'effects': + case 'Effects': + return _thisLayerFunction.effect; + case 'ADBE Text Properties': + return _thisLayerFunction.textInterface; + default: + return null; + } + } + _thisLayerFunction.getMatrix = getMatrix; + _thisLayerFunction.invertPoint = invertPoint; + _thisLayerFunction.applyPoint = applyPoint; + _thisLayerFunction.toWorld = toWorld; + _thisLayerFunction.toWorldVec = toWorldVec; + _thisLayerFunction.fromWorld = fromWorld; + _thisLayerFunction.fromWorldVec = fromWorldVec; + _thisLayerFunction.toComp = toWorld; + _thisLayerFunction.fromComp = fromComp; + _thisLayerFunction.sampleImage = sampleImage; + _thisLayerFunction.sourceRectAtTime = elem.sourceRectAtTime.bind(elem); + _thisLayerFunction._elem = elem; + transformInterface = TransformExpressionInterface(elem.finalTransform.mProp); + var anchorPointDescriptor = getDescriptor(transformInterface, 'anchorPoint'); + Object.defineProperties(_thisLayerFunction, { + hasParent: { + get: function () { + return elem.hierarchy.length; }, - }; + }, + parent: { + get: function () { + return elem.hierarchy[0].layerInterface; + }, + }, + rotation: getDescriptor(transformInterface, 'rotation'), + scale: getDescriptor(transformInterface, 'scale'), + position: getDescriptor(transformInterface, 'position'), + opacity: getDescriptor(transformInterface, 'opacity'), + anchorPoint: anchorPointDescriptor, + anchor_point: anchorPointDescriptor, + transform: { + get: function () { + return transformInterface; + }, + }, + active: { + get: function () { + return elem.isInRange; + }, + }, + }); + + _thisLayerFunction.startTime = elem.data.st; + _thisLayerFunction.index = elem.data.ind; + _thisLayerFunction.source = elem.data.refId; + _thisLayerFunction.height = elem.data.ty === 0 ? elem.data.h : 100; + _thisLayerFunction.width = elem.data.ty === 0 ? elem.data.w : 100; + _thisLayerFunction.inPoint = elem.data.ip / elem.comp.globalData.frameRate; + _thisLayerFunction.outPoint = elem.data.op / elem.comp.globalData.frameRate; + _thisLayerFunction._name = elem.data.nm; + + _thisLayerFunction.registerMaskInterface = _registerMaskInterface; + _thisLayerFunction.registerEffectsInterface = _registerEffectsInterface; + return _thisLayerFunction; + }; +}()); + +const propertyGroupFactory = (function () { + return function (interfaceFunction, parentPropertyGroup) { + return function (val) { + val = val === undefined ? 1 : val; + if (val <= 0) { + return interfaceFunction; + } + return parentPropertyGroup(val - 1); + }; + }; +}()); - return ob; - }()); +const PropertyInterface = (function () { + return function (propertyName, propertyGroup) { + var interfaceFunction = { + _name: propertyName, + }; - const pooling = (function () { - function double(arr) { - return arr.concat(createSizedArray(arr.length)); + function _propertyGroup(val) { + val = val === undefined ? 1 : val; + if (val <= 0) { + return interfaceFunction; } + return propertyGroup(val - 1); + } - return { - double: double, - }; - }()); + return _propertyGroup; + }; +}()); - const poolFactory = (function () { - return function (initialLength, _create, _release) { - var _length = 0; - var _maxLength = initialLength; - var pool = createSizedArray(_maxLength); +const EffectsExpressionInterface = (function () { + var ob = { + createEffectsInterface: createEffectsInterface, + }; - var ob = { - newElement: newElement, - release: release, - }; + function createEffectsInterface(elem, propertyGroup) { + if (elem.effectsManager) { + var effectElements = []; + var effectsData = elem.data.ef; + var i; + var len = elem.effectsManager.effectElements.length; + for (i = 0; i < len; i += 1) { + effectElements.push(createGroupInterface(effectsData[i], elem.effectsManager.effectElements[i], propertyGroup, elem)); + } - function newElement() { - var element; - if (_length) { - _length -= 1; - element = pool[_length]; - } else { - element = _create(); + var effects = elem.data.ef || []; + var groupInterface = function (name) { + i = 0; + len = effects.length; + while (i < len) { + if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { + return effectElements[i]; } - return element; + i += 1; } + return null; + }; + Object.defineProperty(groupInterface, 'numProperties', { + get: function () { + return effects.length; + }, + }); + return groupInterface; + } + return null; + } - function release(element) { - if (_length === _maxLength) { - pool = pooling.double(pool); - _maxLength *= 2; - } - if (_release) { - _release(element); + function createGroupInterface(data, elements, propertyGroup, elem) { + function groupInterface(name) { + var effects = data.ef; + var i = 0; + var len = effects.length; + while (i < len) { + if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { + if (effects[i].ty === 5) { + return effectElements[i]; } - pool[_length] = element; - _length += 1; + return effectElements[i](); } - - return ob; - }; - }()); - - const bezierLengthPool = (function () { - function create() { - return { - addedLength: 0, - percents: createTypedArray('float32', getDefaultCurveSegments()), - lengths: createTypedArray('float32', getDefaultCurveSegments()), - }; + i += 1; } - return poolFactory(8, create); - }()); - - const segmentsLengthPool = (function () { - function create() { - return { - lengths: [], - totalLength: 0, - }; + throw new Error(); + } + var _propertyGroup = propertyGroupFactory(groupInterface, propertyGroup); + + var effectElements = []; + var i; + var len = data.ef.length; + for (i = 0; i < len; i += 1) { + if (data.ef[i].ty === 5) { + effectElements.push(createGroupInterface(data.ef[i], elements.effectElements[i], elements.effectElements[i].propertyGroup, elem)); + } else { + effectElements.push(createValueInterface(elements.effectElements[i], data.ef[i].ty, elem, _propertyGroup)); } + } - function release(element) { - var i; - var len = element.lengths.length; - for (i = 0; i < len; i += 1) { - bezierLengthPool.release(element.lengths[i]); - } - element.lengths.length = 0; - } + if (data.mn === 'ADBE Color Control') { + Object.defineProperty(groupInterface, 'color', { + get: function () { + return effectElements[0](); + }, + }); + } + Object.defineProperties(groupInterface, { + numProperties: { + get: function () { + return data.np; + }, + }, + _name: { value: data.nm }, + propertyGroup: { value: _propertyGroup }, + }); + groupInterface.enabled = data.en !== 0; + groupInterface.active = groupInterface.enabled; + return groupInterface; + } + + function createValueInterface(element, type, elem, propertyGroup) { + var expressionProperty = ExpressionPropertyInterface(element.p); + function interfaceFunction() { + if (type === 10) { + return elem.comp.compInterface(element.p.v); + } + return expressionProperty(); + } - return poolFactory(8, create, release); - }()); + if (element.p.setGroupProperty) { + element.p.setGroupProperty(PropertyInterface('', propertyGroup)); + } - function bezFunction() { - var math = Math; + return interfaceFunction; + } - function pointOnLine2D(x1, y1, x2, y2, x3, y3) { - var det1 = (x1 * y2) + (y1 * x3) + (x2 * y3) - (x3 * y2) - (y3 * x1) - (x2 * y1); - return det1 > -0.001 && det1 < 0.001; - } + return ob; +}()); - function pointOnLine3D(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - if (z1 === 0 && z2 === 0 && z3 === 0) { - return pointOnLine2D(x1, y1, x2, y2, x3, y3); - } - var dist1 = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2)); - var dist2 = math.sqrt(math.pow(x3 - x1, 2) + math.pow(y3 - y1, 2) + math.pow(z3 - z1, 2)); - var dist3 = math.sqrt(math.pow(x3 - x2, 2) + math.pow(y3 - y2, 2) + math.pow(z3 - z2, 2)); - var diffDist; - if (dist1 > dist2) { - if (dist1 > dist3) { - diffDist = dist1 - dist2 - dist3; - } else { - diffDist = dist3 - dist2 - dist1; - } - } else if (dist3 > dist2) { - diffDist = dist3 - dist2 - dist1; - } else { - diffDist = dist2 - dist1 - dist3; - } - return diffDist > -0.0001 && diffDist < 0.0001; - } - - var getBezierLength = (function () { - return function (pt1, pt2, pt3, pt4) { - var curveSegments = getDefaultCurveSegments(); - var k; - var i; - var len; - var ptCoord; - var perc; - var addedLength = 0; - var ptDistance; - var point = []; - var lastPoint = []; - var lengthData = bezierLengthPool.newElement(); - len = pt3.length; - for (k = 0; k < curveSegments; k += 1) { - perc = k / (curveSegments - 1); - ptDistance = 0; - for (i = 0; i < len; i += 1) { - ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * pt3[i] + 3 * (1 - perc) * bmPow(perc, 2) * pt4[i] + bmPow(perc, 3) * pt2[i]; - point[i] = ptCoord; - if (lastPoint[i] !== null) { - ptDistance += bmPow(point[i] - lastPoint[i], 2); - } - lastPoint[i] = point[i]; - } - if (ptDistance) { - ptDistance = bmSqrt(ptDistance); - addedLength += ptDistance; - } - lengthData.percents[k] = perc; - lengthData.lengths[k] = addedLength; - } - lengthData.addedLength = addedLength; - return lengthData; - }; - }()); - - function getSegmentsLength(shapeData) { - var segmentsLength = segmentsLengthPool.newElement(); - var closed = shapeData.c; - var pathV = shapeData.v; - var pathO = shapeData.o; - var pathI = shapeData.i; - var i; - var len = shapeData._length; - var lengths = segmentsLength.lengths; - var totalLength = 0; - for (i = 0; i < len - 1; i += 1) { - lengths[i] = getBezierLength(pathV[i], pathV[i + 1], pathO[i], pathI[i + 1]); - totalLength += lengths[i].addedLength; +const CompExpressionInterface = (function () { + return function (comp) { + function _thisLayerFunction(name) { + var i = 0; + var len = comp.layers.length; + while (i < len) { + if (comp.layers[i].nm === name || comp.layers[i].ind === name) { + return comp.elements[i].layerInterface; } - if (closed && len) { - lengths[i] = getBezierLength(pathV[i], pathV[0], pathO[i], pathI[0]); - totalLength += lengths[i].addedLength; + i += 1; + } + return null; + // return {active:false}; + } + Object.defineProperty(_thisLayerFunction, '_name', { value: comp.data.nm }); + _thisLayerFunction.layer = _thisLayerFunction; + _thisLayerFunction.pixelAspect = 1; + _thisLayerFunction.height = comp.data.h || comp.globalData.compSize.h; + _thisLayerFunction.width = comp.data.w || comp.globalData.compSize.w; + _thisLayerFunction.pixelAspect = 1; + _thisLayerFunction.frameDuration = 1 / comp.globalData.frameRate; + _thisLayerFunction.displayStartTime = 0; + _thisLayerFunction.numLayers = comp.layers.length; + return _thisLayerFunction; + }; +}()); + +const ShapePathInterface = ( + + function () { + return function pathInterfaceFactory(shape, view, propertyGroup) { + var prop = view.sh; + + function interfaceFunction(val) { + if (val === 'Shape' || val === 'shape' || val === 'Path' || val === 'path' || val === 'ADBE Vector Shape' || val === 2) { + return interfaceFunction.path; } - segmentsLength.totalLength = totalLength; - return segmentsLength; + return null; } - function BezierData(length) { - this.segmentLength = 0; - this.points = new Array(length); + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + prop.setGroupProperty(PropertyInterface('Path', _propertyGroup)); + Object.defineProperties(interfaceFunction, { + path: { + get: function () { + if (prop.k) { + prop.getValue(); + } + return prop; + }, + }, + shape: { + get: function () { + if (prop.k) { + prop.getValue(); + } + return prop; + }, + }, + _name: { value: shape.nm }, + ix: { value: shape.ix }, + propertyIndex: { value: shape.ix }, + mn: { value: shape.mn }, + propertyGroup: { value: propertyGroup }, + }); + return interfaceFunction; + }; + }() +); + +const ShapeExpressionInterface = (function () { + function iterateElements(shapes, view, propertyGroup) { + var arr = []; + var i; + var len = shapes ? shapes.length : 0; + for (i = 0; i < len; i += 1) { + if (shapes[i].ty === 'gr') { + arr.push(groupInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'fl') { + arr.push(fillInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'st') { + arr.push(strokeInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'tm') { + arr.push(trimInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'tr') { + // arr.push(transformInterfaceFactory(shapes[i],view[i],propertyGroup)); + } else if (shapes[i].ty === 'el') { + arr.push(ellipseInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'sr') { + arr.push(starInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'sh') { + arr.push(ShapePathInterface(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'rc') { + arr.push(rectInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'rd') { + arr.push(roundedInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'rp') { + arr.push(repeaterInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else if (shapes[i].ty === 'gf') { + arr.push(gradientFillInterfaceFactory(shapes[i], view[i], propertyGroup)); + } else { + arr.push(defaultInterfaceFactory(shapes[i], view[i], propertyGroup)); } + } + return arr; + } - function PointData(partial, point) { - this.partialLength = partial; - this.point = point; + function contentsInterfaceFactory(shape, view, propertyGroup) { + var interfaces; + var interfaceFunction = function _interfaceFunction(value) { + var i = 0; + var len = interfaces.length; + while (i < len) { + if (interfaces[i]._name === value || interfaces[i].mn === value || interfaces[i].propertyIndex === value || interfaces[i].ix === value || interfaces[i].ind === value) { + return interfaces[i]; + } + i += 1; } + if (typeof value === 'number') { + return interfaces[value - 1]; + } + return null; + }; - var buildBezierData = (function () { - var storedData = {}; + interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + interfaces = iterateElements(shape.it, view.it, interfaceFunction.propertyGroup); + interfaceFunction.numProperties = interfaces.length; + var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); + interfaceFunction.transform = transformInterface; + interfaceFunction.propertyIndex = shape.cix; + interfaceFunction._name = shape.nm; - return function (pt1, pt2, pt3, pt4) { - var bezierName = (pt1[0] + '_' + pt1[1] + '_' + pt2[0] + '_' + pt2[1] + '_' + pt3[0] + '_' + pt3[1] + '_' + pt4[0] + '_' + pt4[1]).replace(/\./g, 'p'); - if (!storedData[bezierName]) { - var curveSegments = getDefaultCurveSegments(); - var k; - var i; - var len; - var ptCoord; - var perc; - var addedLength = 0; - var ptDistance; - var point; - var lastPoint = null; - if (pt1.length === 2 && (pt1[0] !== pt2[0] || pt1[1] !== pt2[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt1[0] + pt3[0], pt1[1] + pt3[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt2[0] + pt4[0], pt2[1] + pt4[1])) { - curveSegments = 2; - } - var bezierData = new BezierData(curveSegments); - len = pt3.length; - for (k = 0; k < curveSegments; k += 1) { - point = createSizedArray(len); - perc = k / (curveSegments - 1); - ptDistance = 0; - for (i = 0; i < len; i += 1) { - ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * (pt1[i] + pt3[i]) + 3 * (1 - perc) * bmPow(perc, 2) * (pt2[i] + pt4[i]) + bmPow(perc, 3) * pt2[i]; - point[i] = ptCoord; - if (lastPoint !== null) { - ptDistance += bmPow(point[i] - lastPoint[i], 2); - } - } - ptDistance = bmSqrt(ptDistance); - addedLength += ptDistance; - bezierData.points[k] = new PointData(ptDistance, point); - lastPoint = point; - } - bezierData.segmentLength = addedLength; - storedData[bezierName] = bezierData; - } - return storedData[bezierName]; - }; - }()); - - function getDistancePerc(perc, bezierData) { - var percents = bezierData.percents; - var lengths = bezierData.lengths; - var len = percents.length; - var initPos = bmFloor((len - 1) * perc); - var lengthPos = perc * bezierData.addedLength; - var lPerc = 0; - if (initPos === len - 1 || initPos === 0 || lengthPos === lengths[initPos]) { - return percents[initPos]; - } - var dir = lengths[initPos] > lengthPos ? -1 : 1; - var flag = true; - while (flag) { - if (lengths[initPos] <= lengthPos && lengths[initPos + 1] > lengthPos) { - lPerc = (lengthPos - lengths[initPos]) / (lengths[initPos + 1] - lengths[initPos]); - flag = false; - } else { - initPos += dir; - } - if (initPos < 0 || initPos >= len - 1) { - // FIX for TypedArrays that don't store floating point values with enough accuracy - if (initPos === len - 1) { - return percents[initPos]; - } - flag = false; - } - } - return percents[initPos] + (percents[initPos + 1] - percents[initPos]) * lPerc; - } + return interfaceFunction; + } - function getPointInSegment(pt1, pt2, pt3, pt4, percent, bezierData) { - var t1 = getDistancePerc(percent, bezierData); - var u1 = 1 - t1; - var ptX = math.round((u1 * u1 * u1 * pt1[0] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[0] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[0] + t1 * t1 * t1 * pt2[0]) * 1000) / 1000; - var ptY = math.round((u1 * u1 * u1 * pt1[1] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[1] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[1] + t1 * t1 * t1 * pt2[1]) * 1000) / 1000; - return [ptX, ptY]; + function groupInterfaceFactory(shape, view, propertyGroup) { + var interfaceFunction = function _interfaceFunction(value) { + switch (value) { + case 'ADBE Vectors Group': + case 'Contents': + case 2: + return interfaceFunction.content; + // Not necessary for now. Keeping them here in case a new case appears + // case 'ADBE Vector Transform Group': + // case 3: + default: + return interfaceFunction.transform; } + }; + interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var content = contentsInterfaceFactory(shape, view, interfaceFunction.propertyGroup); + var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); + interfaceFunction.content = content; + interfaceFunction.transform = transformInterface; + Object.defineProperty(interfaceFunction, '_name', { + get: function () { + return shape.nm; + }, + }); + // interfaceFunction.content = interfaceFunction; + interfaceFunction.numProperties = shape.np; + interfaceFunction.propertyIndex = shape.ix; + interfaceFunction.nm = shape.nm; + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function fillInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(val) { + if (val === 'Color' || val === 'color') { + return interfaceFunction.color; + } if (val === 'Opacity' || val === 'opacity') { + return interfaceFunction.opacity; + } + return null; + } + Object.defineProperties(interfaceFunction, { + color: { + get: ExpressionPropertyInterface(view.c), + }, + opacity: { + get: ExpressionPropertyInterface(view.o), + }, + _name: { value: shape.nm }, + mn: { value: shape.mn }, + }); - var bezierSegmentPoints = createTypedArray('float32', 8); - - function getNewSegment(pt1, pt2, pt3, pt4, startPerc, endPerc, bezierData) { - if (startPerc < 0) { - startPerc = 0; - } else if (startPerc > 1) { - startPerc = 1; - } - var t0 = getDistancePerc(startPerc, bezierData); - endPerc = endPerc > 1 ? 1 : endPerc; - var t1 = getDistancePerc(endPerc, bezierData); - var i; - var len = pt1.length; - var u0 = 1 - t0; - var u1 = 1 - t1; - var u0u0u0 = u0 * u0 * u0; - var t0u0u0_3 = t0 * u0 * u0 * 3; // eslint-disable-line camelcase - var t0t0u0_3 = t0 * t0 * u0 * 3; // eslint-disable-line camelcase - var t0t0t0 = t0 * t0 * t0; - // - var u0u0u1 = u0 * u0 * u1; - var t0u0u1_3 = t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1; // eslint-disable-line camelcase - var t0t0u1_3 = t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1; // eslint-disable-line camelcase - var t0t0t1 = t0 * t0 * t1; - // - var u0u1u1 = u0 * u1 * u1; - var t0u1u1_3 = t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1; // eslint-disable-line camelcase - var t0t1u1_3 = t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1; // eslint-disable-line camelcase - var t0t1t1 = t0 * t1 * t1; - // - var u1u1u1 = u1 * u1 * u1; - var t1u1u1_3 = t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1; // eslint-disable-line camelcase - var t1t1u1_3 = t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1; // eslint-disable-line camelcase - var t1t1t1 = t1 * t1 * t1; - for (i = 0; i < len; i += 1) { - bezierSegmentPoints[i * 4] = math.round((u0u0u0 * pt1[i] + t0u0u0_3 * pt3[i] + t0t0u0_3 * pt4[i] + t0t0t0 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - bezierSegmentPoints[i * 4 + 1] = math.round((u0u0u1 * pt1[i] + t0u0u1_3 * pt3[i] + t0t0u1_3 * pt4[i] + t0t0t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - bezierSegmentPoints[i * 4 + 2] = math.round((u0u1u1 * pt1[i] + t0u1u1_3 * pt3[i] + t0t1u1_3 * pt4[i] + t0t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - bezierSegmentPoints[i * 4 + 3] = math.round((u1u1u1 * pt1[i] + t1u1u1_3 * pt3[i] + t1t1u1_3 * pt4[i] + t1t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase - } + view.c.setGroupProperty(PropertyInterface('Color', propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); + return interfaceFunction; + } - return bezierSegmentPoints; + function gradientFillInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(val) { + if (val === 'Start Point' || val === 'start point') { + return interfaceFunction.startPoint; + } + if (val === 'End Point' || val === 'end point') { + return interfaceFunction.endPoint; } + if (val === 'Opacity' || val === 'opacity') { + return interfaceFunction.opacity; + } + return null; + } + Object.defineProperties(interfaceFunction, { + startPoint: { + get: ExpressionPropertyInterface(view.s), + }, + endPoint: { + get: ExpressionPropertyInterface(view.e), + }, + opacity: { + get: ExpressionPropertyInterface(view.o), + }, + type: { + get: function () { + return 'a'; + }, + }, + _name: { value: shape.nm }, + mn: { value: shape.mn }, + }); + + view.s.setGroupProperty(PropertyInterface('Start Point', propertyGroup)); + view.e.setGroupProperty(PropertyInterface('End Point', propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); + return interfaceFunction; + } + function defaultInterfaceFactory() { + function interfaceFunction() { + return null; + } + return interfaceFunction; + } + + function strokeInterfaceFactory(shape, view, propertyGroup) { + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var _dashPropertyGroup = propertyGroupFactory(dashOb, _propertyGroup); + function addPropertyToDashOb(i) { + Object.defineProperty(dashOb, shape.d[i].nm, { + get: ExpressionPropertyInterface(view.d.dataProps[i].p), + }); + } + var i; + var len = shape.d ? shape.d.length : 0; + var dashOb = {}; + for (i = 0; i < len; i += 1) { + addPropertyToDashOb(i); + view.d.dataProps[i].p.setGroupProperty(_dashPropertyGroup); + } - return { - getSegmentsLength: getSegmentsLength, - getNewSegment: getNewSegment, - getPointInSegment: getPointInSegment, - buildBezierData: buildBezierData, - pointOnLine2D: pointOnLine2D, - pointOnLine3D: pointOnLine3D, - }; + function interfaceFunction(val) { + if (val === 'Color' || val === 'color') { + return interfaceFunction.color; + } if (val === 'Opacity' || val === 'opacity') { + return interfaceFunction.opacity; + } if (val === 'Stroke Width' || val === 'stroke width') { + return interfaceFunction.strokeWidth; + } + return null; } + Object.defineProperties(interfaceFunction, { + color: { + get: ExpressionPropertyInterface(view.c), + }, + opacity: { + get: ExpressionPropertyInterface(view.o), + }, + strokeWidth: { + get: ExpressionPropertyInterface(view.w), + }, + dash: { + get: function () { + return dashOb; + }, + }, + _name: { value: shape.nm }, + mn: { value: shape.mn }, + }); - const bez = bezFunction(); + view.c.setGroupProperty(PropertyInterface('Color', _propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); + view.w.setGroupProperty(PropertyInterface('Stroke Width', _propertyGroup)); + return interfaceFunction; + } - const PropertyFactory = (function () { - var initFrame = initialDefaultFrame; - var mathAbs = Math.abs; + function trimInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(val) { + if (val === shape.e.ix || val === 'End' || val === 'end') { + return interfaceFunction.end; + } + if (val === shape.s.ix) { + return interfaceFunction.start; + } + if (val === shape.o.ix) { + return interfaceFunction.offset; + } + return null; + } - function interpolateValue(frameNum, caching) { - var offsetTime = this.offsetTime; - var newValue; - if (this.propType === 'multidimensional') { - newValue = createTypedArray('float32', this.pv.length); - } - var iterationIndex = caching.lastIndex; - var i = iterationIndex; - var len = this.keyframes.length - 1; - var flag = true; - var keyData; - var nextKeyData; - var keyframeMetadata; + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + interfaceFunction.propertyIndex = shape.ix; - while (flag) { - keyData = this.keyframes[i]; - nextKeyData = this.keyframes[i + 1]; - if (i === len - 1 && frameNum >= nextKeyData.t - offsetTime) { - if (keyData.h) { - keyData = nextKeyData; - } - iterationIndex = 0; - break; - } - if ((nextKeyData.t - offsetTime) > frameNum) { - iterationIndex = i; - break; - } - if (i < len - 1) { - i += 1; - } else { - iterationIndex = 0; - flag = false; - } - } - keyframeMetadata = this.keyframesMetadata[i] || {}; + view.s.setGroupProperty(PropertyInterface('Start', _propertyGroup)); + view.e.setGroupProperty(PropertyInterface('End', _propertyGroup)); + view.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); + interfaceFunction.propertyIndex = shape.ix; + interfaceFunction.propertyGroup = propertyGroup; - var k; - var kLen; - var perc; - var jLen; - var j; - var fnc; - var nextKeyTime = nextKeyData.t - offsetTime; - var keyTime = keyData.t - offsetTime; - var endValue; - if (keyData.to) { - if (!keyframeMetadata.bezierData) { - keyframeMetadata.bezierData = bez.buildBezierData(keyData.s, nextKeyData.s || keyData.e, keyData.to, keyData.ti); - } - var bezierData = keyframeMetadata.bezierData; - if (frameNum >= nextKeyTime || frameNum < keyTime) { - var ind = frameNum >= nextKeyTime ? bezierData.points.length - 1 : 0; - kLen = bezierData.points[ind].point.length; - for (k = 0; k < kLen; k += 1) { - newValue[k] = bezierData.points[ind].point[k]; - } - // caching._lastKeyframeIndex = -1; - } else { - if (keyframeMetadata.__fnct) { - fnc = keyframeMetadata.__fnct; - } else { - fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y, keyData.n).get; - keyframeMetadata.__fnct = fnc; - } - perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); - var distanceInLine = bezierData.segmentLength * perc; - - var segmentPerc; - var addedLength = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastAddedLength : 0; - j = (caching.lastFrame < frameNum && caching._lastKeyframeIndex === i) ? caching._lastPoint : 0; - flag = true; - jLen = bezierData.points.length; - while (flag) { - addedLength += bezierData.points[j].partialLength; - if (distanceInLine === 0 || perc === 0 || j === bezierData.points.length - 1) { - kLen = bezierData.points[j].point.length; - for (k = 0; k < kLen; k += 1) { - newValue[k] = bezierData.points[j].point[k]; - } - break; - } else if (distanceInLine >= addedLength && distanceInLine < addedLength + bezierData.points[j + 1].partialLength) { - segmentPerc = (distanceInLine - addedLength) / bezierData.points[j + 1].partialLength; - kLen = bezierData.points[j].point.length; - for (k = 0; k < kLen; k += 1) { - newValue[k] = bezierData.points[j].point[k] + (bezierData.points[j + 1].point[k] - bezierData.points[j].point[k]) * segmentPerc; - } - break; - } - if (j < jLen - 1) { - j += 1; - } else { - flag = false; - } - } - caching._lastPoint = j; - caching._lastAddedLength = addedLength - bezierData.points[j].partialLength; - caching._lastKeyframeIndex = i; - } - } else { - var outX; - var outY; - var inX; - var inY; - var keyValue; - len = keyData.s.length; - endValue = nextKeyData.s || keyData.e; - if (this.sh && keyData.h !== 1) { - if (frameNum >= nextKeyTime) { - newValue[0] = endValue[0]; - newValue[1] = endValue[1]; - newValue[2] = endValue[2]; - } else if (frameNum <= keyTime) { - newValue[0] = keyData.s[0]; - newValue[1] = keyData.s[1]; - newValue[2] = keyData.s[2]; - } else { - var quatStart = createQuaternion(keyData.s); - var quatEnd = createQuaternion(endValue); - var time = (frameNum - keyTime) / (nextKeyTime - keyTime); - quaternionToEuler(newValue, slerp(quatStart, quatEnd, time)); - } - } else { - for (i = 0; i < len; i += 1) { - if (keyData.h !== 1) { - if (frameNum >= nextKeyTime) { - perc = 1; - } else if (frameNum < keyTime) { - perc = 0; - } else { - if (keyData.o.x.constructor === Array) { - if (!keyframeMetadata.__fnct) { - keyframeMetadata.__fnct = []; - } - if (!keyframeMetadata.__fnct[i]) { - outX = keyData.o.x[i] === undefined ? keyData.o.x[0] : keyData.o.x[i]; - outY = keyData.o.y[i] === undefined ? keyData.o.y[0] : keyData.o.y[i]; - inX = keyData.i.x[i] === undefined ? keyData.i.x[0] : keyData.i.x[i]; - inY = keyData.i.y[i] === undefined ? keyData.i.y[0] : keyData.i.y[i]; - fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; - keyframeMetadata.__fnct[i] = fnc; - } else { - fnc = keyframeMetadata.__fnct[i]; - } - } else if (!keyframeMetadata.__fnct) { - outX = keyData.o.x; - outY = keyData.o.y; - inX = keyData.i.x; - inY = keyData.i.y; - fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get; - keyData.keyframeMetadata = fnc; - } else { - fnc = keyframeMetadata.__fnct; - } - perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime)); - } - } - - endValue = nextKeyData.s || keyData.e; - keyValue = keyData.h === 1 ? keyData.s[i] : keyData.s[i] + (endValue[i] - keyData.s[i]) * perc; - - if (this.propType === 'multidimensional') { - newValue[i] = keyValue; - } else { - newValue = keyValue; - } - } - } - } - caching.lastIndex = iterationIndex; - return newValue; - } - - // based on @Toji's https://github.com/toji/gl-matrix/ - function slerp(a, b, t) { - var out = []; - var ax = a[0]; - var ay = a[1]; - var az = a[2]; - var aw = a[3]; - var bx = b[0]; - var by = b[1]; - var bz = b[2]; - var bw = b[3]; - - var omega; - var cosom; - var sinom; - var scale0; - var scale1; - - cosom = ax * bx + ay * by + az * bz + aw * bw; - if (cosom < 0.0) { - cosom = -cosom; - bx = -bx; - by = -by; - bz = -bz; - bw = -bw; - } - if ((1.0 - cosom) > 0.000001) { - omega = Math.acos(cosom); - sinom = Math.sin(omega); - scale0 = Math.sin((1.0 - t) * omega) / sinom; - scale1 = Math.sin(t * omega) / sinom; - } else { - scale0 = 1.0 - t; - scale1 = t; - } - out[0] = scale0 * ax + scale1 * bx; - out[1] = scale0 * ay + scale1 * by; - out[2] = scale0 * az + scale1 * bz; - out[3] = scale0 * aw + scale1 * bw; - - return out; - } - - function quaternionToEuler(out, quat) { - var qx = quat[0]; - var qy = quat[1]; - var qz = quat[2]; - var qw = quat[3]; - var heading = Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy * qy - 2 * qz * qz); - var attitude = Math.asin(2 * qx * qy + 2 * qz * qw); - var bank = Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx * qx - 2 * qz * qz); - out[0] = heading / degToRads; - out[1] = attitude / degToRads; - out[2] = bank / degToRads; - } - - function createQuaternion(values) { - var heading = values[0] * degToRads; - var attitude = values[1] * degToRads; - var bank = values[2] * degToRads; - var c1 = Math.cos(heading / 2); - var c2 = Math.cos(attitude / 2); - var c3 = Math.cos(bank / 2); - var s1 = Math.sin(heading / 2); - var s2 = Math.sin(attitude / 2); - var s3 = Math.sin(bank / 2); - var w = c1 * c2 * c3 - s1 * s2 * s3; - var x = s1 * s2 * c3 + c1 * c2 * s3; - var y = s1 * c2 * c3 + c1 * s2 * s3; - var z = c1 * s2 * c3 - s1 * c2 * s3; - - return [x, y, z, w]; - } - - function getValueAtCurrentTime() { - var frameNum = this.comp.renderedFrame - this.offsetTime; - var initTime = this.keyframes[0].t - this.offsetTime; - var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; - if (!(frameNum === this._caching.lastFrame || (this._caching.lastFrame !== initFrame && ((this._caching.lastFrame >= endTime && frameNum >= endTime) || (this._caching.lastFrame < initTime && frameNum < initTime))))) { - if (this._caching.lastFrame >= frameNum) { - this._caching._lastKeyframeIndex = -1; - this._caching.lastIndex = 0; - } + Object.defineProperties(interfaceFunction, { + start: { + get: ExpressionPropertyInterface(view.s), + }, + end: { + get: ExpressionPropertyInterface(view.e), + }, + offset: { + get: ExpressionPropertyInterface(view.o), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } - var renderResult = this.interpolateValue(frameNum, this._caching); - this.pv = renderResult; - } - this._caching.lastFrame = frameNum; - return this.pv; + function transformInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.a.ix === value || value === 'Anchor Point') { + return interfaceFunction.anchorPoint; } - - function setVValue(val) { - var multipliedValue; - if (this.propType === 'unidimensional') { - multipliedValue = val * this.mult; - if (mathAbs(this.v - multipliedValue) > 0.00001) { - this.v = multipliedValue; - this._mdf = true; - } - } else { - var i = 0; - var len = this.v.length; - while (i < len) { - multipliedValue = val[i] * this.mult; - if (mathAbs(this.v[i] - multipliedValue) > 0.00001) { - this.v[i] = multipliedValue; - this._mdf = true; - } - i += 1; - } - } + if (shape.o.ix === value || value === 'Opacity') { + return interfaceFunction.opacity; } - - function processEffectsSequence() { - if (this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) { - return; - } - if (this.lock) { - this.setVValue(this.pv); - return; - } - this.lock = true; - this._mdf = this._isFirstFrame; - var i; - var len = this.effectsSequence.length; - var finalValue = this.kf ? this.pv : this.data.k; - for (i = 0; i < len; i += 1) { - finalValue = this.effectsSequence[i](finalValue); - } - this.setVValue(finalValue); - this._isFirstFrame = false; - this.lock = false; - this.frameId = this.elem.globalData.frameId; + if (shape.p.ix === value || value === 'Position') { + return interfaceFunction.position; } - - function addEffect(effectFunction) { - this.effectsSequence.push(effectFunction); - this.container.addDynamicProperty(this); + if (shape.r.ix === value || value === 'Rotation' || value === 'ADBE Vector Rotation') { + return interfaceFunction.rotation; } - - function ValueProperty(elem, data, mult, container) { - this.propType = 'unidimensional'; - this.mult = mult || 1; - this.data = data; - this.v = mult ? data.k * mult : data.k; - this.pv = data.k; - this._mdf = false; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.k = false; - this.kf = false; - this.vel = 0; - this.effectsSequence = []; - this._isFirstFrame = true; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.addEffect = addEffect; - } - - function MultiDimensionalProperty(elem, data, mult, container) { - this.propType = 'multidimensional'; - this.mult = mult || 1; - this.data = data; - this._mdf = false; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.k = false; - this.kf = false; - this.frameId = -1; - var i; - var len = data.k.length; - this.v = createTypedArray('float32', len); - this.pv = createTypedArray('float32', len); - this.vel = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - this.v[i] = data.k[i] * this.mult; - this.pv[i] = data.k[i]; - } - this._isFirstFrame = true; - this.effectsSequence = []; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.addEffect = addEffect; - } - - function KeyframedValueProperty(elem, data, mult, container) { - this.propType = 'unidimensional'; - this.keyframes = data.k; - this.keyframesMetadata = []; - this.offsetTime = elem.data.st; - this.frameId = -1; - this._caching = { - lastFrame: initFrame, lastIndex: 0, value: 0, _lastKeyframeIndex: -1, - }; - this.k = true; - this.kf = true; - this.data = data; - this.mult = mult || 1; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.v = initFrame; - this.pv = initFrame; - this._isFirstFrame = true; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.interpolateValue = interpolateValue; - this.effectsSequence = [getValueAtCurrentTime.bind(this)]; - this.addEffect = addEffect; + if (shape.s.ix === value || value === 'Scale') { + return interfaceFunction.scale; } - - function KeyframedMultidimensionalProperty(elem, data, mult, container) { - this.propType = 'multidimensional'; - var i; - var len = data.k.length; - var s; - var e; - var to; - var ti; - for (i = 0; i < len - 1; i += 1) { - if (data.k[i].to && data.k[i].s && data.k[i + 1] && data.k[i + 1].s) { - s = data.k[i].s; - e = data.k[i + 1].s; - to = data.k[i].to; - ti = data.k[i].ti; - if ((s.length === 2 && !(s[0] === e[0] && s[1] === e[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], s[0] + to[0], s[1] + to[1]) && bez.pointOnLine2D(s[0], s[1], e[0], e[1], e[0] + ti[0], e[1] + ti[1])) || (s.length === 3 && !(s[0] === e[0] && s[1] === e[1] && s[2] === e[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], s[0] + to[0], s[1] + to[1], s[2] + to[2]) && bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], e[0] + ti[0], e[1] + ti[1], e[2] + ti[2]))) { - data.k[i].to = null; - data.k[i].ti = null; - } - if (s[0] === e[0] && s[1] === e[1] && to[0] === 0 && to[1] === 0 && ti[0] === 0 && ti[1] === 0) { - if (s.length === 2 || (s[2] === e[2] && to[2] === 0 && ti[2] === 0)) { - data.k[i].to = null; - data.k[i].ti = null; - } - } - } - } - this.effectsSequence = [getValueAtCurrentTime.bind(this)]; - this.data = data; - this.keyframes = data.k; - this.keyframesMetadata = []; - this.offsetTime = elem.data.st; - this.k = true; - this.kf = true; - this._isFirstFrame = true; - this.mult = mult || 1; - this.elem = elem; - this.container = container; - this.comp = elem.comp; - this.getValue = processEffectsSequence; - this.setVValue = setVValue; - this.interpolateValue = interpolateValue; - this.frameId = -1; - var arrLen = data.k[0].s.length; - this.v = createTypedArray('float32', arrLen); - this.pv = createTypedArray('float32', arrLen); - for (i = 0; i < arrLen; i += 1) { - this.v[i] = initFrame; - this.pv[i] = initFrame; - } - this._caching = { lastFrame: initFrame, lastIndex: 0, value: createTypedArray('float32', arrLen) }; - this.addEffect = addEffect; - } - - function getProp(elem, data, type, mult, container) { - var p; - if (!data.k.length) { - p = new ValueProperty(elem, data, mult, container); - } else if (typeof (data.k[0]) === 'number') { - p = new MultiDimensionalProperty(elem, data, mult, container); - } else { - switch (type) { - case 0: - p = new KeyframedValueProperty(elem, data, mult, container); - break; - case 1: - p = new KeyframedMultidimensionalProperty(elem, data, mult, container); - break; - default: - break; - } - } - if (p.effectsSequence.length) { - container.addDynamicProperty(p); - } - return p; + if ((shape.sk && shape.sk.ix === value) || value === 'Skew') { + return interfaceFunction.skew; } - - var ob = { - getProp: getProp, - }; - return ob; - }()); - - function DynamicPropertyContainer() {} - DynamicPropertyContainer.prototype = { - addDynamicProperty: function (prop) { - if (this.dynamicProperties.indexOf(prop) === -1) { - this.dynamicProperties.push(prop); - this.container.addDynamicProperty(this); - this._isAnimated = true; - } + if ((shape.sa && shape.sa.ix === value) || value === 'Skew Axis') { + return interfaceFunction.skewAxis; + } + return null; + } + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + view.transform.mProps.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); + view.transform.mProps.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + view.transform.mProps.a.setGroupProperty(PropertyInterface('Anchor Point', _propertyGroup)); + view.transform.mProps.s.setGroupProperty(PropertyInterface('Scale', _propertyGroup)); + view.transform.mProps.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); + if (view.transform.mProps.sk) { + view.transform.mProps.sk.setGroupProperty(PropertyInterface('Skew', _propertyGroup)); + view.transform.mProps.sa.setGroupProperty(PropertyInterface('Skew Angle', _propertyGroup)); + } + view.transform.op.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); + Object.defineProperties(interfaceFunction, { + opacity: { + get: ExpressionPropertyInterface(view.transform.mProps.o), }, - iterateDynamicProperties: function () { - this._mdf = false; - var i; - var len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - this.dynamicProperties[i].getValue(); - if (this.dynamicProperties[i]._mdf) { - this._mdf = true; - } - } + position: { + get: ExpressionPropertyInterface(view.transform.mProps.p), }, - initDynamicPropertyContainer: function (container) { - this.container = container; - this.dynamicProperties = []; - this._mdf = false; - this._isAnimated = false; + anchorPoint: { + get: ExpressionPropertyInterface(view.transform.mProps.a), }, - }; - - const pointPool = (function () { - function create() { - return createTypedArray('float32', 2); + scale: { + get: ExpressionPropertyInterface(view.transform.mProps.s), + }, + rotation: { + get: ExpressionPropertyInterface(view.transform.mProps.r), + }, + skew: { + get: ExpressionPropertyInterface(view.transform.mProps.sk), + }, + skewAxis: { + get: ExpressionPropertyInterface(view.transform.mProps.sa), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.ty = 'tr'; + interfaceFunction.mn = shape.mn; + interfaceFunction.propertyGroup = propertyGroup; + return interfaceFunction; + } + + function ellipseInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.p.ix === value) { + return interfaceFunction.position; + } + if (shape.s.ix === value) { + return interfaceFunction.size; } - return poolFactory(8, create); - }()); - - function ShapePath() { - this.c = false; - this._length = 0; - this._maxLength = 8; - this.v = createSizedArray(this._maxLength); - this.o = createSizedArray(this._maxLength); - this.i = createSizedArray(this._maxLength); + return null; } + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + interfaceFunction.propertyIndex = shape.ix; + var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; + prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); + prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + + Object.defineProperties(interfaceFunction, { + size: { + get: ExpressionPropertyInterface(prop.s), + }, + position: { + get: ExpressionPropertyInterface(prop.p), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } - ShapePath.prototype.setPathData = function (closed, len) { - this.c = closed; - this.setLength(len); - var i = 0; - while (i < len) { - this.v[i] = pointPool.newElement(); - this.o[i] = pointPool.newElement(); - this.i[i] = pointPool.newElement(); - i += 1; + function starInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.p.ix === value) { + return interfaceFunction.position; } - }; - - ShapePath.prototype.setLength = function (len) { - while (this._maxLength < len) { - this.doubleArrayLength(); + if (shape.r.ix === value) { + return interfaceFunction.rotation; } - this._length = len; - }; - - ShapePath.prototype.doubleArrayLength = function () { - this.v = this.v.concat(createSizedArray(this._maxLength)); - this.i = this.i.concat(createSizedArray(this._maxLength)); - this.o = this.o.concat(createSizedArray(this._maxLength)); - this._maxLength *= 2; - }; - - ShapePath.prototype.setXYAt = function (x, y, type, pos, replace) { - var arr; - this._length = Math.max(this._length, pos + 1); - if (this._length >= this._maxLength) { - this.doubleArrayLength(); + if (shape.pt.ix === value) { + return interfaceFunction.points; } - switch (type) { - case 'v': - arr = this.v; - break; - case 'i': - arr = this.i; - break; - case 'o': - arr = this.o; - break; - default: - arr = []; - break; + if (shape.or.ix === value || value === 'ADBE Vector Star Outer Radius') { + return interfaceFunction.outerRadius; } - if (!arr[pos] || (arr[pos] && !replace)) { - arr[pos] = pointPool.newElement(); + if (shape.os.ix === value) { + return interfaceFunction.outerRoundness; } - arr[pos][0] = x; - arr[pos][1] = y; - }; + if (shape.ir && (shape.ir.ix === value || value === 'ADBE Vector Star Inner Radius')) { + return interfaceFunction.innerRadius; + } + if (shape.is && shape.is.ix === value) { + return interfaceFunction.innerRoundness; + } + return null; + } - ShapePath.prototype.setTripleAt = function (vX, vY, oX, oY, iX, iY, pos, replace) { - this.setXYAt(vX, vY, 'v', pos, replace); - this.setXYAt(oX, oY, 'o', pos, replace); - this.setXYAt(iX, iY, 'i', pos, replace); - }; + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; + interfaceFunction.propertyIndex = shape.ix; + prop.or.setGroupProperty(PropertyInterface('Outer Radius', _propertyGroup)); + prop.os.setGroupProperty(PropertyInterface('Outer Roundness', _propertyGroup)); + prop.pt.setGroupProperty(PropertyInterface('Points', _propertyGroup)); + prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); + if (shape.ir) { + prop.ir.setGroupProperty(PropertyInterface('Inner Radius', _propertyGroup)); + prop.is.setGroupProperty(PropertyInterface('Inner Roundness', _propertyGroup)); + } - ShapePath.prototype.reverse = function () { - var newPath = new ShapePath(); - newPath.setPathData(this.c, this._length); - var vertices = this.v; - var outPoints = this.o; - var inPoints = this.i; - var init = 0; - if (this.c) { - newPath.setTripleAt(vertices[0][0], vertices[0][1], inPoints[0][0], inPoints[0][1], outPoints[0][0], outPoints[0][1], 0, false); - init = 1; - } - var cnt = this._length - 1; - var len = this._length; + Object.defineProperties(interfaceFunction, { + position: { + get: ExpressionPropertyInterface(prop.p), + }, + rotation: { + get: ExpressionPropertyInterface(prop.r), + }, + points: { + get: ExpressionPropertyInterface(prop.pt), + }, + outerRadius: { + get: ExpressionPropertyInterface(prop.or), + }, + outerRoundness: { + get: ExpressionPropertyInterface(prop.os), + }, + innerRadius: { + get: ExpressionPropertyInterface(prop.ir), + }, + innerRoundness: { + get: ExpressionPropertyInterface(prop.is), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } - var i; - for (i = init; i < len; i += 1) { - newPath.setTripleAt(vertices[cnt][0], vertices[cnt][1], inPoints[cnt][0], inPoints[cnt][1], outPoints[cnt][0], outPoints[cnt][1], i, false); - cnt -= 1; + function rectInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.p.ix === value) { + return interfaceFunction.position; } - return newPath; - }; - - const shapePool = (function () { - function create() { - return new ShapePath(); + if (shape.r.ix === value) { + return interfaceFunction.roundness; } - - function release(shapePath) { - var len = shapePath._length; - var i; - for (i = 0; i < len; i += 1) { - pointPool.release(shapePath.v[i]); - pointPool.release(shapePath.i[i]); - pointPool.release(shapePath.o[i]); - shapePath.v[i] = null; - shapePath.i[i] = null; - shapePath.o[i] = null; - } - shapePath._length = 0; - shapePath.c = false; + if (shape.s.ix === value || value === 'Size' || value === 'ADBE Vector Rect Size') { + return interfaceFunction.size; } + return null; + } + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - function clone(shape) { - var cloned = factory.newElement(); - var i; - var len = shape._length === undefined ? shape.v.length : shape._length; - cloned.setLength(len); - cloned.c = shape.c; + var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; + interfaceFunction.propertyIndex = shape.ix; + prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); + prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); + prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); - for (i = 0; i < len; i += 1) { - cloned.setTripleAt(shape.v[i][0], shape.v[i][1], shape.o[i][0], shape.o[i][1], shape.i[i][0], shape.i[i][1], i); - } - return cloned; - } + Object.defineProperties(interfaceFunction, { + position: { + get: ExpressionPropertyInterface(prop.p), + }, + roundness: { + get: ExpressionPropertyInterface(prop.r), + }, + size: { + get: ExpressionPropertyInterface(prop.s), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } - var factory = poolFactory(4, create, release); - factory.clone = clone; + function roundedInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.r.ix === value || value === 'Round Corners 1') { + return interfaceFunction.radius; + } + return null; + } - return factory; - }()); + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var prop = view; + interfaceFunction.propertyIndex = shape.ix; + prop.rd.setGroupProperty(PropertyInterface('Radius', _propertyGroup)); - function ShapeCollection() { - this._length = 0; - this._maxLength = 4; - this.shapes = createSizedArray(this._maxLength); + Object.defineProperties(interfaceFunction, { + radius: { + get: ExpressionPropertyInterface(prop.rd), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + function repeaterInterfaceFactory(shape, view, propertyGroup) { + function interfaceFunction(value) { + if (shape.c.ix === value || value === 'Copies') { + return interfaceFunction.copies; + } if (shape.o.ix === value || value === 'Offset') { + return interfaceFunction.offset; + } + return null; } - ShapeCollection.prototype.addShape = function (shapeData) { - if (this._length === this._maxLength) { - this.shapes = this.shapes.concat(createSizedArray(this._maxLength)); - this._maxLength *= 2; + var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); + var prop = view; + interfaceFunction.propertyIndex = shape.ix; + prop.c.setGroupProperty(PropertyInterface('Copies', _propertyGroup)); + prop.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); + Object.defineProperties(interfaceFunction, { + copies: { + get: ExpressionPropertyInterface(prop.c), + }, + offset: { + get: ExpressionPropertyInterface(prop.o), + }, + _name: { value: shape.nm }, + }); + interfaceFunction.mn = shape.mn; + return interfaceFunction; + } + + return function (shapes, view, propertyGroup) { + var interfaces; + function _interfaceFunction(value) { + if (typeof value === 'number') { + value = value === undefined ? 1 : value; + if (value === 0) { + return propertyGroup; + } + return interfaces[value - 1]; } - this.shapes[this._length] = shapeData; - this._length += 1; - }; - - ShapeCollection.prototype.releaseShapes = function () { - var i; - for (i = 0; i < this._length; i += 1) { - shapePool.release(this.shapes[i]); + var i = 0; + var len = interfaces.length; + while (i < len) { + if (interfaces[i]._name === value) { + return interfaces[i]; + } + i += 1; } - this._length = 0; - }; - - const shapeCollectionPool = (function () { - var ob = { - newShapeCollection: newShapeCollection, - release: release, - }; + return null; + } + function parentGroupWrapper() { + return propertyGroup; + } + _interfaceFunction.propertyGroup = propertyGroupFactory(_interfaceFunction, parentGroupWrapper); + interfaces = iterateElements(shapes, view, _interfaceFunction.propertyGroup); + _interfaceFunction.numProperties = interfaces.length; + _interfaceFunction._name = 'Contents'; + return _interfaceFunction; + }; +}()); + +const TextExpressionInterface = (function () { + return function (elem) { + var _prevValue; + var _sourceText; + function _thisLayerFunction(name) { + switch (name) { + case 'ADBE Text Document': + return _thisLayerFunction.sourceText; + default: + return null; + } + } + Object.defineProperty(_thisLayerFunction, 'sourceText', { + get: function () { + elem.textProperty.getValue(); + var stringValue = elem.textProperty.currentData.t; + if (stringValue !== _prevValue) { + elem.textProperty.currentData.t = _prevValue; + _sourceText = new String(stringValue); // eslint-disable-line no-new-wrappers + // If stringValue is an empty string, eval returns undefined, so it has to be returned as a String primitive + _sourceText.value = stringValue || new String(stringValue); // eslint-disable-line no-new-wrappers + } + return _sourceText; + }, + }); + return _thisLayerFunction; + }; +}()); + +const getBlendMode = (function () { + var blendModeEnums = { + 0: 'source-over', + 1: 'multiply', + 2: 'screen', + 3: 'overlay', + 4: 'darken', + 5: 'lighten', + 6: 'color-dodge', + 7: 'color-burn', + 8: 'hard-light', + 9: 'soft-light', + 10: 'difference', + 11: 'exclusion', + 12: 'hue', + 13: 'saturation', + 14: 'color', + 15: 'luminosity', + }; + + return function (mode) { + return blendModeEnums[mode] || ''; + }; +}()); + +function SliderEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); +} +function AngleEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); +} +function ColorEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); +} +function PointEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); +} +function LayerIndexEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); +} +function MaskIndexEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); +} +function CheckboxEffect(data, elem, container) { + this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); +} +function NoValueEffect() { + this.p = {}; +} + +function EffectsManager(data, element) { + var effects = data.ef || []; + this.effectElements = []; + var i; + var len = effects.length; + var effectItem; + for (i = 0; i < len; i += 1) { + effectItem = new GroupEffect(effects[i], element); + this.effectElements.push(effectItem); + } +} + +function GroupEffect(data, element) { + this.init(data, element); +} + +extendPrototype([DynamicPropertyContainer], GroupEffect); + +GroupEffect.prototype.getValue = GroupEffect.prototype.iterateDynamicProperties; + +GroupEffect.prototype.init = function (data, element) { + this.data = data; + this.effectElements = []; + this.initDynamicPropertyContainer(element); + var i; + var len = this.data.ef.length; + var eff; + var effects = this.data.ef; + for (i = 0; i < len; i += 1) { + eff = null; + switch (effects[i].ty) { + case 0: + eff = new SliderEffect(effects[i], element, this); + break; + case 1: + eff = new AngleEffect(effects[i], element, this); + break; + case 2: + eff = new ColorEffect(effects[i], element, this); + break; + case 3: + eff = new PointEffect(effects[i], element, this); + break; + case 4: + case 7: + eff = new CheckboxEffect(effects[i], element, this); + break; + case 10: + eff = new LayerIndexEffect(effects[i], element, this); + break; + case 11: + eff = new MaskIndexEffect(effects[i], element, this); + break; + case 5: + eff = new EffectsManager(effects[i], element, this); + break; + // case 6: + default: + eff = new NoValueEffect(effects[i], element, this); + break; + } + if (eff) { + this.effectElements.push(eff); + } + } +}; - var _length = 0; - var _maxLength = 4; - var pool = createSizedArray(_maxLength); +function BaseElement() { +} - function newShapeCollection() { - var shapeCollection; - if (_length) { - _length -= 1; - shapeCollection = pool[_length]; - } else { - shapeCollection = new ShapeCollection(); +BaseElement.prototype = { + checkMasks: function () { + if (!this.data.hasMask) { + return false; + } + var i = 0; + var len = this.data.masksProperties.length; + while (i < len) { + if ((this.data.masksProperties[i].mode !== 'n' && this.data.masksProperties[i].cl !== false)) { + return true; + } + i += 1; + } + return false; + }, + initExpressions: function () { + this.layerInterface = LayerExpressionInterface(this); + if (this.data.hasMask && this.maskManager) { + this.layerInterface.registerMaskInterface(this.maskManager); + } + var effectsInterface = EffectsExpressionInterface.createEffectsInterface(this, this.layerInterface); + this.layerInterface.registerEffectsInterface(effectsInterface); + + if (this.data.ty === 0 || this.data.xt) { + this.compInterface = CompExpressionInterface(this); + } else if (this.data.ty === 4) { + this.layerInterface.shapeInterface = ShapeExpressionInterface(this.shapesData, this.itemsData, this.layerInterface); + this.layerInterface.content = this.layerInterface.shapeInterface; + } else if (this.data.ty === 5) { + this.layerInterface.textInterface = TextExpressionInterface(this); + this.layerInterface.text = this.layerInterface.textInterface; + } + }, + setBlendMode: function () { + var blendModeValue = getBlendMode(this.data.bm); + var elem = this.baseElement || this.layerElement; + + elem.style['mix-blend-mode'] = blendModeValue; + }, + initBaseData: function (data, globalData, comp) { + this.globalData = globalData; + this.comp = comp; + this.data = data; + this.layerId = createElementID(); + + // Stretch factor for old animations missing this property. + if (!this.data.sr) { + this.data.sr = 1; + } + // effects manager + this.effectsManager = new EffectsManager(this.data, this, this.dynamicProperties); + }, + getType: function () { + return this.type; + }, + sourceRectAtTime: function () {}, +}; + +/** + * @file + * Handles element's layer frame update. + * Checks layer in point and out point + * + */ + +function FrameElement() {} + +FrameElement.prototype = { + /** + * @function + * Initializes frame related properties. + * + */ + initFrame: function () { + // set to true when inpoint is rendered + this._isFirstFrame = false; + // list of animated properties + this.dynamicProperties = []; + // If layer has been modified in current tick this will be true + this._mdf = false; + }, + /** + * @function + * Calculates all dynamic values + * + * @param {number} num + * current frame number in Layer's time + * @param {boolean} isVisible + * if layers is currently in range + * + */ + prepareProperties: function (num, isVisible) { + var i; + var len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + if (isVisible || (this._isParent && this.dynamicProperties[i].propType === 'transform')) { + this.dynamicProperties[i].getValue(); + if (this.dynamicProperties[i]._mdf) { + this.globalData._mdf = true; + this._mdf = true; } - return shapeCollection; } - - function release(shapeCollection) { - var i; - var len = shapeCollection._length; - for (i = 0; i < len; i += 1) { - shapePool.release(shapeCollection.shapes[i]); + } + }, + addDynamicProperty: function (prop) { + if (this.dynamicProperties.indexOf(prop) === -1) { + this.dynamicProperties.push(prop); + } + }, +}; + +const FootageInterface = (function () { + var outlineInterfaceFactory = (function (elem) { + var currentPropertyName = ''; + var currentProperty = elem.getFootageData(); + function init() { + currentPropertyName = ''; + currentProperty = elem.getFootageData(); + return searchProperty; + } + function searchProperty(value) { + if (currentProperty[value]) { + currentPropertyName = value; + currentProperty = currentProperty[value]; + if (typeof currentProperty === 'object') { + return searchProperty; } - shapeCollection._length = 0; - - if (_length === _maxLength) { - pool = pooling.double(pool); - _maxLength *= 2; + return currentProperty; + } + var propertyNameIndex = value.indexOf(currentPropertyName); + if (propertyNameIndex !== -1) { + var index = parseInt(value.substr(propertyNameIndex + currentPropertyName.length), 10); + currentProperty = currentProperty[index]; + if (typeof currentProperty === 'object') { + return searchProperty; } - pool[_length] = shapeCollection; - _length += 1; + return currentProperty; } + return ''; + } + return init; + }); - return ob; - }()); - - const ShapePropertyFactory = (function () { - var initFrame = -999999; + var dataInterfaceFactory = function (elem) { + function interfaceFunction(value) { + if (value === 'Outline') { + return interfaceFunction.outlineInterface(); + } + return null; + } - function interpolateShape(frameNum, previousValue, caching) { - var iterationIndex = caching.lastIndex; - var keyPropS; - var keyPropE; - var isHold; - var j; - var k; - var jLen; - var kLen; - var perc; - var vertexValue; - var kf = this.keyframes; - if (frameNum < kf[0].t - this.offsetTime) { - keyPropS = kf[0].s[0]; - isHold = true; - iterationIndex = 0; - } else if (frameNum >= kf[kf.length - 1].t - this.offsetTime) { - keyPropS = kf[kf.length - 1].s ? kf[kf.length - 1].s[0] : kf[kf.length - 2].e[0]; - /* if(kf[kf.length - 1].s){ - keyPropS = kf[kf.length - 1].s[0]; - }else{ - keyPropS = kf[kf.length - 2].e[0]; - } */ - isHold = true; - } else { - var i = iterationIndex; - var len = kf.length - 1; - var flag = true; - var keyData; - var nextKeyData; - var keyframeMetadata; - while (flag) { - keyData = kf[i]; - nextKeyData = kf[i + 1]; - if ((nextKeyData.t - this.offsetTime) > frameNum) { - break; - } - if (i < len - 1) { - i += 1; - } else { - flag = false; - } - } - keyframeMetadata = this.keyframesMetadata[i] || {}; - isHold = keyData.h === 1; - iterationIndex = i; - if (!isHold) { - if (frameNum >= nextKeyData.t - this.offsetTime) { - perc = 1; - } else if (frameNum < keyData.t - this.offsetTime) { - perc = 0; - } else { - var fnc; - if (keyframeMetadata.__fnct) { - fnc = keyframeMetadata.__fnct; - } else { - fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y).get; - keyframeMetadata.__fnct = fnc; - } - perc = fnc((frameNum - (keyData.t - this.offsetTime)) / ((nextKeyData.t - this.offsetTime) - (keyData.t - this.offsetTime))); - } - keyPropE = nextKeyData.s ? nextKeyData.s[0] : keyData.e[0]; - } - keyPropS = keyData.s[0]; - } - jLen = previousValue._length; - kLen = keyPropS.i[0].length; - caching.lastIndex = iterationIndex; + interfaceFunction._name = 'Outline'; + interfaceFunction.outlineInterface = outlineInterfaceFactory(elem); + return interfaceFunction; + }; - for (j = 0; j < jLen; j += 1) { - for (k = 0; k < kLen; k += 1) { - vertexValue = isHold ? keyPropS.i[j][k] : keyPropS.i[j][k] + (keyPropE.i[j][k] - keyPropS.i[j][k]) * perc; - previousValue.i[j][k] = vertexValue; - vertexValue = isHold ? keyPropS.o[j][k] : keyPropS.o[j][k] + (keyPropE.o[j][k] - keyPropS.o[j][k]) * perc; - previousValue.o[j][k] = vertexValue; - vertexValue = isHold ? keyPropS.v[j][k] : keyPropS.v[j][k] + (keyPropE.v[j][k] - keyPropS.v[j][k]) * perc; - previousValue.v[j][k] = vertexValue; - } - } + return function (elem) { + function _interfaceFunction(value) { + if (value === 'Data') { + return _interfaceFunction.dataInterface; } + return null; + } - function interpolateShapeCurrentTime() { - var frameNum = this.comp.renderedFrame - this.offsetTime; - var initTime = this.keyframes[0].t - this.offsetTime; - var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime; - var lastFrame = this._caching.lastFrame; - if (!(lastFrame !== initFrame && ((lastFrame < initTime && frameNum < initTime) || (lastFrame > endTime && frameNum > endTime)))) { - /// / - this._caching.lastIndex = lastFrame < frameNum ? this._caching.lastIndex : 0; - this.interpolateShape(frameNum, this.pv, this._caching); - /// / + _interfaceFunction._name = 'Data'; + _interfaceFunction.dataInterface = dataInterfaceFactory(elem); + return _interfaceFunction; + }; +}()); + +function FootageElement(data, globalData, comp) { + this.initFrame(); + this.initRenderable(); + this.assetData = globalData.getAssetData(data.refId); + this.footageData = globalData.imageLoader.getAsset(this.assetData); + this.initBaseData(data, globalData, comp); +} + +FootageElement.prototype.prepareFrame = function () { +}; + +extendPrototype([RenderableElement, BaseElement, FrameElement], FootageElement); + +FootageElement.prototype.getBaseElement = function () { + return null; +}; + +FootageElement.prototype.renderFrame = function () { +}; + +FootageElement.prototype.destroy = function () { +}; + +FootageElement.prototype.initExpressions = function () { + this.layerInterface = FootageInterface(this); +}; + +FootageElement.prototype.getFootageData = function () { + return this.footageData; +}; + +function AudioElement(data, globalData, comp) { + this.initFrame(); + this.initRenderable(); + this.assetData = globalData.getAssetData(data.refId); + this.initBaseData(data, globalData, comp); + this._isPlaying = false; + this._canPlay = false; + var assetPath = this.globalData.getAssetsPath(this.assetData); + this.audio = this.globalData.audioController.createAudio(assetPath); + this._currentTime = 0; + this.globalData.audioController.addAudio(this); + this._volumeMultiplier = 1; + this._volume = 1; + this._previousVolume = null; + this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; + this.lv = PropertyFactory.getProp(this, data.au && data.au.lv ? data.au.lv : { k: [100] }, 1, 0.01, this); +} + +AudioElement.prototype.prepareFrame = function (num) { + this.prepareRenderableFrame(num, true); + this.prepareProperties(num, true); + if (!this.tm._placeholder) { + var timeRemapped = this.tm.v; + this._currentTime = timeRemapped; + } else { + this._currentTime = num / this.data.sr; + } + this._volume = this.lv.v[0]; + var totalVolume = this._volume * this._volumeMultiplier; + if (this._previousVolume !== totalVolume) { + this._previousVolume = totalVolume; + this.audio.volume(totalVolume); + } +}; + +extendPrototype([RenderableElement, BaseElement, FrameElement], AudioElement); + +AudioElement.prototype.renderFrame = function () { + if (this.isInRange && this._canPlay) { + if (!this._isPlaying) { + this.audio.play(); + this.audio.seek(this._currentTime / this.globalData.frameRate); + this._isPlaying = true; + } else if (!this.audio.playing() + || Math.abs(this._currentTime / this.globalData.frameRate - this.audio.seek()) > 0.1 + ) { + this.audio.seek(this._currentTime / this.globalData.frameRate); + } + } +}; + +AudioElement.prototype.show = function () { + // this.audio.play() +}; + +AudioElement.prototype.hide = function () { + this.audio.pause(); + this._isPlaying = false; +}; + +AudioElement.prototype.pause = function () { + this.audio.pause(); + this._isPlaying = false; + this._canPlay = false; +}; + +AudioElement.prototype.resume = function () { + this._canPlay = true; +}; + +AudioElement.prototype.setRate = function (rateValue) { + this.audio.rate(rateValue); +}; + +AudioElement.prototype.volume = function (volumeValue) { + this._volumeMultiplier = volumeValue; + this._previousVolume = volumeValue * this._volume; + this.audio.volume(this._previousVolume); +}; + +AudioElement.prototype.getBaseElement = function () { + return null; +}; + +AudioElement.prototype.destroy = function () { +}; + +AudioElement.prototype.sourceRectAtTime = function () { +}; + +AudioElement.prototype.initExpressions = function () { +}; + +function BaseRenderer() {} +BaseRenderer.prototype.checkLayers = function (num) { + var i; + var len = this.layers.length; + var data; + this.completeLayers = true; + for (i = len - 1; i >= 0; i -= 1) { + if (!this.elements[i]) { + data = this.layers[i]; + if (data.ip - data.st <= (num - this.layers[i].st) && data.op - data.st > (num - this.layers[i].st)) { + this.buildItem(i); + } + } + this.completeLayers = this.elements[i] ? this.completeLayers : false; + } + this.checkPendingElements(); +}; + +BaseRenderer.prototype.createItem = function (layer) { + switch (layer.ty) { + case 2: + return this.createImage(layer); + case 0: + return this.createComp(layer); + case 1: + return this.createSolid(layer); + case 3: + return this.createNull(layer); + case 4: + return this.createShape(layer); + case 5: + return this.createText(layer); + case 6: + return this.createAudio(layer); + case 13: + return this.createCamera(layer); + case 15: + return this.createFootage(layer); + default: + return this.createNull(layer); + } +}; + +BaseRenderer.prototype.createCamera = function () { + throw new Error('You\'re using a 3d camera. Try the html renderer.'); +}; + +BaseRenderer.prototype.createAudio = function (data) { + return new AudioElement(data, this.globalData, this); +}; + +BaseRenderer.prototype.createFootage = function (data) { + return new FootageElement(data, this.globalData, this); +}; + +BaseRenderer.prototype.buildAllItems = function () { + var i; + var len = this.layers.length; + for (i = 0; i < len; i += 1) { + this.buildItem(i); + } + this.checkPendingElements(); +}; + +BaseRenderer.prototype.includeLayers = function (newLayers) { + this.completeLayers = false; + var i; + var len = newLayers.length; + var j; + var jLen = this.layers.length; + for (i = 0; i < len; i += 1) { + j = 0; + while (j < jLen) { + if (this.layers[j].id === newLayers[i].id) { + this.layers[j] = newLayers[i]; + break; + } + j += 1; + } + } +}; + +BaseRenderer.prototype.setProjectInterface = function (pInterface) { + this.globalData.projectInterface = pInterface; +}; + +BaseRenderer.prototype.initItems = function () { + if (!this.globalData.progressiveLoad) { + this.buildAllItems(); + } +}; +BaseRenderer.prototype.buildElementParenting = function (element, parentName, hierarchy) { + var elements = this.elements; + var layers = this.layers; + var i = 0; + var len = layers.length; + while (i < len) { + if (layers[i].ind == parentName) { // eslint-disable-line eqeqeq + if (!elements[i] || elements[i] === true) { + this.buildItem(i); + this.addPendingElement(element); + } else { + hierarchy.push(elements[i]); + elements[i].setAsParent(); + if (layers[i].parent !== undefined) { + this.buildElementParenting(element, layers[i].parent, hierarchy); + } else { + element.setHierarchy(hierarchy); } - this._caching.lastFrame = frameNum; - return this.pv; } - - function resetShape() { - this.paths = this.localShapeCollection; + } + i += 1; + } +}; + +BaseRenderer.prototype.addPendingElement = function (element) { + this.pendingElements.push(element); +}; + +BaseRenderer.prototype.searchExtraCompositions = function (assets) { + var i; + var len = assets.length; + for (i = 0; i < len; i += 1) { + if (assets[i].xt) { + var comp = this.createComp(assets[i]); + comp.initExpressions(); + this.globalData.projectInterface.registerComposition(comp); + } + } +}; + +BaseRenderer.prototype.getElementByPath = function (path) { + var pathValue = path.shift(); + var element; + if (typeof pathValue === 'number') { + element = this.elements[pathValue]; + } else { + var i; + var len = this.elements.length; + for (i = 0; i < len; i += 1) { + if (this.elements[i].data.nm === pathValue) { + element = this.elements[i]; + break; } + } + } + if (path.length === 0) { + return element; + } + return element.getElementByPath(path); +}; + +BaseRenderer.prototype.setupGlobalData = function (animData, fontsContainer) { + this.globalData.fontManager = new FontManager(); + this.globalData.fontManager.addChars(animData.chars); + this.globalData.fontManager.addFonts(animData.fonts, fontsContainer); + this.globalData.getAssetData = this.animationItem.getAssetData.bind(this.animationItem); + this.globalData.getAssetsPath = this.animationItem.getAssetsPath.bind(this.animationItem); + this.globalData.imageLoader = this.animationItem.imagePreloader; + this.globalData.audioController = this.animationItem.audioController; + this.globalData.frameId = 0; + this.globalData.frameRate = animData.fr; + this.globalData.nm = animData.nm; + this.globalData.compSize = { + w: animData.w, + h: animData.h, + }; +}; + +function TransformElement() {} + +TransformElement.prototype = { + initTransform: function () { + this.finalTransform = { + mProp: this.data.ks ? TransformPropertyFactory.getTransformProperty(this, this.data.ks, this) : { o: 0 }, + _matMdf: false, + _opMdf: false, + mat: new Matrix(), + }; + if (this.data.ao) { + this.finalTransform.mProp.autoOriented = true; + } - function shapesEqual(shape1, shape2) { - if (shape1._length !== shape2._length || shape1.c !== shape2.c) { - return false; - } - var i; - var len = shape1._length; - for (i = 0; i < len; i += 1) { - if (shape1.v[i][0] !== shape2.v[i][0] - || shape1.v[i][1] !== shape2.v[i][1] - || shape1.o[i][0] !== shape2.o[i][0] - || shape1.o[i][1] !== shape2.o[i][1] - || shape1.i[i][0] !== shape2.i[i][0] - || shape1.i[i][1] !== shape2.i[i][1]) { - return false; + // TODO: check TYPE 11: Guided elements + if (this.data.ty !== 11) { + // this.createElements(); + } + }, + renderTransform: function () { + this.finalTransform._opMdf = this.finalTransform.mProp.o._mdf || this._isFirstFrame; + this.finalTransform._matMdf = this.finalTransform.mProp._mdf || this._isFirstFrame; + + if (this.hierarchy) { + var mat; + var finalMat = this.finalTransform.mat; + var i = 0; + var len = this.hierarchy.length; + // Checking if any of the transformation matrices in the hierarchy chain has changed. + if (!this.finalTransform._matMdf) { + while (i < len) { + if (this.hierarchy[i].finalTransform.mProp._mdf) { + this.finalTransform._matMdf = true; + break; } + i += 1; } - return true; } - function setVValue(newPath) { - if (!shapesEqual(this.v, newPath)) { - this.v = shapePool.clone(newPath); - this.localShapeCollection.releaseShapes(); - this.localShapeCollection.addShape(this.v); - this._mdf = true; - this.paths = this.localShapeCollection; + if (this.finalTransform._matMdf) { + mat = this.finalTransform.mProp.v.props; + finalMat.cloneFromProps(mat); + for (i = 0; i < len; i += 1) { + mat = this.hierarchy[i].finalTransform.mProp.v.props; + finalMat.transform(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5], mat[6], mat[7], mat[8], mat[9], mat[10], mat[11], mat[12], mat[13], mat[14], mat[15]); } } - - function processEffectsSequence() { - if (this.elem.globalData.frameId === this.frameId) { - return; - } if (!this.effectsSequence.length) { - this._mdf = false; - return; - } - if (this.lock) { - this.setVValue(this.pv); - return; - } - this.lock = true; - this._mdf = false; - var finalValue; - if (this.kf) { - finalValue = this.pv; - } else if (this.data.ks) { - finalValue = this.data.ks.k; - } else { - finalValue = this.data.pt.k; - } - var i; - var len = this.effectsSequence.length; - for (i = 0; i < len; i += 1) { - finalValue = this.effectsSequence[i](finalValue); - } - this.setVValue(finalValue); - this.lock = false; - this.frameId = this.elem.globalData.frameId; + } + }, + globalToLocal: function (pt) { + var transforms = []; + transforms.push(this.finalTransform); + var flag = true; + var comp = this.comp; + while (flag) { + if (comp.finalTransform) { + if (comp.data.hasMask) { + transforms.splice(0, 0, comp.finalTransform); + } + comp = comp.comp; + } else { + flag = false; } + } + var i; + var len = transforms.length; + var ptNew; + for (i = 0; i < len; i += 1) { + ptNew = transforms[i].mat.applyToPointArray(0, 0, 0); + // ptNew = transforms[i].mat.applyToPointArray(pt[0],pt[1],pt[2]); + pt = [pt[0] - ptNew[0], pt[1] - ptNew[1], 0]; + } + return pt; + }, + mHelper: new Matrix(), +}; + +function MaskElement(data, element, globalData) { + this.data = data; + this.element = element; + this.globalData = globalData; + this.storedData = []; + this.masksProperties = this.data.masksProperties || []; + this.maskElement = null; + var defs = this.globalData.defs; + var i; + var len = this.masksProperties ? this.masksProperties.length : 0; + this.viewData = createSizedArray(len); + this.solidPath = ''; + + var path; + var properties = this.masksProperties; + var count = 0; + var currentMasks = []; + var j; + var jLen; + var layerId = createElementID(); + var rect; + var expansor; + var feMorph; + var x; + var maskType = 'clipPath'; + var maskRef = 'clip-path'; + for (i = 0; i < len; i += 1) { + if ((properties[i].mode !== 'a' && properties[i].mode !== 'n') || properties[i].inv || properties[i].o.k !== 100 || properties[i].o.x) { + maskType = 'mask'; + maskRef = 'mask'; + } - function ShapeProperty(elem, data, type) { - this.propType = 'shape'; - this.comp = elem.comp; - this.container = elem; - this.elem = elem; - this.data = data; - this.k = false; - this.kf = false; - this._mdf = false; - var pathData = type === 3 ? data.pt.k : data.ks.k; - this.v = shapePool.clone(pathData); - this.pv = shapePool.clone(this.v); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.paths = this.localShapeCollection; - this.paths.addShape(this.v); - this.reset = resetShape; - this.effectsSequence = []; - } - - function addEffect(effectFunction) { - this.effectsSequence.push(effectFunction); - this.container.addDynamicProperty(this); - } - - ShapeProperty.prototype.interpolateShape = interpolateShape; - ShapeProperty.prototype.getValue = processEffectsSequence; - ShapeProperty.prototype.setVValue = setVValue; - ShapeProperty.prototype.addEffect = addEffect; - - function KeyframedShapeProperty(elem, data, type) { - this.propType = 'shape'; - this.comp = elem.comp; - this.elem = elem; - this.container = elem; - this.offsetTime = elem.data.st; - this.keyframes = type === 3 ? data.pt.k : data.ks.k; - this.keyframesMetadata = []; - this.k = true; - this.kf = true; - var len = this.keyframes[0].s[0].i.length; - this.v = shapePool.newElement(); - this.v.setPathData(this.keyframes[0].s[0].c, len); - this.pv = shapePool.clone(this.v); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.paths = this.localShapeCollection; - this.paths.addShape(this.v); - this.lastFrame = initFrame; - this.reset = resetShape; - this._caching = { lastFrame: initFrame, lastIndex: 0 }; - this.effectsSequence = [interpolateShapeCurrentTime.bind(this)]; - } - KeyframedShapeProperty.prototype.getValue = processEffectsSequence; - KeyframedShapeProperty.prototype.interpolateShape = interpolateShape; - KeyframedShapeProperty.prototype.setVValue = setVValue; - KeyframedShapeProperty.prototype.addEffect = addEffect; - - var EllShapeProperty = (function () { - var cPoint = roundCorner; - - function EllShapePropertyFactory(elem, data) { - this.v = shapePool.newElement(); - this.v.setPathData(true, 4); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.paths = this.localShapeCollection; - this.localShapeCollection.addShape(this.v); - this.d = data.d; - this.elem = elem; - this.comp = elem.comp; - this.frameId = -1; - this.initDynamicPropertyContainer(elem); - this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); - this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.k = false; - this.convertEllToPath(); - } - } - - EllShapePropertyFactory.prototype = { - reset: resetShape, - getValue: function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - - if (this._mdf) { - this.convertEllToPath(); - } - }, - convertEllToPath: function () { - var p0 = this.p.v[0]; - var p1 = this.p.v[1]; - var s0 = this.s.v[0] / 2; - var s1 = this.s.v[1] / 2; - var _cw = this.d !== 3; - var _v = this.v; - _v.v[0][0] = p0; - _v.v[0][1] = p1 - s1; - _v.v[1][0] = _cw ? p0 + s0 : p0 - s0; - _v.v[1][1] = p1; - _v.v[2][0] = p0; - _v.v[2][1] = p1 + s1; - _v.v[3][0] = _cw ? p0 - s0 : p0 + s0; - _v.v[3][1] = p1; - _v.i[0][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; - _v.i[0][1] = p1 - s1; - _v.i[1][0] = _cw ? p0 + s0 : p0 - s0; - _v.i[1][1] = p1 - s1 * cPoint; - _v.i[2][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; - _v.i[2][1] = p1 + s1; - _v.i[3][0] = _cw ? p0 - s0 : p0 + s0; - _v.i[3][1] = p1 + s1 * cPoint; - _v.o[0][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint; - _v.o[0][1] = p1 - s1; - _v.o[1][0] = _cw ? p0 + s0 : p0 - s0; - _v.o[1][1] = p1 + s1 * cPoint; - _v.o[2][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint; - _v.o[2][1] = p1 + s1; - _v.o[3][0] = _cw ? p0 - s0 : p0 + s0; - _v.o[3][1] = p1 - s1 * cPoint; - }, - }; - - extendPrototype([DynamicPropertyContainer], EllShapePropertyFactory); - - return EllShapePropertyFactory; - }()); - - var StarShapeProperty = (function () { - function StarShapePropertyFactory(elem, data) { - this.v = shapePool.newElement(); - this.v.setPathData(true, 0); - this.elem = elem; - this.comp = elem.comp; - this.data = data; - this.frameId = -1; - this.d = data.d; - this.initDynamicPropertyContainer(elem); - if (data.sy === 1) { - this.ir = PropertyFactory.getProp(elem, data.ir, 0, 0, this); - this.is = PropertyFactory.getProp(elem, data.is, 0, 0.01, this); - this.convertToPath = this.convertStarToPath; - } else { - this.convertToPath = this.convertPolygonToPath; - } - this.pt = PropertyFactory.getProp(elem, data.pt, 0, 0, this); - this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); - this.r = PropertyFactory.getProp(elem, data.r, 0, degToRads, this); - this.or = PropertyFactory.getProp(elem, data.or, 0, 0, this); - this.os = PropertyFactory.getProp(elem, data.os, 0, 0.01, this); - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.localShapeCollection.addShape(this.v); - this.paths = this.localShapeCollection; - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.k = false; - this.convertToPath(); - } - } - - StarShapePropertyFactory.prototype = { - reset: resetShape, - getValue: function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - if (this._mdf) { - this.convertToPath(); - } - }, - convertStarToPath: function () { - var numPts = Math.floor(this.pt.v) * 2; - var angle = (Math.PI * 2) / numPts; - /* this.v.v.length = numPts; - this.v.i.length = numPts; - this.v.o.length = numPts; */ - var longFlag = true; - var longRad = this.or.v; - var shortRad = this.ir.v; - var longRound = this.os.v; - var shortRound = this.is.v; - var longPerimSegment = (2 * Math.PI * longRad) / (numPts * 2); - var shortPerimSegment = (2 * Math.PI * shortRad) / (numPts * 2); - var i; - var rad; - var roundness; - var perimSegment; - var currentAng = -Math.PI / 2; - currentAng += this.r.v; - var dir = this.data.d === 3 ? -1 : 1; - this.v._length = 0; - for (i = 0; i < numPts; i += 1) { - rad = longFlag ? longRad : shortRad; - roundness = longFlag ? longRound : shortRound; - perimSegment = longFlag ? longPerimSegment : shortPerimSegment; - var x = rad * Math.cos(currentAng); - var y = rad * Math.sin(currentAng); - var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); - var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); - x += +this.p.v[0]; - y += +this.p.v[1]; - this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); - - /* this.v.v[i] = [x,y]; - this.v.i[i] = [x+ox*perimSegment*roundness*dir,y+oy*perimSegment*roundness*dir]; - this.v.o[i] = [x-ox*perimSegment*roundness*dir,y-oy*perimSegment*roundness*dir]; - this.v._length = numPts; */ - longFlag = !longFlag; - currentAng += angle * dir; - } - }, - convertPolygonToPath: function () { - var numPts = Math.floor(this.pt.v); - var angle = (Math.PI * 2) / numPts; - var rad = this.or.v; - var roundness = this.os.v; - var perimSegment = (2 * Math.PI * rad) / (numPts * 4); - var i; - var currentAng = -Math.PI * 0.5; - var dir = this.data.d === 3 ? -1 : 1; - currentAng += this.r.v; - this.v._length = 0; - for (i = 0; i < numPts; i += 1) { - var x = rad * Math.cos(currentAng); - var y = rad * Math.sin(currentAng); - var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); - var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); - x += +this.p.v[0]; - y += +this.p.v[1]; - this.v.setTripleAt(x, y, x - ox * perimSegment * roundness * dir, y - oy * perimSegment * roundness * dir, x + ox * perimSegment * roundness * dir, y + oy * perimSegment * roundness * dir, i, true); - currentAng += angle * dir; - } - this.paths.length = 0; - this.paths[0] = this.v; - }, - - }; - extendPrototype([DynamicPropertyContainer], StarShapePropertyFactory); - - return StarShapePropertyFactory; - }()); - - var RectShapeProperty = (function () { - function RectShapePropertyFactory(elem, data) { - this.v = shapePool.newElement(); - this.v.c = true; - this.localShapeCollection = shapeCollectionPool.newShapeCollection(); - this.localShapeCollection.addShape(this.v); - this.paths = this.localShapeCollection; - this.elem = elem; - this.comp = elem.comp; - this.frameId = -1; - this.d = data.d; - this.initDynamicPropertyContainer(elem); - this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this); - this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this); - this.r = PropertyFactory.getProp(elem, data.r, 0, 0, this); - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.k = false; - this.convertRectToPath(); - } - } + if ((properties[i].mode === 's' || properties[i].mode === 'i') && count === 0) { + rect = createNS('rect'); + rect.setAttribute('fill', '#ffffff'); + rect.setAttribute('width', this.element.comp.data.w || 0); + rect.setAttribute('height', this.element.comp.data.h || 0); + currentMasks.push(rect); + } else { + rect = null; + } - RectShapePropertyFactory.prototype = { - convertRectToPath: function () { - var p0 = this.p.v[0]; - var p1 = this.p.v[1]; - var v0 = this.s.v[0] / 2; - var v1 = this.s.v[1] / 2; - var round = bmMin(v0, v1, this.r.v); - var cPoint = round * (1 - roundCorner); - this.v._length = 0; - - if (this.d === 2 || this.d === 1) { - this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, 0, true); - this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, p0 + v0, p1 + v1 - round, 1, true); - if (round !== 0) { - this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, 2, true); - this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0 + round, p1 + v1, 3, true); - this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, 4, true); - this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1 + round, 5, true); - this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, 6, true); - this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, p0 + v0 - round, p1 - v1, 7, true); - } else { - this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0, p1 + v1, 2); - this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1, 3); - } - } else { - this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, p0 + v0, p1 - v1 + round, 0, true); - if (round !== 0) { - this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, 1, true); - this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0 + round, p1 - v1, 2, true); - this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, 3, true); - this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1 - round, 4, true); - this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, 5, true); - this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0 - round, p1 + v1, 6, true); - this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, 7, true); - } else { - this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0, p1 - v1, 1, true); - this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1, 2, true); - this.v.setTripleAt(p0 + v0, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0, p1 + v1, 3, true); - } - } - }, - getValue: function () { - if (this.elem.globalData.frameId === this.frameId) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - if (this._mdf) { - this.convertRectToPath(); - } - }, - reset: resetShape, - }; - extendPrototype([DynamicPropertyContainer], RectShapePropertyFactory); - - return RectShapePropertyFactory; - }()); - - function getShapeProp(elem, data, type) { - var prop; - if (type === 3 || type === 4) { - var dataProp = type === 3 ? data.pt : data.ks; - var keys = dataProp.k; - if (keys.length) { - prop = new KeyframedShapeProperty(elem, data, type); - } else { - prop = new ShapeProperty(elem, data, type); - } - } else if (type === 5) { - prop = new RectShapeProperty(elem, data); - } else if (type === 6) { - prop = new EllShapeProperty(elem, data); - } else if (type === 7) { - prop = new StarShapeProperty(elem, data); - } - if (prop.k) { - elem.addDynamicProperty(prop); + path = createNS('path'); + if (properties[i].mode === 'n') { + // TODO move this to a factory or to a constructor + this.viewData[i] = { + op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), + prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), + elem: path, + lastPath: '', + }; + defs.appendChild(path); + } else { + count += 1; + + path.setAttribute('fill', properties[i].mode === 's' ? '#000000' : '#ffffff'); + path.setAttribute('clip-rule', 'nonzero'); + var filterID; + + if (properties[i].x.k !== 0) { + maskType = 'mask'; + maskRef = 'mask'; + x = PropertyFactory.getProp(this.element, properties[i].x, 0, null, this.element); + filterID = createElementID(); + expansor = createNS('filter'); + expansor.setAttribute('id', filterID); + feMorph = createNS('feMorphology'); + feMorph.setAttribute('operator', 'erode'); + feMorph.setAttribute('in', 'SourceGraphic'); + feMorph.setAttribute('radius', '0'); + expansor.appendChild(feMorph); + defs.appendChild(expansor); + path.setAttribute('stroke', properties[i].mode === 's' ? '#000000' : '#ffffff'); + } else { + feMorph = null; + x = null; + } + + // TODO move this to a factory or to a constructor + this.storedData[i] = { + elem: path, + x: x, + expan: feMorph, + lastPath: '', + lastOperator: '', + filterId: filterID, + lastRadius: 0, + }; + if (properties[i].mode === 'i') { + jLen = currentMasks.length; + var g = createNS('g'); + for (j = 0; j < jLen; j += 1) { + g.appendChild(currentMasks[j]); } - return prop; - } - - function getConstructorFunction() { - return ShapeProperty; - } - - function getKeyframedConstructorFunction() { - return KeyframedShapeProperty; + var mask = createNS('mask'); + mask.setAttribute('mask-type', 'alpha'); + mask.setAttribute('id', layerId + '_' + count); + mask.appendChild(path); + defs.appendChild(mask); + g.setAttribute('mask', 'url(' + getLocationHref() + '#' + layerId + '_' + count + ')'); + + currentMasks.length = 0; + currentMasks.push(g); + } else { + currentMasks.push(path); + } + if (properties[i].inv && !this.solidPath) { + this.solidPath = this.createLayerSolidPath(); + } + // TODO move this to a factory or to a constructor + this.viewData[i] = { + elem: path, + lastPath: '', + op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), + prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), + invRect: rect, + }; + if (!this.viewData[i].prop.k) { + this.drawPath(properties[i], this.viewData[i].prop.v, this.viewData[i]); } - - var ob = {}; - ob.getShapeProp = getShapeProp; - ob.getConstructorFunction = getConstructorFunction; - ob.getKeyframedConstructorFunction = getKeyframedConstructorFunction; - return ob; - }()); - - /*! - Transformation Matrix v2.0 - (c) Epistemex 2014-2015 - www.epistemex.com - By Ken Fyrstenberg - Contributions by leeoniya. - License: MIT, header required. - */ - - /** - * 2D transformation matrix object initialized with identity matrix. - * - * The matrix can synchronize a canvas context by supplying the context - * as an argument, or later apply current absolute transform to an - * existing context. - * - * All values are handled as floating point values. - * - * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix - * @prop {number} a - scale x - * @prop {number} b - shear y - * @prop {number} c - shear x - * @prop {number} d - scale y - * @prop {number} e - translate x - * @prop {number} f - translate y - * @prop {CanvasRenderingContext2D|null} [context=null] - set or get current canvas context - * @constructor - */ - - const Matrix = (function () { - var _cos = Math.cos; - var _sin = Math.sin; - var _tan = Math.tan; - var _rnd = Math.round; - - function reset() { - this.props[0] = 1; - this.props[1] = 0; - this.props[2] = 0; - this.props[3] = 0; - this.props[4] = 0; - this.props[5] = 1; - this.props[6] = 0; - this.props[7] = 0; - this.props[8] = 0; - this.props[9] = 0; - this.props[10] = 1; - this.props[11] = 0; - this.props[12] = 0; - this.props[13] = 0; - this.props[14] = 0; - this.props[15] = 1; - return this; - } - - function rotate(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - } - - function rotateX(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(1, 0, 0, 0, 0, mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1); - } - - function rotateY(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, 0, mSin, 0, 0, 1, 0, 0, -mSin, 0, mCos, 0, 0, 0, 0, 1); - } - - function rotateZ(angle) { - if (angle === 0) { - return this; - } - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - } - - function shear(sx, sy) { - return this._t(1, sy, sx, 1, 0, 0); - } - - function skew(ax, ay) { - return this.shear(_tan(ax), _tan(ay)); - } - - function skewFromAxis(ax, angle) { - var mCos = _cos(angle); - var mSin = _sin(angle); - return this._t(mCos, mSin, 0, 0, -mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) - ._t(1, 0, 0, 0, _tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) - ._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - // return this._t(mCos, mSin, -mSin, mCos, 0, 0)._t(1, 0, _tan(ax), 1, 0, 0)._t(mCos, -mSin, mSin, mCos, 0, 0); - } - - function scale(sx, sy, sz) { - if (!sz && sz !== 0) { - sz = 1; - } - if (sx === 1 && sy === 1 && sz === 1) { - return this; - } - return this._t(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1); - } - - function setTransform(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { - this.props[0] = a; - this.props[1] = b; - this.props[2] = c; - this.props[3] = d; - this.props[4] = e; - this.props[5] = f; - this.props[6] = g; - this.props[7] = h; - this.props[8] = i; - this.props[9] = j; - this.props[10] = k; - this.props[11] = l; - this.props[12] = m; - this.props[13] = n; - this.props[14] = o; - this.props[15] = p; - return this; - } - - function translate(tx, ty, tz) { - tz = tz || 0; - if (tx !== 0 || ty !== 0 || tz !== 0) { - return this._t(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1); - } - return this; - } - - function transform(a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2) { - var _p = this.props; - - if (a2 === 1 && b2 === 0 && c2 === 0 && d2 === 0 && e2 === 0 && f2 === 1 && g2 === 0 && h2 === 0 && i2 === 0 && j2 === 0 && k2 === 1 && l2 === 0) { - // NOTE: commenting this condition because TurboFan deoptimizes code when present - // if(m2 !== 0 || n2 !== 0 || o2 !== 0){ - _p[12] = _p[12] * a2 + _p[15] * m2; - _p[13] = _p[13] * f2 + _p[15] * n2; - _p[14] = _p[14] * k2 + _p[15] * o2; - _p[15] *= p2; - // } - this._identityCalculated = false; - return this; - } - - var a1 = _p[0]; - var b1 = _p[1]; - var c1 = _p[2]; - var d1 = _p[3]; - var e1 = _p[4]; - var f1 = _p[5]; - var g1 = _p[6]; - var h1 = _p[7]; - var i1 = _p[8]; - var j1 = _p[9]; - var k1 = _p[10]; - var l1 = _p[11]; - var m1 = _p[12]; - var n1 = _p[13]; - var o1 = _p[14]; - var p1 = _p[15]; - - /* matrix order (canvas compatible): - * ace - * bdf - * 001 - */ - _p[0] = a1 * a2 + b1 * e2 + c1 * i2 + d1 * m2; - _p[1] = a1 * b2 + b1 * f2 + c1 * j2 + d1 * n2; - _p[2] = a1 * c2 + b1 * g2 + c1 * k2 + d1 * o2; - _p[3] = a1 * d2 + b1 * h2 + c1 * l2 + d1 * p2; - - _p[4] = e1 * a2 + f1 * e2 + g1 * i2 + h1 * m2; - _p[5] = e1 * b2 + f1 * f2 + g1 * j2 + h1 * n2; - _p[6] = e1 * c2 + f1 * g2 + g1 * k2 + h1 * o2; - _p[7] = e1 * d2 + f1 * h2 + g1 * l2 + h1 * p2; - - _p[8] = i1 * a2 + j1 * e2 + k1 * i2 + l1 * m2; - _p[9] = i1 * b2 + j1 * f2 + k1 * j2 + l1 * n2; - _p[10] = i1 * c2 + j1 * g2 + k1 * k2 + l1 * o2; - _p[11] = i1 * d2 + j1 * h2 + k1 * l2 + l1 * p2; - - _p[12] = m1 * a2 + n1 * e2 + o1 * i2 + p1 * m2; - _p[13] = m1 * b2 + n1 * f2 + o1 * j2 + p1 * n2; - _p[14] = m1 * c2 + n1 * g2 + o1 * k2 + p1 * o2; - _p[15] = m1 * d2 + n1 * h2 + o1 * l2 + p1 * p2; - - this._identityCalculated = false; - return this; - } - - function isIdentity() { - if (!this._identityCalculated) { - this._identity = !(this.props[0] !== 1 || this.props[1] !== 0 || this.props[2] !== 0 || this.props[3] !== 0 || this.props[4] !== 0 || this.props[5] !== 1 || this.props[6] !== 0 || this.props[7] !== 0 || this.props[8] !== 0 || this.props[9] !== 0 || this.props[10] !== 1 || this.props[11] !== 0 || this.props[12] !== 0 || this.props[13] !== 0 || this.props[14] !== 0 || this.props[15] !== 1); - this._identityCalculated = true; - } - return this._identity; - } - - function equals(matr) { - var i = 0; - while (i < 16) { - if (matr.props[i] !== this.props[i]) { - return false; + } + } + + this.maskElement = createNS(maskType); + + len = currentMasks.length; + for (i = 0; i < len; i += 1) { + this.maskElement.appendChild(currentMasks[i]); + } + + if (count > 0) { + this.maskElement.setAttribute('id', layerId); + this.element.maskedElement.setAttribute(maskRef, 'url(' + getLocationHref() + '#' + layerId + ')'); + defs.appendChild(this.maskElement); + } + if (this.viewData.length) { + this.element.addRenderableComponent(this); + } +} + +MaskElement.prototype.getMaskProperty = function (pos) { + return this.viewData[pos].prop; +}; + +MaskElement.prototype.renderFrame = function (isFirstFrame) { + var finalMat = this.element.finalTransform.mat; + var i; + var len = this.masksProperties.length; + for (i = 0; i < len; i += 1) { + if (this.viewData[i].prop._mdf || isFirstFrame) { + this.drawPath(this.masksProperties[i], this.viewData[i].prop.v, this.viewData[i]); + } + if (this.viewData[i].op._mdf || isFirstFrame) { + this.viewData[i].elem.setAttribute('fill-opacity', this.viewData[i].op.v); + } + if (this.masksProperties[i].mode !== 'n') { + if (this.viewData[i].invRect && (this.element.finalTransform.mProp._mdf || isFirstFrame)) { + this.viewData[i].invRect.setAttribute('transform', finalMat.getInverseMatrix().to2dCSS()); + } + if (this.storedData[i].x && (this.storedData[i].x._mdf || isFirstFrame)) { + var feMorph = this.storedData[i].expan; + if (this.storedData[i].x.v < 0) { + if (this.storedData[i].lastOperator !== 'erode') { + this.storedData[i].lastOperator = 'erode'; + this.storedData[i].elem.setAttribute('filter', 'url(' + getLocationHref() + '#' + this.storedData[i].filterId + ')'); + } + feMorph.setAttribute('radius', -this.storedData[i].x.v); + } else { + if (this.storedData[i].lastOperator !== 'dilate') { + this.storedData[i].lastOperator = 'dilate'; + this.storedData[i].elem.setAttribute('filter', null); } - i += 1; + this.storedData[i].elem.setAttribute('stroke-width', this.storedData[i].x.v * 2); } - return true; } - - function clone(matr) { - var i; - for (i = 0; i < 16; i += 1) { - matr.props[i] = this.props[i]; - } - return matr; + } + } +}; + +MaskElement.prototype.getMaskelement = function () { + return this.maskElement; +}; + +MaskElement.prototype.createLayerSolidPath = function () { + var path = 'M0,0 '; + path += ' h' + this.globalData.compSize.w; + path += ' v' + this.globalData.compSize.h; + path += ' h-' + this.globalData.compSize.w; + path += ' v-' + this.globalData.compSize.h + ' '; + return path; +}; + +MaskElement.prototype.drawPath = function (pathData, pathNodes, viewData) { + var pathString = ' M' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; + var i; + var len; + len = pathNodes._length; + for (i = 1; i < len; i += 1) { + // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[i][0]+','+pathNodes.i[i][1] + " "+pathNodes.v[i][0]+','+pathNodes.v[i][1]; + pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[i][0] + ',' + pathNodes.i[i][1] + ' ' + pathNodes.v[i][0] + ',' + pathNodes.v[i][1]; + } + // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[0][0]+','+pathNodes.i[0][1] + " "+pathNodes.v[0][0]+','+pathNodes.v[0][1]; + if (pathNodes.c && len > 1) { + pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[0][0] + ',' + pathNodes.i[0][1] + ' ' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; + } + // pathNodes.__renderedString = pathString; + + if (viewData.lastPath !== pathString) { + var pathShapeValue = ''; + if (viewData.elem) { + if (pathNodes.c) { + pathShapeValue = pathData.inv ? this.solidPath + pathString : pathString; + } + viewData.elem.setAttribute('d', pathShapeValue); + } + viewData.lastPath = pathString; + } +}; + +MaskElement.prototype.destroy = function () { + this.element = null; + this.globalData = null; + this.maskElement = null; + this.data = null; + this.masksProperties = null; +}; + +const filtersFactory = (function () { + var ob = {}; + ob.createFilter = createFilter; + ob.createAlphaToLuminanceFilter = createAlphaToLuminanceFilter; + + function createFilter(filId, skipCoordinates) { + var fil = createNS('filter'); + fil.setAttribute('id', filId); + if (skipCoordinates !== true) { + fil.setAttribute('filterUnits', 'objectBoundingBox'); + fil.setAttribute('x', '0%'); + fil.setAttribute('y', '0%'); + fil.setAttribute('width', '100%'); + fil.setAttribute('height', '100%'); + } + return fil; + } + + function createAlphaToLuminanceFilter() { + var feColorMatrix = createNS('feColorMatrix'); + feColorMatrix.setAttribute('type', 'matrix'); + feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); + feColorMatrix.setAttribute('values', '0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1'); + return feColorMatrix; + } + + return ob; +}()); + +const featureSupport = (function () { + var ob = { + maskType: true, + }; + if (/MSIE 10/i.test(navigator.userAgent) || /MSIE 9/i.test(navigator.userAgent) || /rv:11.0/i.test(navigator.userAgent) || /Edge\/\d./i.test(navigator.userAgent)) { + ob.maskType = false; + } + return ob; +}()); + +var registeredEffects = {}; +var idPrefix = 'filter_result_'; + +function SVGEffects(elem) { + var i; + var source = 'SourceGraphic'; + var len = elem.data.ef ? elem.data.ef.length : 0; + var filId = createElementID(); + var fil = filtersFactory.createFilter(filId, true); + var count = 0; + this.filters = []; + var filterManager; + for (i = 0; i < len; i += 1) { + filterManager = null; + var type = elem.data.ef[i].ty; + if (registeredEffects[type]) { + var Effect = registeredEffects[type].effect; + filterManager = new Effect(fil, elem.effectsManager.effectElements[i], elem, idPrefix + count, source); + source = idPrefix + count; + if (registeredEffects[type].countsAsEffect) { + count += 1; } + } + if (filterManager) { + this.filters.push(filterManager); + } + } + if (count) { + elem.globalData.defs.appendChild(fil); + elem.layerElement.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); + } + if (this.filters.length) { + elem.addRenderableComponent(this); + } +} + +SVGEffects.prototype.renderFrame = function (_isFirstFrame) { + var i; + var len = this.filters.length; + for (i = 0; i < len; i += 1) { + this.filters[i].renderFrame(_isFirstFrame); + } +}; + +function registerEffect(id, effect, countsAsEffect) { + registeredEffects[id] = { + effect, + countsAsEffect, + }; +} + +function SVGBaseElement() { +} + +SVGBaseElement.prototype = { + initRendererElement: function () { + this.layerElement = createNS('g'); + }, + createContainerElements: function () { + this.matteElement = createNS('g'); + this.transformedElement = this.layerElement; + this.maskedElement = this.layerElement; + this._sizeChanged = false; + var layerElementParent = null; + // If this layer acts as a mask for the following layer + var filId; + var fil; + var gg; + if (this.data.td) { + if (this.data.td == 3 || this.data.td == 1) { // eslint-disable-line eqeqeq + var masker = createNS('mask'); + masker.setAttribute('id', this.layerId); + masker.setAttribute('mask-type', this.data.td == 3 ? 'luminance' : 'alpha'); // eslint-disable-line eqeqeq + masker.appendChild(this.layerElement); + layerElementParent = masker; + this.globalData.defs.appendChild(masker); + // This is only for IE and Edge when mask if of type alpha + if (!featureSupport.maskType && this.data.td == 1) { // eslint-disable-line eqeqeq + masker.setAttribute('mask-type', 'luminance'); + filId = createElementID(); + fil = filtersFactory.createFilter(filId); + this.globalData.defs.appendChild(fil); + fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); + gg = createNS('g'); + gg.appendChild(this.layerElement); + layerElementParent = gg; + masker.appendChild(gg); + gg.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); + } + } else if (this.data.td == 2) { // eslint-disable-line eqeqeq + var maskGroup = createNS('mask'); + maskGroup.setAttribute('id', this.layerId); + maskGroup.setAttribute('mask-type', 'alpha'); + var maskGrouper = createNS('g'); + maskGroup.appendChild(maskGrouper); + filId = createElementID(); + fil = filtersFactory.createFilter(filId); + /// / - function cloneFromProps(props) { - var i; - for (i = 0; i < 16; i += 1) { - this.props[i] = props[i]; + // This solution doesn't work on Android when meta tag with viewport attribute is set + /* var feColorMatrix = createNS('feColorMatrix'); + feColorMatrix.setAttribute('type', 'matrix'); + feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); + feColorMatrix.setAttribute('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 -1 1'); + fil.appendChild(feColorMatrix); */ + /// / + var feCTr = createNS('feComponentTransfer'); + feCTr.setAttribute('in', 'SourceGraphic'); + fil.appendChild(feCTr); + var feFunc = createNS('feFuncA'); + feFunc.setAttribute('type', 'table'); + feFunc.setAttribute('tableValues', '1.0 0.0'); + feCTr.appendChild(feFunc); + /// / + this.globalData.defs.appendChild(fil); + var alphaRect = createNS('rect'); + alphaRect.setAttribute('width', this.comp.data.w); + alphaRect.setAttribute('height', this.comp.data.h); + alphaRect.setAttribute('x', '0'); + alphaRect.setAttribute('y', '0'); + alphaRect.setAttribute('fill', '#ffffff'); + alphaRect.setAttribute('opacity', '0'); + maskGrouper.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); + maskGrouper.appendChild(alphaRect); + maskGrouper.appendChild(this.layerElement); + layerElementParent = maskGrouper; + if (!featureSupport.maskType) { + maskGroup.setAttribute('mask-type', 'luminance'); + fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); + gg = createNS('g'); + maskGrouper.appendChild(alphaRect); + gg.appendChild(this.layerElement); + layerElementParent = gg; + maskGrouper.appendChild(gg); + } + this.globalData.defs.appendChild(maskGroup); + } + } else if (this.data.tt) { + this.matteElement.appendChild(this.layerElement); + layerElementParent = this.matteElement; + this.baseElement = this.matteElement; + } else { + this.baseElement = this.layerElement; + } + if (this.data.ln) { + this.layerElement.setAttribute('id', this.data.ln); + } + if (this.data.cl) { + this.layerElement.setAttribute('class', this.data.cl); + } + // Clipping compositions to hide content that exceeds boundaries. If collapsed transformations is on, component should not be clipped + if (this.data.ty === 0 && !this.data.hd) { + var cp = createNS('clipPath'); + var pt = createNS('path'); + pt.setAttribute('d', 'M0,0 L' + this.data.w + ',0 L' + this.data.w + ',' + this.data.h + ' L0,' + this.data.h + 'z'); + var clipId = createElementID(); + cp.setAttribute('id', clipId); + cp.appendChild(pt); + this.globalData.defs.appendChild(cp); + + if (this.checkMasks()) { + var cpGroup = createNS('g'); + cpGroup.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); + cpGroup.appendChild(this.layerElement); + this.transformedElement = cpGroup; + if (layerElementParent) { + layerElementParent.appendChild(this.transformedElement); + } else { + this.baseElement = this.transformedElement; } + } else { + this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); } + } + if (this.data.bm !== 0) { + this.setBlendMode(); + } + }, + renderElement: function () { + if (this.finalTransform._matMdf) { + this.transformedElement.setAttribute('transform', this.finalTransform.mat.to2dCSS()); + } + if (this.finalTransform._opMdf) { + this.transformedElement.setAttribute('opacity', this.finalTransform.mProp.o.v); + } + }, + destroyBaseElement: function () { + this.layerElement = null; + this.matteElement = null; + this.maskManager.destroy(); + }, + getBaseElement: function () { + if (this.data.hd) { + return null; + } + return this.baseElement; + }, + createRenderableComponents: function () { + this.maskManager = new MaskElement(this.data, this, this.globalData); + this.renderableEffectsManager = new SVGEffects(this); + }, + setMatte: function (id) { + if (!this.matteElement) { + return; + } + this.matteElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + id + ')'); + }, +}; + +/** + * @file + * Handles AE's layer parenting property. + * + */ + +function HierarchyElement() {} + +HierarchyElement.prototype = { + /** + * @function + * Initializes hierarchy properties + * + */ + initHierarchy: function () { + // element's parent list + this.hierarchy = []; + // if element is parent of another layer _isParent will be true + this._isParent = false; + this.checkParenting(); + }, + /** + * @function + * Sets layer's hierarchy. + * @param {array} hierarch + * layer's parent list + * + */ + setHierarchy: function (hierarchy) { + this.hierarchy = hierarchy; + }, + /** + * @function + * Sets layer as parent. + * + */ + setAsParent: function () { + this._isParent = true; + }, + /** + * @function + * Searches layer's parenting chain + * + */ + checkParenting: function () { + if (this.data.parent !== undefined) { + this.comp.buildElementParenting(this, this.data.parent, []); + } + }, +}; - function applyToPoint(x, y, z) { - return { - x: x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], - y: x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], - z: x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], - }; - /* return { - x: x * me.a + y * me.c + me.e, - y: x * me.b + y * me.d + me.f - }; */ +function RenderableDOMElement() {} + +(function () { + var _prototype = { + initElement: function (data, globalData, comp) { + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.initTransform(data, globalData, comp); + this.initHierarchy(); + this.initRenderable(); + this.initRendererElement(); + this.createContainerElements(); + this.createRenderableComponents(); + this.createContent(); + this.hide(); + }, + hide: function () { + // console.log('HIDE', this); + if (!this.hidden && (!this.isInRange || this.isTransparent)) { + var elem = this.baseElement || this.layerElement; + elem.style.display = 'none'; + this.hidden = true; } - function applyToX(x, y, z) { - return x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12]; + }, + show: function () { + // console.log('SHOW', this); + if (this.isInRange && !this.isTransparent) { + if (!this.data.hd) { + var elem = this.baseElement || this.layerElement; + elem.style.display = 'block'; + } + this.hidden = false; + this._isFirstFrame = true; } - function applyToY(x, y, z) { - return x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13]; + }, + renderFrame: function () { + // If it is exported as hidden (data.hd === true) no need to render + // If it is not visible no need to render + if (this.data.hd || this.hidden) { + return; } - function applyToZ(x, y, z) { - return x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14]; + this.renderTransform(); + this.renderRenderable(); + this.renderElement(); + this.renderInnerContent(); + if (this._isFirstFrame) { + this._isFirstFrame = false; } - - function getInverseMatrix() { - var determinant = this.props[0] * this.props[5] - this.props[1] * this.props[4]; - var a = this.props[5] / determinant; - var b = -this.props[1] / determinant; - var c = -this.props[4] / determinant; - var d = this.props[0] / determinant; - var e = (this.props[4] * this.props[13] - this.props[5] * this.props[12]) / determinant; - var f = -(this.props[0] * this.props[13] - this.props[1] * this.props[12]) / determinant; - var inverseMatrix = new Matrix(); - inverseMatrix.props[0] = a; - inverseMatrix.props[1] = b; - inverseMatrix.props[4] = c; - inverseMatrix.props[5] = d; - inverseMatrix.props[12] = e; - inverseMatrix.props[13] = f; - return inverseMatrix; + }, + renderInnerContent: function () {}, + prepareFrame: function (num) { + this._mdf = false; + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + this.checkTransparency(); + }, + destroy: function () { + this.innerElem = null; + this.destroyBaseElement(); + }, + }; + extendPrototype([RenderableElement, createProxyFunction(_prototype)], RenderableDOMElement); +}()); + +function IImageElement(data, globalData, comp) { + this.assetData = globalData.getAssetData(data.refId); + this.initElement(data, globalData, comp); + this.sourceRect = { + top: 0, left: 0, width: this.assetData.w, height: this.assetData.h, + }; +} + +extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement], IImageElement); + +IImageElement.prototype.createContent = function () { + var assetPath = this.globalData.getAssetsPath(this.assetData); + + this.innerElem = createNS('image'); + this.innerElem.setAttribute('width', this.assetData.w + 'px'); + this.innerElem.setAttribute('height', this.assetData.h + 'px'); + this.innerElem.setAttribute('preserveAspectRatio', this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio); + this.innerElem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', assetPath); + + this.layerElement.appendChild(this.innerElem); +}; + +IImageElement.prototype.sourceRectAtTime = function () { + return this.sourceRect; +}; + +function ProcessedElement(element, position) { + this.elem = element; + this.pos = position; +} + +function IShapeElement() { +} + +IShapeElement.prototype = { + addShapeToModifiers: function (data) { + var i; + var len = this.shapeModifiers.length; + for (i = 0; i < len; i += 1) { + this.shapeModifiers[i].addShape(data); + } + }, + isShapeInAnimatedModifiers: function (data) { + var i = 0; + var len = this.shapeModifiers.length; + while (i < len) { + if (this.shapeModifiers[i].isAnimatedWithShape(data)) { + return true; } + } + return false; + }, + renderModifiers: function () { + if (!this.shapeModifiers.length) { + return; + } + var i; + var len = this.shapes.length; + for (i = 0; i < len; i += 1) { + this.shapes[i].sh.reset(); + } - function inversePoint(pt) { - var inverseMatrix = this.getInverseMatrix(); - return inverseMatrix.applyToPointArray(pt[0], pt[1], pt[2] || 0); + len = this.shapeModifiers.length; + var shouldBreakProcess; + for (i = len - 1; i >= 0; i -= 1) { + shouldBreakProcess = this.shapeModifiers[i].processShapes(this._isFirstFrame); + // workaround to fix cases where a repeater resets the shape so the following processes get called twice + // TODO: find a better solution for this + if (shouldBreakProcess) { + break; } + } + }, - function inversePoints(pts) { - var i; - var len = pts.length; - var retPts = []; - for (i = 0; i < len; i += 1) { - retPts[i] = inversePoint(pts[i]); - } - return retPts; + searchProcessedElement: function (elem) { + var elements = this.processedElements; + var i = 0; + var len = elements.length; + while (i < len) { + if (elements[i].elem === elem) { + return elements[i].pos; } - - function applyToTriplePoints(pt1, pt2, pt3) { - var arr = createTypedArray('float32', 6); - if (this.isIdentity()) { - arr[0] = pt1[0]; - arr[1] = pt1[1]; - arr[2] = pt2[0]; - arr[3] = pt2[1]; - arr[4] = pt3[0]; - arr[5] = pt3[1]; - } else { - var p0 = this.props[0]; - var p1 = this.props[1]; - var p4 = this.props[4]; - var p5 = this.props[5]; - var p12 = this.props[12]; - var p13 = this.props[13]; - arr[0] = pt1[0] * p0 + pt1[1] * p4 + p12; - arr[1] = pt1[0] * p1 + pt1[1] * p5 + p13; - arr[2] = pt2[0] * p0 + pt2[1] * p4 + p12; - arr[3] = pt2[0] * p1 + pt2[1] * p5 + p13; - arr[4] = pt3[0] * p0 + pt3[1] * p4 + p12; - arr[5] = pt3[0] * p1 + pt3[1] * p5 + p13; - } - return arr; + i += 1; + } + return 0; + }, + addProcessedElement: function (elem, pos) { + var elements = this.processedElements; + var i = elements.length; + while (i) { + i -= 1; + if (elements[i].elem === elem) { + elements[i].pos = pos; + return; } - - function applyToPointArray(x, y, z) { - var arr; - if (this.isIdentity()) { - arr = [x, y, z]; + } + elements.push(new ProcessedElement(elem, pos)); + }, + prepareFrame: function (num) { + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + }, +}; + +const lineCapEnum = { + 1: 'butt', + 2: 'round', + 3: 'square', +}; + +const lineJoinEnum = { + 1: 'miter', + 2: 'round', + 3: 'bevel', +}; + +function SVGShapeData(transformers, level, shape) { + this.caches = []; + this.styles = []; + this.transformers = transformers; + this.lStr = ''; + this.sh = shape; + this.lvl = level; + // TODO find if there are some cases where _isAnimated can be false. + // For now, since shapes add up with other shapes. They have to be calculated every time. + // One way of finding out is checking if all styles associated to this shape depend only of this shape + this._isAnimated = !!shape.k; + // TODO: commenting this for now since all shapes are animated + var i = 0; + var len = transformers.length; + while (i < len) { + if (transformers[i].mProps.dynamicProperties.length) { + this._isAnimated = true; + break; + } + i += 1; + } +} + +SVGShapeData.prototype.setAsAnimated = function () { + this._isAnimated = true; +}; + +function SVGStyleData(data, level) { + this.data = data; + this.type = data.ty; + this.d = ''; + this.lvl = level; + this._mdf = false; + this.closed = data.hd === true; + this.pElem = createNS('path'); + this.msElem = null; +} + +SVGStyleData.prototype.reset = function () { + this.d = ''; + this._mdf = false; +}; + +function DashProperty(elem, data, renderer, container) { + this.elem = elem; + this.frameId = -1; + this.dataProps = createSizedArray(data.length); + this.renderer = renderer; + this.k = false; + this.dashStr = ''; + this.dashArray = createTypedArray('float32', data.length ? data.length - 1 : 0); + this.dashoffset = createTypedArray('float32', 1); + this.initDynamicPropertyContainer(container); + var i; + var len = data.length || 0; + var prop; + for (i = 0; i < len; i += 1) { + prop = PropertyFactory.getProp(elem, data[i].v, 0, 0, this); + this.k = prop.k || this.k; + this.dataProps[i] = { n: data[i].n, p: prop }; + } + if (!this.k) { + this.getValue(true); + } + this._isAnimated = this.k; +} + +DashProperty.prototype.getValue = function (forceRender) { + if (this.elem.globalData.frameId === this.frameId && !forceRender) { + return; + } + this.frameId = this.elem.globalData.frameId; + this.iterateDynamicProperties(); + this._mdf = this._mdf || forceRender; + if (this._mdf) { + var i = 0; + var len = this.dataProps.length; + if (this.renderer === 'svg') { + this.dashStr = ''; + } + for (i = 0; i < len; i += 1) { + if (this.dataProps[i].n !== 'o') { + if (this.renderer === 'svg') { + this.dashStr += ' ' + this.dataProps[i].p.v; } else { - arr = [ - x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12], - x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13], - x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14], - ]; + this.dashArray[i] = this.dataProps[i].p.v; } - return arr; + } else { + this.dashoffset[0] = this.dataProps[i].p.v; } - - function applyToPointStringified(x, y) { - if (this.isIdentity()) { - return x + ',' + y; - } - var _p = this.props; - return Math.round((x * _p[0] + y * _p[4] + _p[12]) * 100) / 100 + ',' + Math.round((x * _p[1] + y * _p[5] + _p[13]) * 100) / 100; + } + } +}; +extendPrototype([DynamicPropertyContainer], DashProperty); + +function SVGStrokeStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); + this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); + this.d = new DashProperty(elem, data.d || {}, 'svg', this); + this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); + this.style = styleOb; + this._isAnimated = !!this._isAnimated; +} + +extendPrototype([DynamicPropertyContainer], SVGStrokeStyleData); + +function SVGFillStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); + this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); + this.style = styleOb; +} + +extendPrototype([DynamicPropertyContainer], SVGFillStyleData); + +function SVGNoStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.style = styleOb; +} + +extendPrototype([DynamicPropertyContainer], SVGNoStyleData); + +function GradientProperty(elem, data, container) { + this.data = data; + this.c = createTypedArray('uint8c', data.p * 4); + var cLength = data.k.k[0].s ? (data.k.k[0].s.length - data.p * 4) : data.k.k.length - data.p * 4; + this.o = createTypedArray('float32', cLength); + this._cmdf = false; + this._omdf = false; + this._collapsable = this.checkCollapsable(); + this._hasOpacity = cLength; + this.initDynamicPropertyContainer(container); + this.prop = PropertyFactory.getProp(elem, data.k, 1, null, this); + this.k = this.prop.k; + this.getValue(true); +} + +GradientProperty.prototype.comparePoints = function (values, points) { + var i = 0; + var len = this.o.length / 2; + var diff; + while (i < len) { + diff = Math.abs(values[i * 4] - values[points * 4 + i * 2]); + if (diff > 0.01) { + return false; + } + i += 1; + } + return true; +}; + +GradientProperty.prototype.checkCollapsable = function () { + if (this.o.length / 2 !== this.c.length / 4) { + return false; + } + if (this.data.k.k[0].s) { + var i = 0; + var len = this.data.k.k.length; + while (i < len) { + if (!this.comparePoints(this.data.k.k[i].s, this.data.p)) { + return false; } - - function toCSS() { - // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. - /* if(this.isIdentity()) { - return ''; - } */ - var i = 0; - var props = this.props; - var cssValue = 'matrix3d('; - var v = 10000; - while (i < 16) { - cssValue += _rnd(props[i] * v) / v; - cssValue += i === 15 ? ')' : ','; - i += 1; - } - return cssValue; + i += 1; + } + } else if (!this.comparePoints(this.data.k.k, this.data.p)) { + return false; + } + return true; +}; + +GradientProperty.prototype.getValue = function (forceRender) { + this.prop.getValue(); + this._mdf = false; + this._cmdf = false; + this._omdf = false; + if (this.prop._mdf || forceRender) { + var i; + var len = this.data.p * 4; + var mult; + var val; + for (i = 0; i < len; i += 1) { + mult = i % 4 === 0 ? 100 : 255; + val = Math.round(this.prop.v[i] * mult); + if (this.c[i] !== val) { + this.c[i] = val; + this._cmdf = !forceRender; } - - function roundMatrixProperty(val) { - var v = 10000; - if ((val < 0.000001 && val > 0) || (val > -0.000001 && val < 0)) { - return _rnd(val * v) / v; + } + if (this.o.length) { + len = this.prop.v.length; + for (i = this.data.p * 4; i < len; i += 1) { + mult = i % 2 === 0 ? 100 : 1; + val = i % 2 === 0 ? Math.round(this.prop.v[i] * 100) : this.prop.v[i]; + if (this.o[i - this.data.p * 4] !== val) { + this.o[i - this.data.p * 4] = val; + this._omdf = !forceRender; } - return val; } - - function to2dCSS() { - // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed. - /* if(this.isIdentity()) { - return ''; - } */ - var props = this.props; - var _a = roundMatrixProperty(props[0]); - var _b = roundMatrixProperty(props[1]); - var _c = roundMatrixProperty(props[4]); - var _d = roundMatrixProperty(props[5]); - var _e = roundMatrixProperty(props[12]); - var _f = roundMatrixProperty(props[13]); - return 'matrix(' + _a + ',' + _b + ',' + _c + ',' + _d + ',' + _e + ',' + _f + ')'; - } - - return function () { - this.reset = reset; - this.rotate = rotate; - this.rotateX = rotateX; - this.rotateY = rotateY; - this.rotateZ = rotateZ; - this.skew = skew; - this.skewFromAxis = skewFromAxis; - this.shear = shear; - this.scale = scale; - this.setTransform = setTransform; - this.translate = translate; - this.transform = transform; - this.applyToPoint = applyToPoint; - this.applyToX = applyToX; - this.applyToY = applyToY; - this.applyToZ = applyToZ; - this.applyToPointArray = applyToPointArray; - this.applyToTriplePoints = applyToTriplePoints; - this.applyToPointStringified = applyToPointStringified; - this.toCSS = toCSS; - this.to2dCSS = to2dCSS; - this.clone = clone; - this.cloneFromProps = cloneFromProps; - this.equals = equals; - this.inversePoints = inversePoints; - this.inversePoint = inversePoint; - this.getInverseMatrix = getInverseMatrix; - this._t = this.transform; - this.isIdentity = isIdentity; - this._identity = true; - this._identityCalculated = false; - - this.props = createTypedArray('float32', 16); - this.reset(); - }; - }()); - - const lottie = {}; - var standalone = '__[STANDALONE]__'; - var animationData = '__[ANIMATIONDATA]__'; - var renderer = ''; - - function setLocation(href) { - setLocationHref(href); } - - function searchAnimations() { - if (standalone === true) { - animationManager.searchAnimations(animationData, standalone, renderer); - } else { - animationManager.searchAnimations(); + this._mdf = !forceRender; + } +}; + +extendPrototype([DynamicPropertyContainer], GradientProperty); + +function SVGGradientFillStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.initGradientData(elem, data, styleOb); +} + +SVGGradientFillStyleData.prototype.initGradientData = function (elem, data, styleOb) { + this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); + this.s = PropertyFactory.getProp(elem, data.s, 1, null, this); + this.e = PropertyFactory.getProp(elem, data.e, 1, null, this); + this.h = PropertyFactory.getProp(elem, data.h || { k: 0 }, 0, 0.01, this); + this.a = PropertyFactory.getProp(elem, data.a || { k: 0 }, 0, degToRads, this); + this.g = new GradientProperty(elem, data.g, this); + this.style = styleOb; + this.stops = []; + this.setGradientData(styleOb.pElem, data); + this.setGradientOpacity(data, styleOb); + this._isAnimated = !!this._isAnimated; +}; + +SVGGradientFillStyleData.prototype.setGradientData = function (pathElement, data) { + var gradientId = createElementID(); + var gfill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); + gfill.setAttribute('id', gradientId); + gfill.setAttribute('spreadMethod', 'pad'); + gfill.setAttribute('gradientUnits', 'userSpaceOnUse'); + var stops = []; + var stop; + var j; + var jLen; + jLen = data.g.p * 4; + for (j = 0; j < jLen; j += 4) { + stop = createNS('stop'); + gfill.appendChild(stop); + stops.push(stop); + } + pathElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + gradientId + ')'); + this.gf = gfill; + this.cst = stops; +}; + +SVGGradientFillStyleData.prototype.setGradientOpacity = function (data, styleOb) { + if (this.g._hasOpacity && !this.g._collapsable) { + var stop; + var j; + var jLen; + var mask = createNS('mask'); + var maskElement = createNS('path'); + mask.appendChild(maskElement); + var opacityId = createElementID(); + var maskId = createElementID(); + mask.setAttribute('id', maskId); + var opFill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); + opFill.setAttribute('id', opacityId); + opFill.setAttribute('spreadMethod', 'pad'); + opFill.setAttribute('gradientUnits', 'userSpaceOnUse'); + jLen = data.g.k.k[0].s ? data.g.k.k[0].s.length : data.g.k.k.length; + var stops = this.stops; + for (j = data.g.p * 4; j < jLen; j += 2) { + stop = createNS('stop'); + stop.setAttribute('stop-color', 'rgb(255,255,255)'); + opFill.appendChild(stop); + stops.push(stop); + } + maskElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + opacityId + ')'); + if (data.ty === 'gs') { + maskElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); + maskElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); + if (data.lj === 1) { + maskElement.setAttribute('stroke-miterlimit', data.ml); } } - - function setSubframeRendering(flag) { - setSubframeEnabled(flag); + this.of = opFill; + this.ms = mask; + this.ost = stops; + this.maskId = maskId; + styleOb.msElem = maskElement; + } +}; + +extendPrototype([DynamicPropertyContainer], SVGGradientFillStyleData); + +function SVGGradientStrokeStyleData(elem, data, styleOb) { + this.initDynamicPropertyContainer(elem); + this.getValue = this.iterateDynamicProperties; + this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); + this.d = new DashProperty(elem, data.d || {}, 'svg', this); + this.initGradientData(elem, data, styleOb); + this._isAnimated = !!this._isAnimated; +} + +extendPrototype([SVGGradientFillStyleData, DynamicPropertyContainer], SVGGradientStrokeStyleData); + +function ShapeGroupData() { + this.it = []; + this.prevViewData = []; + this.gr = createNS('g'); +} + +function SVGTransformData(mProps, op, container) { + this.transform = { + mProps: mProps, + op: op, + container: container, + }; + this.elements = []; + this._isAnimated = this.transform.mProps.dynamicProperties.length || this.transform.op.effectsSequence.length; +} + +const buildShapeString = function (pathNodes, length, closed, mat) { + if (length === 0) { + return ''; + } + var _o = pathNodes.o; + var _i = pathNodes.i; + var _v = pathNodes.v; + var i; + var shapeString = ' M' + mat.applyToPointStringified(_v[0][0], _v[0][1]); + for (i = 1; i < length; i += 1) { + shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[i][0], _i[i][1]) + ' ' + mat.applyToPointStringified(_v[i][0], _v[i][1]); + } + if (closed && length) { + shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[0][0], _i[0][1]) + ' ' + mat.applyToPointStringified(_v[0][0], _v[0][1]); + shapeString += 'z'; + } + return shapeString; +}; + +const SVGElementsRenderer = (function () { + var _identityMatrix = new Matrix(); + var _matrixHelper = new Matrix(); + + var ob = { + createRenderFunction: createRenderFunction, + }; + + function createRenderFunction(data) { + switch (data.ty) { + case 'fl': + return renderFill; + case 'gf': + return renderGradient; + case 'gs': + return renderGradientStroke; + case 'st': + return renderStroke; + case 'sh': + case 'el': + case 'rc': + case 'sr': + return renderPath; + case 'tr': + return renderContentTransform; + case 'no': + return renderNoop; + default: + return null; } + } - function setPrefix(prefix) { - setIdPrefix(prefix); + function renderContentTransform(styleData, itemData, isFirstFrame) { + if (isFirstFrame || itemData.transform.op._mdf) { + itemData.transform.container.setAttribute('opacity', itemData.transform.op.v); } - - function loadAnimation(params) { - if (standalone === true) { - params.animationData = JSON.parse(animationData); - } - return animationManager.loadAnimation(params); + if (isFirstFrame || itemData.transform.mProps._mdf) { + itemData.transform.container.setAttribute('transform', itemData.transform.mProps.v.to2dCSS()); } - - function setQuality(value) { - if (typeof value === 'string') { - switch (value) { - case 'high': - setDefaultCurveSegments(200); - break; - default: - case 'medium': - setDefaultCurveSegments(50); - break; - case 'low': - setDefaultCurveSegments(10); - break; + } + + function renderNoop() { + + } + + function renderPath(styleData, itemData, isFirstFrame) { + var j; + var jLen; + var pathStringTransformed; + var redraw; + var pathNodes; + var l; + var lLen = itemData.styles.length; + var lvl = itemData.lvl; + var paths; + var mat; + var props; + var iterations; + var k; + for (l = 0; l < lLen; l += 1) { + redraw = itemData.sh._mdf || isFirstFrame; + if (itemData.styles[l].lvl < lvl) { + mat = _matrixHelper.reset(); + iterations = lvl - itemData.styles[l].lvl; + k = itemData.transformers.length - 1; + while (!redraw && iterations > 0) { + redraw = itemData.transformers[k].mProps._mdf || redraw; + iterations -= 1; + k -= 1; + } + if (redraw) { + iterations = lvl - itemData.styles[l].lvl; + k = itemData.transformers.length - 1; + while (iterations > 0) { + props = itemData.transformers[k].mProps.v.props; + mat.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); + iterations -= 1; + k -= 1; + } } - } else if (!isNaN(value) && value > 1) { - setDefaultCurveSegments(value); + } else { + mat = _identityMatrix; } - if (getDefaultCurveSegments() >= 50) { - roundValues(false); + paths = itemData.sh.paths; + jLen = paths._length; + if (redraw) { + pathStringTransformed = ''; + for (j = 0; j < jLen; j += 1) { + pathNodes = paths.shapes[j]; + if (pathNodes && pathNodes._length) { + pathStringTransformed += buildShapeString(pathNodes, pathNodes._length, pathNodes.c, mat); + } + } + itemData.caches[l] = pathStringTransformed; } else { - roundValues(true); + pathStringTransformed = itemData.caches[l]; } + itemData.styles[l].d += styleData.hd === true ? '' : pathStringTransformed; + itemData.styles[l]._mdf = redraw || itemData.styles[l]._mdf; } + } - function inBrowser() { - return typeof navigator !== 'undefined'; - } + function renderFill(styleData, itemData, isFirstFrame) { + var styleElem = itemData.style; - function installPlugin(type, plugin) { - if (type === 'expressions') { - setExpressionsPlugin(plugin); + if (itemData.c._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('fill', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); + } + if (itemData.o._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('fill-opacity', itemData.o.v); + } + } + + function renderGradientStroke(styleData, itemData, isFirstFrame) { + renderGradient(styleData, itemData, isFirstFrame); + renderStroke(styleData, itemData, isFirstFrame); + } + + function renderGradient(styleData, itemData, isFirstFrame) { + var gfill = itemData.gf; + var hasOpacity = itemData.g._hasOpacity; + var pt1 = itemData.s.v; + var pt2 = itemData.e.v; + + if (itemData.o._mdf || isFirstFrame) { + var attr = styleData.ty === 'gf' ? 'fill-opacity' : 'stroke-opacity'; + itemData.style.pElem.setAttribute(attr, itemData.o.v); + } + if (itemData.s._mdf || isFirstFrame) { + var attr1 = styleData.t === 1 ? 'x1' : 'cx'; + var attr2 = attr1 === 'x1' ? 'y1' : 'cy'; + gfill.setAttribute(attr1, pt1[0]); + gfill.setAttribute(attr2, pt1[1]); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute(attr1, pt1[0]); + itemData.of.setAttribute(attr2, pt1[1]); } } - - function getFactory(name) { - switch (name) { - case 'propertyFactory': - return PropertyFactory; - case 'shapePropertyFactory': - return ShapePropertyFactory; - case 'matrix': - return Matrix; - default: - return null; + var stops; + var i; + var len; + var stop; + if (itemData.g._cmdf || isFirstFrame) { + stops = itemData.cst; + var cValues = itemData.g.c; + len = stops.length; + for (i = 0; i < len; i += 1) { + stop = stops[i]; + stop.setAttribute('offset', cValues[i * 4] + '%'); + stop.setAttribute('stop-color', 'rgb(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ')'); } } - - lottie.play = animationManager.play; - lottie.pause = animationManager.pause; - lottie.setLocationHref = setLocation; - lottie.togglePause = animationManager.togglePause; - lottie.setSpeed = animationManager.setSpeed; - lottie.setDirection = animationManager.setDirection; - lottie.stop = animationManager.stop; - lottie.searchAnimations = searchAnimations; - lottie.registerAnimation = animationManager.registerAnimation; - lottie.loadAnimation = loadAnimation; - lottie.setSubframeRendering = setSubframeRendering; - lottie.resize = animationManager.resize; - // lottie.start = start; - lottie.goToAndStop = animationManager.goToAndStop; - lottie.destroy = animationManager.destroy; - lottie.setQuality = setQuality; - lottie.inBrowser = inBrowser; - lottie.installPlugin = installPlugin; - lottie.freeze = animationManager.freeze; - lottie.unfreeze = animationManager.unfreeze; - lottie.setVolume = animationManager.setVolume; - lottie.mute = animationManager.mute; - lottie.unmute = animationManager.unmute; - lottie.getRegisteredAnimations = animationManager.getRegisteredAnimations; - lottie.useWebWorker = setWebWorker; - lottie.setIDPrefix = setPrefix; - lottie.__getFactory = getFactory; - lottie.version = '[[BM_VERSION]]'; - - function checkReady() { - if (document.readyState === 'complete') { - clearInterval(readyStateCheckInterval); - searchAnimations(); - } - } - - function getQueryVariable(variable) { - var vars = queryString.split('&'); - for (var i = 0; i < vars.length; i += 1) { - var pair = vars[i].split('='); - if (decodeURIComponent(pair[0]) == variable) { // eslint-disable-line eqeqeq - return decodeURIComponent(pair[1]); + if (hasOpacity && (itemData.g._omdf || isFirstFrame)) { + var oValues = itemData.g.o; + if (itemData.g._collapsable) { + stops = itemData.cst; + } else { + stops = itemData.ost; + } + len = stops.length; + for (i = 0; i < len; i += 1) { + stop = stops[i]; + if (!itemData.g._collapsable) { + stop.setAttribute('offset', oValues[i * 2] + '%'); } + stop.setAttribute('stop-opacity', oValues[i * 2 + 1]); } - return null; } - var queryString = ''; - if (standalone) { - var scripts = document.getElementsByTagName('script'); - var index = scripts.length - 1; - var myScript = scripts[index] || { - src: '', - }; - queryString = myScript.src ? myScript.src.replace(/^[^\?]+\??/, '') : ''; // eslint-disable-line no-useless-escape - renderer = getQueryVariable('renderer'); + if (styleData.t === 1) { + if (itemData.e._mdf || isFirstFrame) { + gfill.setAttribute('x2', pt2[0]); + gfill.setAttribute('y2', pt2[1]); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute('x2', pt2[0]); + itemData.of.setAttribute('y2', pt2[1]); + } + } + } else { + var rad; + if (itemData.s._mdf || itemData.e._mdf || isFirstFrame) { + rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); + gfill.setAttribute('r', rad); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute('r', rad); + } + } + if (itemData.e._mdf || itemData.h._mdf || itemData.a._mdf || isFirstFrame) { + if (!rad) { + rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); + } + var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); + + var percent = itemData.h.v; + if (percent >= 1) { + percent = 0.99; + } else if (percent <= -1) { + percent = -0.99; + } + var dist = rad * percent; + var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; + var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; + gfill.setAttribute('fx', x); + gfill.setAttribute('fy', y); + if (hasOpacity && !itemData.g._collapsable) { + itemData.of.setAttribute('fx', x); + itemData.of.setAttribute('fy', y); + } + } + // gfill.setAttribute('fy','200'); } - var readyStateCheckInterval = setInterval(checkReady, 100); - - // this adds bodymovin to the window object for backwards compatibility - try { - if (!(typeof exports === 'object' && typeof module !== 'undefined') - && !(typeof define === 'function' && define.amd) // eslint-disable-line no-undef - ) { - window.bodymovin = lottie; + } + + function renderStroke(styleData, itemData, isFirstFrame) { + var styleElem = itemData.style; + var d = itemData.d; + if (d && (d._mdf || isFirstFrame) && d.dashStr) { + styleElem.pElem.setAttribute('stroke-dasharray', d.dashStr); + styleElem.pElem.setAttribute('stroke-dashoffset', d.dashoffset[0]); + } + if (itemData.c && (itemData.c._mdf || isFirstFrame)) { + styleElem.pElem.setAttribute('stroke', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); + } + if (itemData.o._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('stroke-opacity', itemData.o.v); + } + if (itemData.w._mdf || isFirstFrame) { + styleElem.pElem.setAttribute('stroke-width', itemData.w.v); + if (styleElem.msElem) { + styleElem.msElem.setAttribute('stroke-width', itemData.w.v); } - } catch (err) { - // } - - const ShapeModifiers = (function () { - var ob = {}; - var modifiers = {}; - ob.registerModifier = registerModifier; - ob.getModifier = getModifier; - - function registerModifier(nm, factory) { - if (!modifiers[nm]) { - modifiers[nm] = factory; - } + } + + return ob; +}()); + +function SVGShapeElement(data, globalData, comp) { + // List of drawable elements + this.shapes = []; + // Full shape data + this.shapesData = data.shapes; + // List of styles that will be applied to shapes + this.stylesList = []; + // List of modifiers that will be applied to shapes + this.shapeModifiers = []; + // List of items in shape tree + this.itemsData = []; + // List of items in previous shape tree + this.processedElements = []; + // List of animated components + this.animatedContents = []; + this.initElement(data, globalData, comp); + // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. + // List of elements that have been created + this.prevViewData = []; + // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. +} + +extendPrototype([BaseElement, TransformElement, SVGBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableDOMElement], SVGShapeElement); + +SVGShapeElement.prototype.initSecondaryElement = function () { +}; + +SVGShapeElement.prototype.identityMatrix = new Matrix(); + +SVGShapeElement.prototype.buildExpressionInterface = function () {}; + +SVGShapeElement.prototype.createContent = function () { + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); + this.filterUniqueShapes(); +}; + +/* +This method searches for multiple shapes that affect a single element and one of them is animated +*/ +SVGShapeElement.prototype.filterUniqueShapes = function () { + var i; + var len = this.shapes.length; + var shape; + var j; + var jLen = this.stylesList.length; + var style; + var tempShapes = []; + var areAnimated = false; + for (j = 0; j < jLen; j += 1) { + style = this.stylesList[j]; + areAnimated = false; + tempShapes.length = 0; + for (i = 0; i < len; i += 1) { + shape = this.shapes[i]; + if (shape.styles.indexOf(style) !== -1) { + tempShapes.push(shape); + areAnimated = shape._isAnimated || areAnimated; } - - function getModifier(nm, elem, data) { - return new modifiers[nm](elem, data); + } + if (tempShapes.length > 1 && areAnimated) { + this.setShapesAsAnimated(tempShapes); + } + } +}; + +SVGShapeElement.prototype.setShapesAsAnimated = function (shapes) { + var i; + var len = shapes.length; + for (i = 0; i < len; i += 1) { + shapes[i].setAsAnimated(); + } +}; + +SVGShapeElement.prototype.createStyleElement = function (data, level) { + // TODO: prevent drawing of hidden styles + var elementData; + var styleOb = new SVGStyleData(data, level); + + var pathElement = styleOb.pElem; + if (data.ty === 'st') { + elementData = new SVGStrokeStyleData(this, data, styleOb); + } else if (data.ty === 'fl') { + elementData = new SVGFillStyleData(this, data, styleOb); + } else if (data.ty === 'gf' || data.ty === 'gs') { + var GradientConstructor = data.ty === 'gf' ? SVGGradientFillStyleData : SVGGradientStrokeStyleData; + elementData = new GradientConstructor(this, data, styleOb); + this.globalData.defs.appendChild(elementData.gf); + if (elementData.maskId) { + this.globalData.defs.appendChild(elementData.ms); + this.globalData.defs.appendChild(elementData.of); + pathElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + elementData.maskId + ')'); + } + } else if (data.ty === 'no') { + elementData = new SVGNoStyleData(this, data, styleOb); + } + + if (data.ty === 'st' || data.ty === 'gs') { + pathElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); + pathElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); + pathElement.setAttribute('fill-opacity', '0'); + if (data.lj === 1) { + pathElement.setAttribute('stroke-miterlimit', data.ml); + } + } + + if (data.r === 2) { + pathElement.setAttribute('fill-rule', 'evenodd'); + } + + if (data.ln) { + pathElement.setAttribute('id', data.ln); + } + if (data.cl) { + pathElement.setAttribute('class', data.cl); + } + if (data.bm) { + pathElement.style['mix-blend-mode'] = getBlendMode(data.bm); + } + this.stylesList.push(styleOb); + this.addToAnimatedContents(data, elementData); + return elementData; +}; + +SVGShapeElement.prototype.createGroupElement = function (data) { + var elementData = new ShapeGroupData(); + if (data.ln) { + elementData.gr.setAttribute('id', data.ln); + } + if (data.cl) { + elementData.gr.setAttribute('class', data.cl); + } + if (data.bm) { + elementData.gr.style['mix-blend-mode'] = getBlendMode(data.bm); + } + return elementData; +}; + +SVGShapeElement.prototype.createTransformElement = function (data, container) { + var transformProperty = TransformPropertyFactory.getTransformProperty(this, data, this); + var elementData = new SVGTransformData(transformProperty, transformProperty.o, container); + this.addToAnimatedContents(data, elementData); + return elementData; +}; + +SVGShapeElement.prototype.createShapeElement = function (data, ownTransformers, level) { + var ty = 4; + if (data.ty === 'rc') { + ty = 5; + } else if (data.ty === 'el') { + ty = 6; + } else if (data.ty === 'sr') { + ty = 7; + } + var shapeProperty = ShapePropertyFactory.getShapeProp(this, data, ty, this); + var elementData = new SVGShapeData(ownTransformers, level, shapeProperty); + this.shapes.push(elementData); + this.addShapeToModifiers(elementData); + this.addToAnimatedContents(data, elementData); + return elementData; +}; + +SVGShapeElement.prototype.addToAnimatedContents = function (data, element) { + var i = 0; + var len = this.animatedContents.length; + while (i < len) { + if (this.animatedContents[i].element === element) { + return; + } + i += 1; + } + this.animatedContents.push({ + fn: SVGElementsRenderer.createRenderFunction(data), + element: element, + data: data, + }); +}; + +SVGShapeElement.prototype.setElementStyles = function (elementData) { + var arr = elementData.styles; + var j; + var jLen = this.stylesList.length; + for (j = 0; j < jLen; j += 1) { + if (!this.stylesList[j].closed) { + arr.push(this.stylesList[j]); + } + } +}; + +SVGShapeElement.prototype.reloadShapes = function () { + this._isFirstFrame = true; + var i; + var len = this.itemsData.length; + for (i = 0; i < len; i += 1) { + this.prevViewData[i] = this.itemsData[i]; + } + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); + this.filterUniqueShapes(); + len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + this.dynamicProperties[i].getValue(); + } + this.renderModifiers(); +}; + +SVGShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, container, level, transformers, render) { + var ownTransformers = [].concat(transformers); + var i; + var len = arr.length - 1; + var j; + var jLen; + var ownStyles = []; + var ownModifiers = []; + var currentTransform; + var modifier; + var processedPos; + for (i = len; i >= 0; i -= 1) { + processedPos = this.searchProcessedElement(arr[i]); + if (!processedPos) { + arr[i]._render = render; + } else { + itemsData[i] = prevViewData[processedPos - 1]; + } + if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs' || arr[i].ty === 'no') { + if (!processedPos) { + itemsData[i] = this.createStyleElement(arr[i], level); + } else { + itemsData[i].style.closed = false; } - - return ob; - }()); - - function ShapeModifier() {} - ShapeModifier.prototype.initModifierProperties = function () {}; - ShapeModifier.prototype.addShapeToModifier = function () {}; - ShapeModifier.prototype.addShape = function (data) { - if (!this.closed) { - // Adding shape to dynamic properties. It covers the case where a shape has no effects applied, to reset it's _mdf state on every tick. - data.sh.container.addDynamicProperty(data.sh); - var shapeData = { shape: data.sh, data: data, localShapeCollection: shapeCollectionPool.newShapeCollection() }; - this.shapes.push(shapeData); - this.addShapeToModifier(shapeData); - if (this._isAnimated) { - data.setAsAnimated(); + if (arr[i]._render) { + if (itemsData[i].style.pElem.parentNode !== container) { + container.appendChild(itemsData[i].style.pElem); } } - }; - ShapeModifier.prototype.init = function (elem, data) { - this.shapes = []; - this.elem = elem; - this.initDynamicPropertyContainer(elem); - this.initModifierProperties(elem, data); - this.frameId = initialDefaultFrame; - this.closed = false; - this.k = false; - if (this.dynamicProperties.length) { - this.k = true; + ownStyles.push(itemsData[i].style); + } else if (arr[i].ty === 'gr') { + if (!processedPos) { + itemsData[i] = this.createGroupElement(arr[i]); } else { - this.getValue(true); + jLen = itemsData[i].it.length; + for (j = 0; j < jLen; j += 1) { + itemsData[i].prevViewData[j] = itemsData[i].it[j]; + } } - }; - ShapeModifier.prototype.processKeys = function () { - if (this.elem.globalData.frameId === this.frameId) { - return; + this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, itemsData[i].gr, level + 1, ownTransformers, render); + if (arr[i]._render) { + if (itemsData[i].gr.parentNode !== container) { + container.appendChild(itemsData[i].gr); + } } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - }; - - extendPrototype([DynamicPropertyContainer], ShapeModifier); - - function TrimModifier() { - } - extendPrototype([ShapeModifier], TrimModifier); - TrimModifier.prototype.initModifierProperties = function (elem, data) { - this.s = PropertyFactory.getProp(elem, data.s, 0, 0.01, this); - this.e = PropertyFactory.getProp(elem, data.e, 0, 0.01, this); - this.o = PropertyFactory.getProp(elem, data.o, 0, 0, this); - this.sValue = 0; - this.eValue = 0; - this.getValue = this.processKeys; - this.m = data.m; - this._isAnimated = !!this.s.effectsSequence.length || !!this.e.effectsSequence.length || !!this.o.effectsSequence.length; - }; - - TrimModifier.prototype.addShapeToModifier = function (shapeData) { - shapeData.pathsData = []; - }; - - TrimModifier.prototype.calculateShapeEdges = function (s, e, shapeLength, addedLength, totalModifierLength) { - var segments = []; - if (e <= 1) { - segments.push({ - s: s, - e: e, - }); - } else if (s >= 1) { - segments.push({ - s: s - 1, - e: e - 1, - }); + } else if (arr[i].ty === 'tr') { + if (!processedPos) { + itemsData[i] = this.createTransformElement(arr[i], container); + } + currentTransform = itemsData[i].transform; + ownTransformers.push(currentTransform); + } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { + if (!processedPos) { + itemsData[i] = this.createShapeElement(arr[i], ownTransformers, level); + } + this.setElementStyles(itemsData[i]); + } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'ms' || arr[i].ty === 'pb') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + modifier.init(this, arr[i]); + itemsData[i] = modifier; + this.shapeModifiers.push(modifier); } else { - segments.push({ - s: s, - e: 1, - }); - segments.push({ - s: 0, - e: e - 1, - }); + modifier = itemsData[i]; + modifier.closed = false; + } + ownModifiers.push(modifier); + } else if (arr[i].ty === 'rp') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + itemsData[i] = modifier; + modifier.init(this, arr, i, itemsData); + this.shapeModifiers.push(modifier); + render = false; + } else { + modifier = itemsData[i]; + modifier.closed = true; } - var shapeSegments = []; - var i; - var len = segments.length; - var segmentOb; + ownModifiers.push(modifier); + } + this.addProcessedElement(arr[i], i + 1); + } + len = ownStyles.length; + for (i = 0; i < len; i += 1) { + ownStyles[i].closed = true; + } + len = ownModifiers.length; + for (i = 0; i < len; i += 1) { + ownModifiers[i].closed = true; + } +}; + +SVGShapeElement.prototype.renderInnerContent = function () { + this.renderModifiers(); + var i; + var len = this.stylesList.length; + for (i = 0; i < len; i += 1) { + this.stylesList[i].reset(); + } + this.renderShape(); + for (i = 0; i < len; i += 1) { + if (this.stylesList[i]._mdf || this._isFirstFrame) { + if (this.stylesList[i].msElem) { + this.stylesList[i].msElem.setAttribute('d', this.stylesList[i].d); + // Adding M0 0 fixes same mask bug on all browsers + this.stylesList[i].d = 'M0 0' + this.stylesList[i].d; + } + this.stylesList[i].pElem.setAttribute('d', this.stylesList[i].d || 'M0 0'); + } + } +}; + +SVGShapeElement.prototype.renderShape = function () { + var i; + var len = this.animatedContents.length; + var animatedContent; + for (i = 0; i < len; i += 1) { + animatedContent = this.animatedContents[i]; + if ((this._isFirstFrame || animatedContent.element._isAnimated) && animatedContent.data !== true) { + animatedContent.fn(animatedContent.data, animatedContent.element, this._isFirstFrame); + } + } +}; + +SVGShapeElement.prototype.destroy = function () { + this.destroyBaseElement(); + this.shapesData = null; + this.itemsData = null; +}; + +function LetterProps(o, sw, sc, fc, m, p) { + this.o = o; + this.sw = sw; + this.sc = sc; + this.fc = fc; + this.m = m; + this.p = p; + this._mdf = { + o: true, + sw: !!sw, + sc: !!sc, + fc: !!fc, + m: true, + p: true, + }; +} + +LetterProps.prototype.update = function (o, sw, sc, fc, m, p) { + this._mdf.o = false; + this._mdf.sw = false; + this._mdf.sc = false; + this._mdf.fc = false; + this._mdf.m = false; + this._mdf.p = false; + var updated = false; + + if (this.o !== o) { + this.o = o; + this._mdf.o = true; + updated = true; + } + if (this.sw !== sw) { + this.sw = sw; + this._mdf.sw = true; + updated = true; + } + if (this.sc !== sc) { + this.sc = sc; + this._mdf.sc = true; + updated = true; + } + if (this.fc !== fc) { + this.fc = fc; + this._mdf.fc = true; + updated = true; + } + if (this.m !== m) { + this.m = m; + this._mdf.m = true; + updated = true; + } + if (p.length && (this.p[0] !== p[0] || this.p[1] !== p[1] || this.p[4] !== p[4] || this.p[5] !== p[5] || this.p[12] !== p[12] || this.p[13] !== p[13])) { + this.p = p; + this._mdf.p = true; + updated = true; + } + return updated; +}; + +function TextProperty(elem, data) { + this._frameId = initialDefaultFrame; + this.pv = ''; + this.v = ''; + this.kf = false; + this._isFirstFrame = true; + this._mdf = false; + this.data = data; + this.elem = elem; + this.comp = this.elem.comp; + this.keysIndex = 0; + this.canResize = false; + this.minimumFontSize = 1; + this.effectsSequence = []; + this.currentData = { + ascent: 0, + boxWidth: this.defaultBoxWidth, + f: '', + fStyle: '', + fWeight: '', + fc: '', + j: '', + justifyOffset: '', + l: [], + lh: 0, + lineWidths: [], + ls: '', + of: '', + s: '', + sc: '', + sw: 0, + t: 0, + tr: 0, + sz: 0, + ps: null, + fillColorAnim: false, + strokeColorAnim: false, + strokeWidthAnim: false, + yOffset: 0, + finalSize: 0, + finalText: [], + finalLineHeight: 0, + __complete: false, + + }; + this.copyData(this.currentData, this.data.d.k[0].s); + + if (!this.searchProperty()) { + this.completeTextData(this.currentData); + } +} + +TextProperty.prototype.defaultBoxWidth = [0, 0]; + +TextProperty.prototype.copyData = function (obj, data) { + for (var s in data) { + if (Object.prototype.hasOwnProperty.call(data, s)) { + obj[s] = data[s]; + } + } + return obj; +}; + +TextProperty.prototype.setCurrentData = function (data) { + if (!data.__complete) { + this.completeTextData(data); + } + this.currentData = data; + this.currentData.boxWidth = this.currentData.boxWidth || this.defaultBoxWidth; + this._mdf = true; +}; + +TextProperty.prototype.searchProperty = function () { + return this.searchKeyframes(); +}; + +TextProperty.prototype.searchKeyframes = function () { + this.kf = this.data.d.k.length > 1; + if (this.kf) { + this.addEffect(this.getKeyframeValue.bind(this)); + } + return this.kf; +}; + +TextProperty.prototype.addEffect = function (effectFunction) { + this.effectsSequence.push(effectFunction); + this.elem.addDynamicProperty(this); +}; + +TextProperty.prototype.getValue = function (_finalValue) { + if ((this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) && !_finalValue) { + return; + } + this.currentData.t = this.data.d.k[this.keysIndex].s.t; + var currentValue = this.currentData; + var currentIndex = this.keysIndex; + if (this.lock) { + this.setCurrentData(this.currentData); + return; + } + this.lock = true; + this._mdf = false; + var i; var + len = this.effectsSequence.length; + var finalValue = _finalValue || this.data.d.k[this.keysIndex].s; + for (i = 0; i < len; i += 1) { + // Checking if index changed to prevent creating a new object every time the expression updates. + if (currentIndex !== this.keysIndex) { + finalValue = this.effectsSequence[i](finalValue, finalValue.t); + } else { + finalValue = this.effectsSequence[i](this.currentData, finalValue.t); + } + } + if (currentValue !== finalValue) { + this.setCurrentData(finalValue); + } + this.v = this.currentData; + this.pv = this.v; + this.lock = false; + this.frameId = this.elem.globalData.frameId; +}; + +TextProperty.prototype.getKeyframeValue = function () { + var textKeys = this.data.d.k; + var frameNum = this.elem.comp.renderedFrame; + var i = 0; var + len = textKeys.length; + while (i <= len - 1) { + if (i === len - 1 || textKeys[i + 1].t > frameNum) { + break; + } + i += 1; + } + if (this.keysIndex !== i) { + this.keysIndex = i; + } + return this.data.d.k[this.keysIndex].s; +}; + +TextProperty.prototype.buildFinalText = function (text) { + var charactersArray = []; + var i = 0; + var len = text.length; + var charCode; + var secondCharCode; + var shouldCombine = false; + while (i < len) { + charCode = text.charCodeAt(i); + if (FontManager.isCombinedCharacter(charCode)) { + charactersArray[charactersArray.length - 1] += text.charAt(i); + } else if (charCode >= 0xD800 && charCode <= 0xDBFF) { + secondCharCode = text.charCodeAt(i + 1); + if (secondCharCode >= 0xDC00 && secondCharCode <= 0xDFFF) { + if (shouldCombine || FontManager.isModifier(charCode, secondCharCode)) { + charactersArray[charactersArray.length - 1] += text.substr(i, 2); + shouldCombine = false; + } else { + charactersArray.push(text.substr(i, 2)); + } + i += 1; + } else { + charactersArray.push(text.charAt(i)); + } + } else if (charCode > 0xDBFF) { + secondCharCode = text.charCodeAt(i + 1); + if (FontManager.isZeroWidthJoiner(charCode, secondCharCode)) { + shouldCombine = true; + charactersArray[charactersArray.length - 1] += text.substr(i, 2); + i += 1; + } else { + charactersArray.push(text.charAt(i)); + } + } else if (FontManager.isZeroWidthJoiner(charCode)) { + charactersArray[charactersArray.length - 1] += text.charAt(i); + shouldCombine = true; + } else { + charactersArray.push(text.charAt(i)); + } + i += 1; + } + return charactersArray; +}; + +TextProperty.prototype.completeTextData = function (documentData) { + documentData.__complete = true; + var fontManager = this.elem.globalData.fontManager; + var data = this.data; + var letters = []; + var i; var + len; + var newLineFlag; var index = 0; var + val; + var anchorGrouping = data.m.g; + var currentSize = 0; var currentPos = 0; var currentLine = 0; var + lineWidths = []; + var lineWidth = 0; + var maxLineWidth = 0; + var j; var + jLen; + var fontData = fontManager.getFontByName(documentData.f); + var charData; var + cLength = 0; + + var fontProps = getFontProperties(fontData); + documentData.fWeight = fontProps.weight; + documentData.fStyle = fontProps.style; + documentData.finalSize = documentData.s; + documentData.finalText = this.buildFinalText(documentData.t); + len = documentData.finalText.length; + documentData.finalLineHeight = documentData.lh; + var trackingOffset = (documentData.tr / 1000) * documentData.finalSize; + var charCode; + if (documentData.sz) { + var flag = true; + var boxWidth = documentData.sz[0]; + var boxHeight = documentData.sz[1]; + var currentHeight; var + finalText; + while (flag) { + finalText = this.buildFinalText(documentData.t); + currentHeight = 0; + lineWidth = 0; + len = finalText.length; + trackingOffset = (documentData.tr / 1000) * documentData.finalSize; + var lastSpaceIndex = -1; for (i = 0; i < len; i += 1) { - segmentOb = segments[i]; - if (!(segmentOb.e * totalModifierLength < addedLength || segmentOb.s * totalModifierLength > addedLength + shapeLength)) { - var shapeS; - var shapeE; - if (segmentOb.s * totalModifierLength <= addedLength) { - shapeS = 0; - } else { - shapeS = (segmentOb.s * totalModifierLength - addedLength) / shapeLength; - } - if (segmentOb.e * totalModifierLength >= addedLength + shapeLength) { - shapeE = 1; + charCode = finalText[i].charCodeAt(0); + newLineFlag = false; + if (finalText[i] === ' ') { + lastSpaceIndex = i; + } else if (charCode === 13 || charCode === 3) { + lineWidth = 0; + newLineFlag = true; + currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; + } + if (fontManager.chars) { + charData = fontManager.getCharData(finalText[i], fontData.fStyle, fontData.fFamily); + cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; + } else { + // tCanvasHelper.font = documentData.s + 'px '+ fontData.fFamily; + cLength = fontManager.measureText(finalText[i], documentData.f, documentData.finalSize); + } + if (lineWidth + cLength > boxWidth && finalText[i] !== ' ') { + if (lastSpaceIndex === -1) { + len += 1; } else { - shapeE = ((segmentOb.e * totalModifierLength - addedLength) / shapeLength); + i = lastSpaceIndex; } - shapeSegments.push([shapeS, shapeE]); + currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; + finalText.splice(i, lastSpaceIndex === i ? 1 : 0, '\r'); + // finalText = finalText.substr(0,i) + "\r" + finalText.substr(i === lastSpaceIndex ? i + 1 : i); + lastSpaceIndex = -1; + lineWidth = 0; + } else { + lineWidth += cLength; + lineWidth += trackingOffset; } } - if (!shapeSegments.length) { - shapeSegments.push([0, 0]); + currentHeight += (fontData.ascent * documentData.finalSize) / 100; + if (this.canResize && documentData.finalSize > this.minimumFontSize && boxHeight < currentHeight) { + documentData.finalSize -= 1; + documentData.finalLineHeight = (documentData.finalSize * documentData.lh) / documentData.s; + } else { + documentData.finalText = finalText; + len = documentData.finalText.length; + flag = false; } - return shapeSegments; - }; + } + } + lineWidth = -trackingOffset; + cLength = 0; + var uncollapsedSpaces = 0; + var currentChar; + for (i = 0; i < len; i += 1) { + newLineFlag = false; + currentChar = documentData.finalText[i]; + charCode = currentChar.charCodeAt(0); + if (charCode === 13 || charCode === 3) { + uncollapsedSpaces = 0; + lineWidths.push(lineWidth); + maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; + lineWidth = -2 * trackingOffset; + val = ''; + newLineFlag = true; + currentLine += 1; + } else { + val = currentChar; + } + if (fontManager.chars) { + charData = fontManager.getCharData(currentChar, fontData.fStyle, fontManager.getFontByName(documentData.f).fFamily); + cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; + } else { + // var charWidth = fontManager.measureText(val, documentData.f, documentData.finalSize); + // tCanvasHelper.font = documentData.finalSize + 'px '+ fontManager.getFontByName(documentData.f).fFamily; + cLength = fontManager.measureText(val, documentData.f, documentData.finalSize); + } - TrimModifier.prototype.releasePathsData = function (pathsData) { - var i; - var len = pathsData.length; + // + if (currentChar === ' ') { + uncollapsedSpaces += cLength + trackingOffset; + } else { + lineWidth += cLength + trackingOffset + uncollapsedSpaces; + uncollapsedSpaces = 0; + } + letters.push({ + l: cLength, an: cLength, add: currentSize, n: newLineFlag, anIndexes: [], val: val, line: currentLine, animatorJustifyOffset: 0, + }); + if (anchorGrouping == 2) { // eslint-disable-line eqeqeq + currentSize += cLength; + if (val === '' || val === ' ' || i === len - 1) { + if (val === '' || val === ' ') { + currentSize -= cLength; + } + while (currentPos <= i) { + letters[currentPos].an = currentSize; + letters[currentPos].ind = index; + letters[currentPos].extra = cLength; + currentPos += 1; + } + index += 1; + currentSize = 0; + } + } else if (anchorGrouping == 3) { // eslint-disable-line eqeqeq + currentSize += cLength; + if (val === '' || i === len - 1) { + if (val === '') { + currentSize -= cLength; + } + while (currentPos <= i) { + letters[currentPos].an = currentSize; + letters[currentPos].ind = index; + letters[currentPos].extra = cLength; + currentPos += 1; + } + currentSize = 0; + index += 1; + } + } else { + letters[index].ind = index; + letters[index].extra = 0; + index += 1; + } + } + documentData.l = letters; + maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; + lineWidths.push(lineWidth); + if (documentData.sz) { + documentData.boxWidth = documentData.sz[0]; + documentData.justifyOffset = 0; + } else { + documentData.boxWidth = maxLineWidth; + switch (documentData.j) { + case 1: + documentData.justifyOffset = -documentData.boxWidth; + break; + case 2: + documentData.justifyOffset = -documentData.boxWidth / 2; + break; + default: + documentData.justifyOffset = 0; + } + } + documentData.lineWidths = lineWidths; + + var animators = data.a; var animatorData; var + letterData; + jLen = animators.length; + var based; var ind; var + indexes = []; + for (j = 0; j < jLen; j += 1) { + animatorData = animators[j]; + if (animatorData.a.sc) { + documentData.strokeColorAnim = true; + } + if (animatorData.a.sw) { + documentData.strokeWidthAnim = true; + } + if (animatorData.a.fc || animatorData.a.fh || animatorData.a.fs || animatorData.a.fb) { + documentData.fillColorAnim = true; + } + ind = 0; + based = animatorData.s.b; + for (i = 0; i < len; i += 1) { + letterData = letters[i]; + letterData.anIndexes[j] = ind; + if ((based == 1 && letterData.val !== '') || (based == 2 && letterData.val !== '' && letterData.val !== ' ') || (based == 3 && (letterData.n || letterData.val == ' ' || i == len - 1)) || (based == 4 && (letterData.n || i == len - 1))) { // eslint-disable-line eqeqeq + if (animatorData.s.rn === 1) { + indexes.push(ind); + } + ind += 1; + } + } + data.a[j].s.totalChars = ind; + var currentInd = -1; var + newInd; + if (animatorData.s.rn === 1) { for (i = 0; i < len; i += 1) { - segmentsLengthPool.release(pathsData[i]); + letterData = letters[i]; + if (currentInd != letterData.anIndexes[j]) { // eslint-disable-line eqeqeq + currentInd = letterData.anIndexes[j]; + newInd = indexes.splice(Math.floor(Math.random() * indexes.length), 1)[0]; + } + letterData.anIndexes[j] = newInd; } - pathsData.length = 0; - return pathsData; - }; + } + } + documentData.yOffset = documentData.finalLineHeight || documentData.finalSize * 1.2; + documentData.ls = documentData.ls || 0; + documentData.ascent = (fontData.ascent * documentData.finalSize) / 100; +}; + +TextProperty.prototype.updateDocumentData = function (newData, index) { + index = index === undefined ? this.keysIndex : index; + var dData = this.copyData({}, this.data.d.k[index].s); + dData = this.copyData(dData, newData); + this.data.d.k[index].s = dData; + this.recalculate(index); + this.elem.addDynamicProperty(this); +}; + +TextProperty.prototype.recalculate = function (index) { + var dData = this.data.d.k[index].s; + dData.__complete = false; + this.keysIndex = 0; + this._isFirstFrame = true; + this.getValue(dData); +}; + +TextProperty.prototype.canResizeFont = function (_canResize) { + this.canResize = _canResize; + this.recalculate(this.keysIndex); + this.elem.addDynamicProperty(this); +}; + +TextProperty.prototype.setMinimumFontSize = function (_fontValue) { + this.minimumFontSize = Math.floor(_fontValue) || 1; + this.recalculate(this.keysIndex); + this.elem.addDynamicProperty(this); +}; + +const TextSelectorProp = (function () { + var max = Math.max; + var min = Math.min; + var floor = Math.floor; + + function TextSelectorPropFactory(elem, data) { + this._currentTextLength = -1; + this.k = false; + this.data = data; + this.elem = elem; + this.comp = elem.comp; + this.finalS = 0; + this.finalE = 0; + this.initDynamicPropertyContainer(elem); + this.s = PropertyFactory.getProp(elem, data.s || { k: 0 }, 0, 0, this); + if ('e' in data) { + this.e = PropertyFactory.getProp(elem, data.e, 0, 0, this); + } else { + this.e = { v: 100 }; + } + this.o = PropertyFactory.getProp(elem, data.o || { k: 0 }, 0, 0, this); + this.xe = PropertyFactory.getProp(elem, data.xe || { k: 0 }, 0, 0, this); + this.ne = PropertyFactory.getProp(elem, data.ne || { k: 0 }, 0, 0, this); + this.sm = PropertyFactory.getProp(elem, data.sm || { k: 100 }, 0, 0, this); + this.a = PropertyFactory.getProp(elem, data.a, 0, 0.01, this); + if (!this.dynamicProperties.length) { + this.getValue(); + } + } + + TextSelectorPropFactory.prototype = { + getMult: function (ind) { + if (this._currentTextLength !== this.elem.textProperty.currentData.l.length) { + this.getValue(); + } + var x1 = 0; + var y1 = 0; + var x2 = 1; + var y2 = 1; + if (this.ne.v > 0) { + x1 = this.ne.v / 100.0; + } else { + y1 = -this.ne.v / 100.0; + } + if (this.xe.v > 0) { + x2 = 1.0 - this.xe.v / 100.0; + } else { + y2 = 1.0 + this.xe.v / 100.0; + } + var easer = BezierFactory.getBezierEasing(x1, y1, x2, y2).get; - TrimModifier.prototype.processShapes = function (_isFirstFrame) { - var s; - var e; - if (this._mdf || _isFirstFrame) { - var o = (this.o.v % 360) / 360; - if (o < 0) { - o += 1; - } - if (this.s.v > 1) { - s = 1 + o; - } else if (this.s.v < 0) { - s = 0 + o; + var mult = 0; + var s = this.finalS; + var e = this.finalE; + var type = this.data.sh; + if (type === 2) { + if (e === s) { + mult = ind >= e ? 1 : 0; } else { - s = this.s.v + o; + mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); } - if (this.e.v > 1) { - e = 1 + o; - } else if (this.e.v < 0) { - e = 0 + o; + mult = easer(mult); + } else if (type === 3) { + if (e === s) { + mult = ind >= e ? 0 : 1; } else { - e = this.e.v + o; + mult = 1 - max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); } - if (s > e) { - var _s = s; - s = e; - e = _s; - } - s = Math.round(s * 10000) * 0.0001; - e = Math.round(e * 10000) * 0.0001; - this.sValue = s; - this.eValue = e; - } else { - s = this.sValue; - e = this.eValue; - } - var shapePaths; - var i; - var len = this.shapes.length; - var j; - var jLen; - var pathsData; - var pathData; - var totalShapeLength; - var totalModifierLength = 0; - - if (e === s) { - for (i = 0; i < len; i += 1) { - this.shapes[i].localShapeCollection.releaseShapes(); - this.shapes[i].shape._mdf = true; - this.shapes[i].shape.paths = this.shapes[i].localShapeCollection; - if (this._mdf) { - this.shapes[i].pathsData.length = 0; + mult = easer(mult); + } else if (type === 4) { + if (e === s) { + mult = 0; + } else { + mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); + if (mult < 0.5) { + mult *= 2; + } else { + mult = 1 - 2 * (mult - 0.5); } } - } else if (!((e === 1 && s === 0) || (e === 0 && s === 1))) { - var segments = []; - var shapeData; - var localShapeCollection; - for (i = 0; i < len; i += 1) { - shapeData = this.shapes[i]; - // if shape hasn't changed and trim properties haven't changed, cached previous path can be used - if (!shapeData.shape._mdf && !this._mdf && !_isFirstFrame && this.m !== 2) { - shapeData.shape.paths = shapeData.localShapeCollection; + mult = easer(mult); + } else if (type === 5) { + if (e === s) { + mult = 0; + } else { + var tot = e - s; + /* ind += 0.5; + mult = -4/(tot*tot)*(ind*ind)+(4/tot)*ind; */ + ind = min(max(0, ind + 0.5 - s), e - s); + var x = -tot / 2 + ind; + var a = tot / 2; + mult = Math.sqrt(1 - (x * x) / (a * a)); + } + mult = easer(mult); + } else if (type === 6) { + if (e === s) { + mult = 0; + } else { + ind = min(max(0, ind + 0.5 - s), e - s); + mult = (1 + (Math.cos((Math.PI + Math.PI * 2 * (ind) / (e - s))))) / 2; // eslint-disable-line + } + mult = easer(mult); + } else { + if (ind >= floor(s)) { + if (ind - s < 0) { + mult = max(0, min(min(e, 1) - (s - ind), 1)); } else { - shapePaths = shapeData.shape.paths; - jLen = shapePaths._length; - totalShapeLength = 0; - if (!shapeData.shape._mdf && shapeData.pathsData.length) { - totalShapeLength = shapeData.totalShapeLength; - } else { - pathsData = this.releasePathsData(shapeData.pathsData); - for (j = 0; j < jLen; j += 1) { - pathData = bez.getSegmentsLength(shapePaths.shapes[j]); - pathsData.push(pathData); - totalShapeLength += pathData.totalLength; - } - shapeData.totalShapeLength = totalShapeLength; - shapeData.pathsData = pathsData; - } - - totalModifierLength += totalShapeLength; - shapeData.shape._mdf = true; + mult = max(0, min(e - ind, 1)); + } + } + mult = easer(mult); + } + // Smoothness implementation. + // The smoothness represents a reduced range of the original [0; 1] range. + // if smoothness is 25%, the new range will be [0.375; 0.625] + // Steps are: + // - find the lower value of the new range (threshold) + // - if multiplier is smaller than that value, floor it to 0 + // - if it is larger, + // - subtract the threshold + // - divide it by the smoothness (this will return the range to [0; 1]) + // Note: If it doesn't work on some scenarios, consider applying it before the easer. + if (this.sm.v !== 100) { + var smoothness = this.sm.v * 0.01; + if (smoothness === 0) { + smoothness = 0.00000001; + } + var threshold = 0.5 - smoothness * 0.5; + if (mult < threshold) { + mult = 0; + } else { + mult = (mult - threshold) / smoothness; + if (mult > 1) { + mult = 1; } } - var shapeS = s; - var shapeE = e; - var addedLength = 0; - var edges; - for (i = len - 1; i >= 0; i -= 1) { - shapeData = this.shapes[i]; - if (shapeData.shape._mdf) { - localShapeCollection = shapeData.localShapeCollection; - localShapeCollection.releaseShapes(); - // if m === 2 means paths are trimmed individually so edges need to be found for this specific shape relative to whoel group - if (this.m === 2 && len > 1) { - edges = this.calculateShapeEdges(s, e, shapeData.totalShapeLength, addedLength, totalModifierLength); - addedLength += shapeData.totalShapeLength; - } else { - edges = [[shapeS, shapeE]]; + } + return mult * this.a.v; + }, + getValue: function (newCharsFlag) { + this.iterateDynamicProperties(); + this._mdf = newCharsFlag || this._mdf; + this._currentTextLength = this.elem.textProperty.currentData.l.length || 0; + if (newCharsFlag && this.data.r === 2) { + this.e.v = this._currentTextLength; + } + var divisor = this.data.r === 2 ? 1 : 100 / this.data.totalChars; + var o = this.o.v / divisor; + var s = this.s.v / divisor + o; + var e = (this.e.v / divisor) + o; + if (s > e) { + var _s = s; + s = e; + e = _s; + } + this.finalS = s; + this.finalE = e; + }, + }; + extendPrototype([DynamicPropertyContainer], TextSelectorPropFactory); + + function getTextSelectorProp(elem, data, arr) { + return new TextSelectorPropFactory(elem, data, arr); + } + + return { + getTextSelectorProp: getTextSelectorProp, + }; +}()); + +function TextAnimatorDataProperty(elem, animatorProps, container) { + var defaultData = { propType: false }; + var getProp = PropertyFactory.getProp; + var textAnimatorAnimatables = animatorProps.a; + this.a = { + r: textAnimatorAnimatables.r ? getProp(elem, textAnimatorAnimatables.r, 0, degToRads, container) : defaultData, + rx: textAnimatorAnimatables.rx ? getProp(elem, textAnimatorAnimatables.rx, 0, degToRads, container) : defaultData, + ry: textAnimatorAnimatables.ry ? getProp(elem, textAnimatorAnimatables.ry, 0, degToRads, container) : defaultData, + sk: textAnimatorAnimatables.sk ? getProp(elem, textAnimatorAnimatables.sk, 0, degToRads, container) : defaultData, + sa: textAnimatorAnimatables.sa ? getProp(elem, textAnimatorAnimatables.sa, 0, degToRads, container) : defaultData, + s: textAnimatorAnimatables.s ? getProp(elem, textAnimatorAnimatables.s, 1, 0.01, container) : defaultData, + a: textAnimatorAnimatables.a ? getProp(elem, textAnimatorAnimatables.a, 1, 0, container) : defaultData, + o: textAnimatorAnimatables.o ? getProp(elem, textAnimatorAnimatables.o, 0, 0.01, container) : defaultData, + p: textAnimatorAnimatables.p ? getProp(elem, textAnimatorAnimatables.p, 1, 0, container) : defaultData, + sw: textAnimatorAnimatables.sw ? getProp(elem, textAnimatorAnimatables.sw, 0, 0, container) : defaultData, + sc: textAnimatorAnimatables.sc ? getProp(elem, textAnimatorAnimatables.sc, 1, 0, container) : defaultData, + fc: textAnimatorAnimatables.fc ? getProp(elem, textAnimatorAnimatables.fc, 1, 0, container) : defaultData, + fh: textAnimatorAnimatables.fh ? getProp(elem, textAnimatorAnimatables.fh, 0, 0, container) : defaultData, + fs: textAnimatorAnimatables.fs ? getProp(elem, textAnimatorAnimatables.fs, 0, 0.01, container) : defaultData, + fb: textAnimatorAnimatables.fb ? getProp(elem, textAnimatorAnimatables.fb, 0, 0.01, container) : defaultData, + t: textAnimatorAnimatables.t ? getProp(elem, textAnimatorAnimatables.t, 0, 0, container) : defaultData, + }; + + this.s = TextSelectorProp.getTextSelectorProp(elem, animatorProps.s, container); + this.s.t = animatorProps.s.t; +} + +function TextAnimatorProperty(textData, renderType, elem) { + this._isFirstFrame = true; + this._hasMaskedPath = false; + this._frameId = -1; + this._textData = textData; + this._renderType = renderType; + this._elem = elem; + this._animatorsData = createSizedArray(this._textData.a.length); + this._pathData = {}; + this._moreOptions = { + alignment: {}, + }; + this.renderedLetters = []; + this.lettersChangedFlag = false; + this.initDynamicPropertyContainer(elem); +} + +TextAnimatorProperty.prototype.searchProperties = function () { + var i; + var len = this._textData.a.length; + var animatorProps; + var getProp = PropertyFactory.getProp; + for (i = 0; i < len; i += 1) { + animatorProps = this._textData.a[i]; + this._animatorsData[i] = new TextAnimatorDataProperty(this._elem, animatorProps, this); + } + if (this._textData.p && 'm' in this._textData.p) { + this._pathData = { + a: getProp(this._elem, this._textData.p.a, 0, 0, this), + f: getProp(this._elem, this._textData.p.f, 0, 0, this), + l: getProp(this._elem, this._textData.p.l, 0, 0, this), + r: getProp(this._elem, this._textData.p.r, 0, 0, this), + p: getProp(this._elem, this._textData.p.p, 0, 0, this), + m: this._elem.maskManager.getMaskProperty(this._textData.p.m), + }; + this._hasMaskedPath = true; + } else { + this._hasMaskedPath = false; + } + this._moreOptions.alignment = getProp(this._elem, this._textData.m.a, 1, 0, this); +}; + +TextAnimatorProperty.prototype.getMeasures = function (documentData, lettersChangedFlag) { + this.lettersChangedFlag = lettersChangedFlag; + if (!this._mdf && !this._isFirstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m._mdf)) { + return; + } + this._isFirstFrame = false; + var alignment = this._moreOptions.alignment.v; + var animators = this._animatorsData; + var textData = this._textData; + var matrixHelper = this.mHelper; + var renderType = this._renderType; + var renderedLettersCount = this.renderedLetters.length; + var xPos; + var yPos; + var i; + var len; + var letters = documentData.l; + var pathInfo; + var currentLength; + var currentPoint; + var segmentLength; + var flag; + var pointInd; + var segmentInd; + var prevPoint; + var points; + var segments; + var partialLength; + var totalLength; + var perc; + var tanAngle; + var mask; + if (this._hasMaskedPath) { + mask = this._pathData.m; + if (!this._pathData.n || this._pathData._mdf) { + var paths = mask.v; + if (this._pathData.r.v) { + paths = paths.reverse(); + } + // TODO: release bezier data cached from previous pathInfo: this._pathData.pi + pathInfo = { + tLength: 0, + segments: [], + }; + len = paths._length - 1; + var bezierData; + totalLength = 0; + for (i = 0; i < len; i += 1) { + bezierData = bez.buildBezierData(paths.v[i], + paths.v[i + 1], + [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], + [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]); + pathInfo.tLength += bezierData.segmentLength; + pathInfo.segments.push(bezierData); + totalLength += bezierData.segmentLength; + } + i = len; + if (mask.v.c) { + bezierData = bez.buildBezierData(paths.v[i], + paths.v[0], + [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], + [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]); + pathInfo.tLength += bezierData.segmentLength; + pathInfo.segments.push(bezierData); + totalLength += bezierData.segmentLength; + } + this._pathData.pi = pathInfo; + } + pathInfo = this._pathData.pi; + + currentLength = this._pathData.f.v; + segmentInd = 0; + pointInd = 1; + segmentLength = 0; + flag = true; + segments = pathInfo.segments; + if (currentLength < 0 && mask.v.c) { + if (pathInfo.tLength < Math.abs(currentLength)) { + currentLength = -Math.abs(currentLength) % pathInfo.tLength; + } + segmentInd = segments.length - 1; + points = segments[segmentInd].points; + pointInd = points.length - 1; + while (currentLength < 0) { + currentLength += points[pointInd].partialLength; + pointInd -= 1; + if (pointInd < 0) { + segmentInd -= 1; + points = segments[segmentInd].points; + pointInd = points.length - 1; + } + } + } + points = segments[segmentInd].points; + prevPoint = points[pointInd - 1]; + currentPoint = points[pointInd]; + partialLength = currentPoint.partialLength; + } + + len = letters.length; + xPos = 0; + yPos = 0; + var yOff = documentData.finalSize * 1.2 * 0.714; + var firstLine = true; + var animatorProps; + var animatorSelector; + var j; + var jLen; + var letterValue; + + jLen = animators.length; + + var mult; + var ind = -1; + var offf; + var xPathPos; + var yPathPos; + var initPathPos = currentLength; + var initSegmentInd = segmentInd; + var initPointInd = pointInd; + var currentLine = -1; + var elemOpacity; + var sc; + var sw; + var fc; + var k; + var letterSw; + var letterSc; + var letterFc; + var letterM = ''; + var letterP = this.defaultPropsArray; + var letterO; + + // + if (documentData.j === 2 || documentData.j === 1) { + var animatorJustifyOffset = 0; + var animatorFirstCharOffset = 0; + var justifyOffsetMult = documentData.j === 2 ? -0.5 : -1; + var lastIndex = 0; + var isNewLine = true; + + for (i = 0; i < len; i += 1) { + if (letters[i].n) { + if (animatorJustifyOffset) { + animatorJustifyOffset += animatorFirstCharOffset; + } + while (lastIndex < i) { + letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; + lastIndex += 1; + } + animatorJustifyOffset = 0; + isNewLine = true; + } else { + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.t.propType) { + if (isNewLine && documentData.j === 2) { + animatorFirstCharOffset += animatorProps.t.v * justifyOffsetMult; } - jLen = edges.length; - for (j = 0; j < jLen; j += 1) { - shapeS = edges[j][0]; - shapeE = edges[j][1]; - segments.length = 0; - if (shapeE <= 1) { - segments.push({ - s: shapeData.totalShapeLength * shapeS, - e: shapeData.totalShapeLength * shapeE, - }); - } else if (shapeS >= 1) { - segments.push({ - s: shapeData.totalShapeLength * (shapeS - 1), - e: shapeData.totalShapeLength * (shapeE - 1), - }); - } else { - segments.push({ - s: shapeData.totalShapeLength * shapeS, - e: shapeData.totalShapeLength, - }); - segments.push({ - s: 0, - e: shapeData.totalShapeLength * (shapeE - 1), - }); - } - var newShapesData = this.addShapes(shapeData, segments[0]); - if (segments[0].s !== segments[0].e) { - if (segments.length > 1) { - var lastShapeInCollection = shapeData.shape.paths.shapes[shapeData.shape.paths._length - 1]; - if (lastShapeInCollection.c) { - var lastShape = newShapesData.pop(); - this.addPaths(newShapesData, localShapeCollection); - newShapesData = this.addShapes(shapeData, segments[1], lastShape); - } else { - this.addPaths(newShapesData, localShapeCollection); - newShapesData = this.addShapes(shapeData, segments[1]); - } - } - this.addPaths(newShapesData, localShapeCollection); - } + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + animatorJustifyOffset += animatorProps.t.v * mult[0] * justifyOffsetMult; + } else { + animatorJustifyOffset += animatorProps.t.v * mult * justifyOffsetMult; } - shapeData.shape.paths = localShapeCollection; } } - } else if (this._mdf) { - for (i = 0; i < len; i += 1) { - // Releasign Trim Cached paths data when no trim applied in case shapes are modified inbetween. - // Don't remove this even if it's losing cached info. - this.shapes[i].pathsData.length = 0; - this.shapes[i].shape._mdf = true; - } + isNewLine = false; } - }; - - TrimModifier.prototype.addPaths = function (newPaths, localShapeCollection) { - var i; - var len = newPaths.length; - for (i = 0; i < len; i += 1) { - localShapeCollection.addShape(newPaths[i]); - } - }; - - TrimModifier.prototype.addSegment = function (pt1, pt2, pt3, pt4, shapePath, pos, newShape) { - shapePath.setXYAt(pt2[0], pt2[1], 'o', pos); - shapePath.setXYAt(pt3[0], pt3[1], 'i', pos + 1); - if (newShape) { - shapePath.setXYAt(pt1[0], pt1[1], 'v', pos); - } - shapePath.setXYAt(pt4[0], pt4[1], 'v', pos + 1); - }; - - TrimModifier.prototype.addSegmentFromArray = function (points, shapePath, pos, newShape) { - shapePath.setXYAt(points[1], points[5], 'o', pos); - shapePath.setXYAt(points[2], points[6], 'i', pos + 1); - if (newShape) { - shapePath.setXYAt(points[0], points[4], 'v', pos); - } - shapePath.setXYAt(points[3], points[7], 'v', pos + 1); - }; + } + if (animatorJustifyOffset) { + animatorJustifyOffset += animatorFirstCharOffset; + } + while (lastIndex < i) { + letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; + lastIndex += 1; + } + } + // - TrimModifier.prototype.addShapes = function (shapeData, shapeSegment, shapePath) { - var pathsData = shapeData.pathsData; - var shapePaths = shapeData.shape.paths.shapes; - var i; - var len = shapeData.shape.paths._length; - var j; - var jLen; - var addedLength = 0; - var currentLengthData; - var segmentCount; - var lengths; - var segment; - var shapes = []; - var initPos; - var newShape = true; - if (!shapePath) { - shapePath = shapePool.newElement(); - segmentCount = 0; - initPos = 0; - } else { - segmentCount = shapePath._length; - initPos = shapePath._length; + for (i = 0; i < len; i += 1) { + matrixHelper.reset(); + elemOpacity = 1; + if (letters[i].n) { + xPos = 0; + yPos += documentData.yOffset; + yPos += firstLine ? 1 : 0; + currentLength = initPathPos; + firstLine = false; + if (this._hasMaskedPath) { + segmentInd = initSegmentInd; + pointInd = initPointInd; + points = segments[segmentInd].points; + prevPoint = points[pointInd - 1]; + currentPoint = points[pointInd]; + partialLength = currentPoint.partialLength; + segmentLength = 0; } - shapes.push(shapePath); - for (i = 0; i < len; i += 1) { - lengths = pathsData[i].lengths; - shapePath.c = shapePaths[i].c; - jLen = shapePaths[i].c ? lengths.length : lengths.length + 1; - for (j = 1; j < jLen; j += 1) { - currentLengthData = lengths[j - 1]; - if (addedLength + currentLengthData.addedLength < shapeSegment.s) { - addedLength += currentLengthData.addedLength; - shapePath.c = false; - } else if (addedLength > shapeSegment.e) { - shapePath.c = false; - break; - } else { - if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + currentLengthData.addedLength) { - this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[j], shapePaths[i].v[j], shapePath, segmentCount, newShape); - newShape = false; + letterM = ''; + letterFc = ''; + letterSw = ''; + letterO = ''; + letterP = this.defaultPropsArray; + } else { + if (this._hasMaskedPath) { + if (currentLine !== letters[i].line) { + switch (documentData.j) { + case 1: + currentLength += totalLength - documentData.lineWidths[letters[i].line]; + break; + case 2: + currentLength += (totalLength - documentData.lineWidths[letters[i].line]) / 2; + break; + default: + break; + } + currentLine = letters[i].line; + } + if (ind !== letters[i].ind) { + if (letters[ind]) { + currentLength += letters[ind].extra; + } + currentLength += letters[i].an / 2; + ind = letters[i].ind; + } + currentLength += (alignment[0] * letters[i].an) * 0.005; + var animatorOffset = 0; + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.p.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + animatorOffset += animatorProps.p.v[0] * mult[0]; } else { - segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[j], shapePaths[i].o[j - 1], shapePaths[i].i[j], (shapeSegment.s - addedLength) / currentLengthData.addedLength, (shapeSegment.e - addedLength) / currentLengthData.addedLength, lengths[j - 1]); - this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); - // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); - newShape = false; - shapePath.c = false; + animatorOffset += animatorProps.p.v[0] * mult; } - addedLength += currentLengthData.addedLength; - segmentCount += 1; } - } - if (shapePaths[i].c && lengths.length) { - currentLengthData = lengths[j - 1]; - if (addedLength <= shapeSegment.e) { - var segmentLength = lengths[j - 1].addedLength; - if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + segmentLength) { - this.addSegment(shapePaths[i].v[j - 1], shapePaths[i].o[j - 1], shapePaths[i].i[0], shapePaths[i].v[0], shapePath, segmentCount, newShape); - newShape = false; + if (animatorProps.a.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + animatorOffset += animatorProps.a.v[0] * mult[0]; } else { - segment = bez.getNewSegment(shapePaths[i].v[j - 1], shapePaths[i].v[0], shapePaths[i].o[j - 1], shapePaths[i].i[0], (shapeSegment.s - addedLength) / segmentLength, (shapeSegment.e - addedLength) / segmentLength, lengths[j - 1]); - this.addSegmentFromArray(segment, shapePath, segmentCount, newShape); - // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape); - newShape = false; - shapePath.c = false; + animatorOffset += animatorProps.a.v[0] * mult; } - } else { - shapePath.c = false; } - addedLength += currentLengthData.addedLength; - segmentCount += 1; } - if (shapePath._length) { - shapePath.setXYAt(shapePath.v[initPos][0], shapePath.v[initPos][1], 'i', initPos); - shapePath.setXYAt(shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1], 'o', shapePath._length - 1); - } - if (addedLength > shapeSegment.e) { - break; + flag = true; + // Force alignment only works with a single line for now + if (this._pathData.a.v) { + currentLength = letters[0].an * 0.5 + ((totalLength - this._pathData.f.v - letters[0].an * 0.5 - letters[letters.length - 1].an * 0.5) * ind) / (len - 1); + currentLength += this._pathData.f.v; } - if (i < len - 1) { - shapePath = shapePool.newElement(); - newShape = true; - shapes.push(shapePath); - segmentCount = 0; + while (flag) { + if (segmentLength + partialLength >= currentLength + animatorOffset || !points) { + perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength; + xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc; + yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc; + matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, -(alignment[1] * yOff) * 0.01); + flag = false; + } else if (points) { + segmentLength += currentPoint.partialLength; + pointInd += 1; + if (pointInd >= points.length) { + pointInd = 0; + segmentInd += 1; + if (!segments[segmentInd]) { + if (mask.v.c) { + pointInd = 0; + segmentInd = 0; + points = segments[segmentInd].points; + } else { + segmentLength -= currentPoint.partialLength; + points = null; + } + } else { + points = segments[segmentInd].points; + } + } + if (points) { + prevPoint = currentPoint; + currentPoint = points[pointInd]; + partialLength = currentPoint.partialLength; + } + } } - } - return shapes; - }; - - function PuckerAndBloatModifier() {} - extendPrototype([ShapeModifier], PuckerAndBloatModifier); - PuckerAndBloatModifier.prototype.initModifierProperties = function (elem, data) { - this.getValue = this.processKeys; - this.amount = PropertyFactory.getProp(elem, data.a, 0, null, this); - this._isAnimated = !!this.amount.effectsSequence.length; - }; - - PuckerAndBloatModifier.prototype.processPath = function (path, amount) { - var percent = amount / 100; - var centerPoint = [0, 0]; - var pathLength = path._length; - var i = 0; - for (i = 0; i < pathLength; i += 1) { - centerPoint[0] += path.v[i][0]; - centerPoint[1] += path.v[i][1]; - } - centerPoint[0] /= pathLength; - centerPoint[1] /= pathLength; - var clonedPath = shapePool.newElement(); - clonedPath.c = path.c; - var vX; - var vY; - var oX; - var oY; - var iX; - var iY; - for (i = 0; i < pathLength; i += 1) { - vX = path.v[i][0] + (centerPoint[0] - path.v[i][0]) * percent; - vY = path.v[i][1] + (centerPoint[1] - path.v[i][1]) * percent; - oX = path.o[i][0] + (centerPoint[0] - path.o[i][0]) * -percent; - oY = path.o[i][1] + (centerPoint[1] - path.o[i][1]) * -percent; - iX = path.i[i][0] + (centerPoint[0] - path.i[i][0]) * -percent; - iY = path.i[i][1] + (centerPoint[1] - path.i[i][1]) * -percent; - clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, i); - } - return clonedPath; - }; + offf = letters[i].an / 2 - letters[i].add; + matrixHelper.translate(-offf, 0, 0); + } else { + offf = letters[i].an / 2 - letters[i].add; + matrixHelper.translate(-offf, 0, 0); - PuckerAndBloatModifier.prototype.processShapes = function (_isFirstFrame) { - var shapePaths; - var i; - var len = this.shapes.length; - var j; - var jLen; - var amount = this.amount.v; + // Grouping alignment + matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, (-alignment[1] * yOff) * 0.01, 0); + } - if (amount !== 0) { - var shapeData; - var localShapeCollection; - for (i = 0; i < len; i += 1) { - shapeData = this.shapes[i]; - localShapeCollection = shapeData.localShapeCollection; - if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { - localShapeCollection.releaseShapes(); - shapeData.shape._mdf = true; - shapePaths = shapeData.shape.paths.shapes; - jLen = shapeData.shape.paths._length; - for (j = 0; j < jLen; j += 1) { - localShapeCollection.addShape(this.processPath(shapePaths[j], amount)); + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.t.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + // This condition is to prevent applying tracking to first character in each line. Might be better to use a boolean "isNewLine" + if (xPos !== 0 || documentData.j !== 0) { + if (this._hasMaskedPath) { + if (mult.length) { + currentLength += animatorProps.t.v * mult[0]; + } else { + currentLength += animatorProps.t.v * mult; + } + } else if (mult.length) { + xPos += animatorProps.t.v * mult[0]; + } else { + xPos += animatorProps.t.v * mult; } } - shapeData.shape.paths = shapeData.localShapeCollection; } } - if (!this.dynamicProperties.length) { - this._mdf = false; + if (documentData.strokeWidthAnim) { + sw = documentData.sw || 0; } - }; - - const TransformPropertyFactory = (function () { - var defaultVector = [0, 0]; - - function applyToMatrix(mat) { - var _mdf = this._mdf; - this.iterateDynamicProperties(); - this._mdf = this._mdf || _mdf; - if (this.a) { - mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); - } - if (this.s) { - mat.scale(this.s.v[0], this.s.v[1], this.s.v[2]); - } - if (this.sk) { - mat.skewFromAxis(-this.sk.v, this.sa.v); - } - if (this.r) { - mat.rotate(-this.r.v); + if (documentData.strokeColorAnim) { + if (documentData.sc) { + sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]]; } else { - mat.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) - .rotateY(this.or.v[1]) - .rotateX(this.or.v[0]); + sc = [0, 0, 0]; } - if (this.data.p.s) { - if (this.data.p.z) { - mat.translate(this.px.v, this.py.v, -this.pz.v); + } + if (documentData.fillColorAnim && documentData.fc) { + fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]]; + } + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.a.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + + if (mult.length) { + matrixHelper.translate(-animatorProps.a.v[0] * mult[0], -animatorProps.a.v[1] * mult[1], animatorProps.a.v[2] * mult[2]); } else { - mat.translate(this.px.v, this.py.v, 0); + matrixHelper.translate(-animatorProps.a.v[0] * mult, -animatorProps.a.v[1] * mult, animatorProps.a.v[2] * mult); } - } else { - mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); } } - function processKeys(forceRender) { - if (this.elem.globalData.frameId === this.frameId) { - return; + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + if (animatorProps.s.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (mult.length) { + matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult[0]), 1 + ((animatorProps.s.v[1] - 1) * mult[1]), 1); + } else { + matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult), 1 + ((animatorProps.s.v[1] - 1) * mult), 1); + } } - if (this._isDirty) { - this.precalculateMatrix(); - this._isDirty = false; + } + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (animatorProps.sk.propType) { + if (mult.length) { + matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1]); + } else { + matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult); + } } - - this.iterateDynamicProperties(); - - if (this._mdf || forceRender) { - var frameRate; - this.v.cloneFromProps(this.pre.props); - if (this.appliedTransformations < 1) { - this.v.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); + if (animatorProps.r.propType) { + if (mult.length) { + matrixHelper.rotateZ(-animatorProps.r.v * mult[2]); + } else { + matrixHelper.rotateZ(-animatorProps.r.v * mult); } - if (this.appliedTransformations < 2) { - this.v.scale(this.s.v[0], this.s.v[1], this.s.v[2]); + } + if (animatorProps.ry.propType) { + if (mult.length) { + matrixHelper.rotateY(animatorProps.ry.v * mult[1]); + } else { + matrixHelper.rotateY(animatorProps.ry.v * mult); } - if (this.sk && this.appliedTransformations < 3) { - this.v.skewFromAxis(-this.sk.v, this.sa.v); + } + if (animatorProps.rx.propType) { + if (mult.length) { + matrixHelper.rotateX(animatorProps.rx.v * mult[0]); + } else { + matrixHelper.rotateX(animatorProps.rx.v * mult); } - if (this.r && this.appliedTransformations < 4) { - this.v.rotate(-this.r.v); - } else if (!this.r && this.appliedTransformations < 4) { - this.v.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) - .rotateY(this.or.v[1]) - .rotateX(this.or.v[0]); + } + if (animatorProps.o.propType) { + if (mult.length) { + elemOpacity += ((animatorProps.o.v) * mult[0] - elemOpacity) * mult[0]; + } else { + elemOpacity += ((animatorProps.o.v) * mult - elemOpacity) * mult; } - if (this.autoOriented) { - var v1; - var v2; - frameRate = this.elem.globalData.frameRate; - if (this.p && this.p.keyframes && this.p.getValueAtTime) { - if (this.p._caching.lastFrame + this.p.offsetTime <= this.p.keyframes[0].t) { - v1 = this.p.getValueAtTime((this.p.keyframes[0].t + 0.01) / frameRate, 0); - v2 = this.p.getValueAtTime(this.p.keyframes[0].t / frameRate, 0); - } else if (this.p._caching.lastFrame + this.p.offsetTime >= this.p.keyframes[this.p.keyframes.length - 1].t) { - v1 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t / frameRate), 0); - v2 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t - 0.05) / frameRate, 0); - } else { - v1 = this.p.pv; - v2 = this.p.getValueAtTime((this.p._caching.lastFrame + this.p.offsetTime - 0.01) / frameRate, this.p.offsetTime); - } - } else if (this.px && this.px.keyframes && this.py.keyframes && this.px.getValueAtTime && this.py.getValueAtTime) { - v1 = []; - v2 = []; - var px = this.px; - var py = this.py; - if (px._caching.lastFrame + px.offsetTime <= px.keyframes[0].t) { - v1[0] = px.getValueAtTime((px.keyframes[0].t + 0.01) / frameRate, 0); - v1[1] = py.getValueAtTime((py.keyframes[0].t + 0.01) / frameRate, 0); - v2[0] = px.getValueAtTime((px.keyframes[0].t) / frameRate, 0); - v2[1] = py.getValueAtTime((py.keyframes[0].t) / frameRate, 0); - } else if (px._caching.lastFrame + px.offsetTime >= px.keyframes[px.keyframes.length - 1].t) { - v1[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t / frameRate), 0); - v1[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t / frameRate), 0); - v2[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t - 0.01) / frameRate, 0); - v2[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t - 0.01) / frameRate, 0); + } + if (documentData.strokeWidthAnim && animatorProps.sw.propType) { + if (mult.length) { + sw += animatorProps.sw.v * mult[0]; + } else { + sw += animatorProps.sw.v * mult; + } + } + if (documentData.strokeColorAnim && animatorProps.sc.propType) { + for (k = 0; k < 3; k += 1) { + if (mult.length) { + sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult[0]; + } else { + sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult; + } + } + } + if (documentData.fillColorAnim && documentData.fc) { + if (animatorProps.fc.propType) { + for (k = 0; k < 3; k += 1) { + if (mult.length) { + fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult[0]; } else { - v1 = [px.pv, py.pv]; - v2[0] = px.getValueAtTime((px._caching.lastFrame + px.offsetTime - 0.01) / frameRate, px.offsetTime); - v2[1] = py.getValueAtTime((py._caching.lastFrame + py.offsetTime - 0.01) / frameRate, py.offsetTime); + fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult; } + } + } + if (animatorProps.fh.propType) { + if (mult.length) { + fc = addHueToRGB(fc, animatorProps.fh.v * mult[0]); } else { - v2 = defaultVector; - v1 = v2; + fc = addHueToRGB(fc, animatorProps.fh.v * mult); } - this.v.rotate(-Math.atan2(v1[1] - v2[1], v1[0] - v2[0])); } - if (this.data.p && this.data.p.s) { - if (this.data.p.z) { - this.v.translate(this.px.v, this.py.v, -this.pz.v); + if (animatorProps.fs.propType) { + if (mult.length) { + fc = addSaturationToRGB(fc, animatorProps.fs.v * mult[0]); } else { - this.v.translate(this.px.v, this.py.v, 0); + fc = addSaturationToRGB(fc, animatorProps.fs.v * mult); + } + } + if (animatorProps.fb.propType) { + if (mult.length) { + fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult[0]); + } else { + fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult); } - } else { - this.v.translate(this.p.v[0], this.p.v[1], -this.p.v[2]); } } - this.frameId = this.elem.globalData.frameId; } - function precalculateMatrix() { - if (!this.a.k) { - this.pre.translate(-this.a.v[0], -this.a.v[1], this.a.v[2]); - this.appliedTransformations = 1; - } else { - return; - } - if (!this.s.effectsSequence.length) { - this.pre.scale(this.s.v[0], this.s.v[1], this.s.v[2]); - this.appliedTransformations = 2; - } else { - return; - } - if (this.sk) { - if (!this.sk.effectsSequence.length && !this.sa.effectsSequence.length) { - this.pre.skewFromAxis(-this.sk.v, this.sa.v); - this.appliedTransformations = 3; + for (j = 0; j < jLen; j += 1) { + animatorProps = animators[j].a; + + if (animatorProps.p.propType) { + animatorSelector = animators[j].s; + mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); + if (this._hasMaskedPath) { + if (mult.length) { + matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1]); + } else { + matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); + } + } else if (mult.length) { + matrixHelper.translate(animatorProps.p.v[0] * mult[0], animatorProps.p.v[1] * mult[1], -animatorProps.p.v[2] * mult[2]); } else { - return; - } - } - if (this.r) { - if (!this.r.effectsSequence.length) { - this.pre.rotate(-this.r.v); - this.appliedTransformations = 4; - } - } else if (!this.rz.effectsSequence.length && !this.ry.effectsSequence.length && !this.rx.effectsSequence.length && !this.or.effectsSequence.length) { - this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]) - .rotateY(this.or.v[1]) - .rotateX(this.or.v[0]); - this.appliedTransformations = 4; - } - } - - function autoOrient() { - // - // var prevP = this.getValueAtTime(); - } - - function addDynamicProperty(prop) { - this._addDynamicProperty(prop); - this.elem.addDynamicProperty(prop); - this._isDirty = true; - } - - function TransformProperty(elem, data, container) { - this.elem = elem; - this.frameId = -1; - this.propType = 'transform'; - this.data = data; - this.v = new Matrix(); - // Precalculated matrix with non animated properties - this.pre = new Matrix(); - this.appliedTransformations = 0; - this.initDynamicPropertyContainer(container || elem); - if (data.p && data.p.s) { - this.px = PropertyFactory.getProp(elem, data.p.x, 0, 0, this); - this.py = PropertyFactory.getProp(elem, data.p.y, 0, 0, this); - if (data.p.z) { - this.pz = PropertyFactory.getProp(elem, data.p.z, 0, 0, this); + matrixHelper.translate(animatorProps.p.v[0] * mult, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); } - } else { - this.p = PropertyFactory.getProp(elem, data.p || { k: [0, 0, 0] }, 1, 0, this); } - if (data.rx) { - this.rx = PropertyFactory.getProp(elem, data.rx, 0, degToRads, this); - this.ry = PropertyFactory.getProp(elem, data.ry, 0, degToRads, this); - this.rz = PropertyFactory.getProp(elem, data.rz, 0, degToRads, this); - if (data.or.k[0].ti) { - var i; - var len = data.or.k.length; - for (i = 0; i < len; i += 1) { - data.or.k[i].to = null; - data.or.k[i].ti = null; - } - } - this.or = PropertyFactory.getProp(elem, data.or, 1, degToRads, this); - // sh Indicates it needs to be capped between -180 and 180 - this.or.sh = true; - } else { - this.r = PropertyFactory.getProp(elem, data.r || { k: 0 }, 0, degToRads, this); - } - if (data.sk) { - this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this); - this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this); - } - this.a = PropertyFactory.getProp(elem, data.a || { k: [0, 0, 0] }, 1, 0, this); - this.s = PropertyFactory.getProp(elem, data.s || { k: [100, 100, 100] }, 1, 0.01, this); - // Opacity is not part of the transform properties, that's why it won't use this.dynamicProperties. That way transforms won't get updated if opacity changes. - if (data.o) { - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, elem); - } else { - this.o = { _mdf: false, v: 1 }; - } - this._isDirty = true; - if (!this.dynamicProperties.length) { - this.getValue(true); - } - } - - TransformProperty.prototype = { - applyToMatrix: applyToMatrix, - getValue: processKeys, - precalculateMatrix: precalculateMatrix, - autoOrient: autoOrient, - }; - - extendPrototype([DynamicPropertyContainer], TransformProperty); - TransformProperty.prototype.addDynamicProperty = addDynamicProperty; - TransformProperty.prototype._addDynamicProperty = DynamicPropertyContainer.prototype.addDynamicProperty; - - function getTransformProperty(elem, data, container) { - return new TransformProperty(elem, data, container); - } - - return { - getTransformProperty: getTransformProperty, - }; - }()); - - function RepeaterModifier() {} - extendPrototype([ShapeModifier], RepeaterModifier); - - RepeaterModifier.prototype.initModifierProperties = function (elem, data) { - this.getValue = this.processKeys; - this.c = PropertyFactory.getProp(elem, data.c, 0, null, this); - this.o = PropertyFactory.getProp(elem, data.o, 0, null, this); - this.tr = TransformPropertyFactory.getTransformProperty(elem, data.tr, this); - this.so = PropertyFactory.getProp(elem, data.tr.so, 0, 0.01, this); - this.eo = PropertyFactory.getProp(elem, data.tr.eo, 0, 0.01, this); - this.data = data; - if (!this.dynamicProperties.length) { - this.getValue(true); - } - this._isAnimated = !!this.dynamicProperties.length; - this.pMatrix = new Matrix(); - this.rMatrix = new Matrix(); - this.sMatrix = new Matrix(); - this.tMatrix = new Matrix(); - this.matrix = new Matrix(); - }; - - RepeaterModifier.prototype.applyTransforms = function (pMatrix, rMatrix, sMatrix, transform, perc, inv) { - var dir = inv ? -1 : 1; - var scaleX = transform.s.v[0] + (1 - transform.s.v[0]) * (1 - perc); - var scaleY = transform.s.v[1] + (1 - transform.s.v[1]) * (1 - perc); - pMatrix.translate(transform.p.v[0] * dir * perc, transform.p.v[1] * dir * perc, transform.p.v[2]); - rMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); - rMatrix.rotate(-transform.r.v * dir * perc); - rMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); - sMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2]); - sMatrix.scale(inv ? 1 / scaleX : scaleX, inv ? 1 / scaleY : scaleY); - sMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2]); - }; - - RepeaterModifier.prototype.init = function (elem, arr, pos, elemsData) { - this.elem = elem; - this.arr = arr; - this.pos = pos; - this.elemsData = elemsData; - this._currentCopies = 0; - this._elements = []; - this._groups = []; - this.frameId = -1; - this.initDynamicPropertyContainer(elem); - this.initModifierProperties(elem, arr[pos]); - while (pos > 0) { - pos -= 1; - // this._elements.unshift(arr.splice(pos,1)[0]); - this._elements.unshift(arr[pos]); } - if (this.dynamicProperties.length) { - this.k = true; - } else { - this.getValue(true); + if (documentData.strokeWidthAnim) { + letterSw = sw < 0 ? 0 : sw; } - }; - - RepeaterModifier.prototype.resetElements = function (elements) { - var i; - var len = elements.length; - for (i = 0; i < len; i += 1) { - elements[i]._processed = false; - if (elements[i].ty === 'gr') { - this.resetElements(elements[i].it); - } + if (documentData.strokeColorAnim) { + letterSc = 'rgb(' + Math.round(sc[0] * 255) + ',' + Math.round(sc[1] * 255) + ',' + Math.round(sc[2] * 255) + ')'; } - }; - - RepeaterModifier.prototype.cloneElements = function (elements) { - var newElements = JSON.parse(JSON.stringify(elements)); - this.resetElements(newElements); - return newElements; - }; - - RepeaterModifier.prototype.changeGroupRender = function (elements, renderFlag) { - var i; - var len = elements.length; - for (i = 0; i < len; i += 1) { - elements[i]._render = renderFlag; - if (elements[i].ty === 'gr') { - this.changeGroupRender(elements[i].it, renderFlag); - } + if (documentData.fillColorAnim && documentData.fc) { + letterFc = 'rgb(' + Math.round(fc[0] * 255) + ',' + Math.round(fc[1] * 255) + ',' + Math.round(fc[2] * 255) + ')'; } - }; - - RepeaterModifier.prototype.processShapes = function (_isFirstFrame) { - var items; - var itemsTransform; - var i; - var dir; - var cont; - var hasReloaded = false; - if (this._mdf || _isFirstFrame) { - var copies = Math.ceil(this.c.v); - if (this._groups.length < copies) { - while (this._groups.length < copies) { - var group = { - it: this.cloneElements(this._elements), - ty: 'gr', - }; - group.it.push({ - a: { a: 0, ix: 1, k: [0, 0] }, nm: 'Transform', o: { a: 0, ix: 7, k: 100 }, p: { a: 0, ix: 2, k: [0, 0] }, r: { a: 1, ix: 6, k: [{ s: 0, e: 0, t: 0 }, { s: 0, e: 0, t: 1 }] }, s: { a: 0, ix: 3, k: [100, 100] }, sa: { a: 0, ix: 5, k: 0 }, sk: { a: 0, ix: 4, k: 0 }, ty: 'tr', - }); - - this.arr.splice(0, 0, group); - this._groups.splice(0, 0, group); - this._currentCopies += 1; - } - this.elem.reloadShapes(); - hasReloaded = true; - } - cont = 0; - var renderFlag; - for (i = 0; i <= this._groups.length - 1; i += 1) { - renderFlag = cont < copies; - this._groups[i]._render = renderFlag; - this.changeGroupRender(this._groups[i].it, renderFlag); - if (!renderFlag) { - var elems = this.elemsData[i].it; - var transformData = elems[elems.length - 1]; - if (transformData.transform.op.v !== 0) { - transformData.transform.op._mdf = true; - transformData.transform.op.v = 0; - } else { - transformData.transform.op._mdf = false; - } - } - cont += 1; - } - this._currentCopies = copies; - /// / - - var offset = this.o.v; - var offsetModulo = offset % 1; - var roundOffset = offset > 0 ? Math.floor(offset) : Math.ceil(offset); - var pProps = this.pMatrix.props; - var rProps = this.rMatrix.props; - var sProps = this.sMatrix.props; - this.pMatrix.reset(); - this.rMatrix.reset(); - this.sMatrix.reset(); - this.tMatrix.reset(); - this.matrix.reset(); - var iteration = 0; + if (this._hasMaskedPath) { + matrixHelper.translate(0, -documentData.ls); - if (offset > 0) { - while (iteration < roundOffset) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); - iteration += 1; - } - if (offsetModulo) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, offsetModulo, false); - iteration += offsetModulo; - } - } else if (offset < 0) { - while (iteration > roundOffset) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, true); - iteration -= 1; - } - if (offsetModulo) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, -offsetModulo, true); - iteration -= offsetModulo; + matrixHelper.translate(0, (alignment[1] * yOff) * 0.01 + yPos, 0); + if (this._pathData.p.v) { + tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0]); + var rot = (Math.atan(tanAngle) * 180) / Math.PI; + if (currentPoint.point[0] < prevPoint.point[0]) { + rot += 180; } + matrixHelper.rotate((-rot * Math.PI) / 180); } - i = this.data.m === 1 ? 0 : this._currentCopies - 1; - dir = this.data.m === 1 ? 1 : -1; - cont = this._currentCopies; - var j; - var jLen; - while (cont) { - items = this.elemsData[i].it; - itemsTransform = items[items.length - 1].transform.mProps.v.props; - jLen = itemsTransform.length; - items[items.length - 1].transform.mProps._mdf = true; - items[items.length - 1].transform.op._mdf = true; - items[items.length - 1].transform.op.v = this._currentCopies === 1 - ? this.so.v - : this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1)); - - if (iteration !== 0) { - if ((i !== 0 && dir === 1) || (i !== this._currentCopies - 1 && dir === -1)) { - this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false); - } - this.matrix.transform(rProps[0], rProps[1], rProps[2], rProps[3], rProps[4], rProps[5], rProps[6], rProps[7], rProps[8], rProps[9], rProps[10], rProps[11], rProps[12], rProps[13], rProps[14], rProps[15]); - this.matrix.transform(sProps[0], sProps[1], sProps[2], sProps[3], sProps[4], sProps[5], sProps[6], sProps[7], sProps[8], sProps[9], sProps[10], sProps[11], sProps[12], sProps[13], sProps[14], sProps[15]); - this.matrix.transform(pProps[0], pProps[1], pProps[2], pProps[3], pProps[4], pProps[5], pProps[6], pProps[7], pProps[8], pProps[9], pProps[10], pProps[11], pProps[12], pProps[13], pProps[14], pProps[15]); - - for (j = 0; j < jLen; j += 1) { - itemsTransform[j] = this.matrix.props[j]; - } - this.matrix.reset(); - } else { - this.matrix.reset(); - for (j = 0; j < jLen; j += 1) { - itemsTransform[j] = this.matrix.props[j]; - } - } - iteration += 1; - cont -= 1; - i += dir; + matrixHelper.translate(xPathPos, yPathPos, 0); + currentLength -= (alignment[0] * letters[i].an) * 0.005; + if (letters[i + 1] && ind !== letters[i + 1].ind) { + currentLength += letters[i].an / 2; + currentLength += (documentData.tr * 0.001) * documentData.finalSize; } } else { - cont = this._currentCopies; - i = 0; - dir = 1; - while (cont) { - items = this.elemsData[i].it; - itemsTransform = items[items.length - 1].transform.mProps.v.props; - items[items.length - 1].transform.mProps._mdf = false; - items[items.length - 1].transform.op._mdf = false; - cont -= 1; - i += dir; - } - } - return hasReloaded; - }; - - RepeaterModifier.prototype.addShape = function () {}; - - function RoundCornersModifier() {} - extendPrototype([ShapeModifier], RoundCornersModifier); - RoundCornersModifier.prototype.initModifierProperties = function (elem, data) { - this.getValue = this.processKeys; - this.rd = PropertyFactory.getProp(elem, data.r, 0, null, this); - this._isAnimated = !!this.rd.effectsSequence.length; - }; - - RoundCornersModifier.prototype.processPath = function (path, round) { - var clonedPath = shapePool.newElement(); - clonedPath.c = path.c; - var i; - var len = path._length; - var currentV; - var currentI; - var currentO; - var closerV; - var distance; - var newPosPerc; - var index = 0; - var vX; - var vY; - var oX; - var oY; - var iX; - var iY; - for (i = 0; i < len; i += 1) { - currentV = path.v[i]; - currentO = path.o[i]; - currentI = path.i[i]; - if (currentV[0] === currentO[0] && currentV[1] === currentO[1] && currentV[0] === currentI[0] && currentV[1] === currentI[1]) { - if ((i === 0 || i === len - 1) && !path.c) { - clonedPath.setTripleAt(currentV[0], currentV[1], currentO[0], currentO[1], currentI[0], currentI[1], index); - /* clonedPath.v[index] = currentV; - clonedPath.o[index] = currentO; - clonedPath.i[index] = currentI; */ - index += 1; - } else { - if (i === 0) { - closerV = path.v[len - 1]; - } else { - closerV = path.v[i - 1]; - } - distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); - newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; - iX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; - vX = iX; - iY = currentV[1] - (currentV[1] - closerV[1]) * newPosPerc; - vY = iY; - oX = vX - (vX - currentV[0]) * roundCorner; - oY = vY - (vY - currentV[1]) * roundCorner; - clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); - index += 1; - - if (i === len - 1) { - closerV = path.v[0]; - } else { - closerV = path.v[i + 1]; - } - distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2)); - newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0; - oX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc; - vX = oX; - oY = currentV[1] + (closerV[1] - currentV[1]) * newPosPerc; - vY = oY; - iX = vX - (vX - currentV[0]) * roundCorner; - iY = vY - (vY - currentV[1]) * roundCorner; - clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index); - index += 1; - } - } else { - clonedPath.setTripleAt(path.v[i][0], path.v[i][1], path.o[i][0], path.o[i][1], path.i[i][0], path.i[i][1], index); - index += 1; - } - } - return clonedPath; - }; - - RoundCornersModifier.prototype.processShapes = function (_isFirstFrame) { - var shapePaths; - var i; - var len = this.shapes.length; - var j; - var jLen; - var rd = this.rd.v; + matrixHelper.translate(xPos, yPos, 0); - if (rd !== 0) { - var shapeData; - var localShapeCollection; - for (i = 0; i < len; i += 1) { - shapeData = this.shapes[i]; - localShapeCollection = shapeData.localShapeCollection; - if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) { - localShapeCollection.releaseShapes(); - shapeData.shape._mdf = true; - shapePaths = shapeData.shape.paths.shapes; - jLen = shapeData.shape.paths._length; - for (j = 0; j < jLen; j += 1) { - localShapeCollection.addShape(this.processPath(shapePaths[j], rd)); - } - } - shapeData.shape.paths = shapeData.localShapeCollection; + if (documentData.ps) { + // matrixHelper.translate(documentData.ps[0],documentData.ps[1],0); + matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); } - } - if (!this.dynamicProperties.length) { - this._mdf = false; - } - }; - - function getFontProperties(fontData) { - var styles = fontData.fStyle ? fontData.fStyle.split(' ') : []; - - var fWeight = 'normal'; var - fStyle = 'normal'; - var len = styles.length; - var styleName; - for (var i = 0; i < len; i += 1) { - styleName = styles[i].toLowerCase(); - switch (styleName) { - case 'italic': - fStyle = 'italic'; - break; - case 'bold': - fWeight = '700'; - break; - case 'black': - fWeight = '900'; - break; - case 'medium': - fWeight = '500'; - break; - case 'regular': - case 'normal': - fWeight = '400'; + switch (documentData.j) { + case 1: + matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]), 0, 0); break; - case 'light': - case 'thin': - fWeight = '200'; + case 2: + matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]) / 2, 0, 0); break; default: break; } + matrixHelper.translate(0, -documentData.ls); + matrixHelper.translate(offf, 0, 0); + matrixHelper.translate((alignment[0] * letters[i].an) * 0.005, (alignment[1] * yOff) * 0.01, 0); + xPos += letters[i].l + (documentData.tr * 0.001) * documentData.finalSize; } - - return { - style: fStyle, - weight: fontData.fWeight || fWeight, - }; + if (renderType === 'html') { + letterM = matrixHelper.toCSS(); + } else if (renderType === 'svg') { + letterM = matrixHelper.to2dCSS(); + } else { + letterP = [matrixHelper.props[0], matrixHelper.props[1], matrixHelper.props[2], matrixHelper.props[3], matrixHelper.props[4], matrixHelper.props[5], matrixHelper.props[6], matrixHelper.props[7], matrixHelper.props[8], matrixHelper.props[9], matrixHelper.props[10], matrixHelper.props[11], matrixHelper.props[12], matrixHelper.props[13], matrixHelper.props[14], matrixHelper.props[15]]; + } + letterO = elemOpacity; } - const FontManager = (function () { - var maxWaitingTime = 5000; - var emptyChar = { - w: 0, - size: 0, - shapes: [], - data: { - shapes: [], - }, - }; - var combinedCharacters = []; - // Hindi characters - combinedCharacters = combinedCharacters.concat([2304, 2305, 2306, 2307, 2362, 2363, 2364, 2364, 2366, - 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, - 2380, 2381, 2382, 2383, 2387, 2388, 2389, 2390, 2391, 2402, 2403]); - - var surrogateModifiers = [ - 'd83cdffb', - 'd83cdffc', - 'd83cdffd', - 'd83cdffe', - 'd83cdfff', - ]; - - var zeroWidthJoiner = [65039, 8205]; - - function trimFontOptions(font) { - var familyArray = font.split(','); - var i; - var len = familyArray.length; - var enabledFamilies = []; - for (i = 0; i < len; i += 1) { - if (familyArray[i] !== 'sans-serif' && familyArray[i] !== 'monospace') { - enabledFamilies.push(familyArray[i]); - } - } - return enabledFamilies.join(','); - } - - function setUpNode(font, family) { - var parentNode = createTag('span'); - // Node is invisible to screen readers. - parentNode.setAttribute('aria-hidden', true); - parentNode.style.fontFamily = family; - var node = createTag('span'); - // Characters that vary significantly among different fonts - node.innerText = 'giItT1WQy@!-/#'; - // Visible - so we can measure it - but not on the screen - parentNode.style.position = 'absolute'; - parentNode.style.left = '-10000px'; - parentNode.style.top = '-10000px'; - // Large font size makes even subtle changes obvious - parentNode.style.fontSize = '300px'; - // Reset any font properties - parentNode.style.fontVariant = 'normal'; - parentNode.style.fontStyle = 'normal'; - parentNode.style.fontWeight = 'normal'; - parentNode.style.letterSpacing = '0'; - parentNode.appendChild(node); - document.body.appendChild(parentNode); - - // Remember width with no applied web font - var width = node.offsetWidth; - node.style.fontFamily = trimFontOptions(font) + ', ' + family; - return { node: node, w: width, parent: parentNode }; - } - - function checkLoadedFonts() { - var i; - var len = this.fonts.length; - var node; - var w; - var loadedCount = len; - for (i = 0; i < len; i += 1) { - if (this.fonts[i].loaded) { - loadedCount -= 1; - } else if (this.fonts[i].fOrigin === 'n' || this.fonts[i].origin === 0) { - this.fonts[i].loaded = true; - } else { - node = this.fonts[i].monoCase.node; - w = this.fonts[i].monoCase.w; - if (node.offsetWidth !== w) { - loadedCount -= 1; - this.fonts[i].loaded = true; - } else { - node = this.fonts[i].sansCase.node; - w = this.fonts[i].sansCase.w; - if (node.offsetWidth !== w) { - loadedCount -= 1; - this.fonts[i].loaded = true; - } - } - if (this.fonts[i].loaded) { - this.fonts[i].sansCase.parent.parentNode.removeChild(this.fonts[i].sansCase.parent); - this.fonts[i].monoCase.parent.parentNode.removeChild(this.fonts[i].monoCase.parent); - } - } - } + if (renderedLettersCount <= i) { + letterValue = new LetterProps(letterO, letterSw, letterSc, letterFc, letterM, letterP); + this.renderedLetters.push(letterValue); + renderedLettersCount += 1; + this.lettersChangedFlag = true; + } else { + letterValue = this.renderedLetters[i]; + this.lettersChangedFlag = letterValue.update(letterO, letterSw, letterSc, letterFc, letterM, letterP) || this.lettersChangedFlag; + } + } +}; + +TextAnimatorProperty.prototype.getValue = function () { + if (this._elem.globalData.frameId === this._frameId) { + return; + } + this._frameId = this._elem.globalData.frameId; + this.iterateDynamicProperties(); +}; + +TextAnimatorProperty.prototype.mHelper = new Matrix(); +TextAnimatorProperty.prototype.defaultPropsArray = []; +extendPrototype([DynamicPropertyContainer], TextAnimatorProperty); + +function ITextElement() { +} + +ITextElement.prototype.initElement = function (data, globalData, comp) { + this.lettersChangedFlag = true; + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.textProperty = new TextProperty(this, data.t, this.dynamicProperties); + this.textAnimator = new TextAnimatorProperty(data.t, this.renderType, this); + this.initTransform(data, globalData, comp); + this.initHierarchy(); + this.initRenderable(); + this.initRendererElement(); + this.createContainerElements(); + this.createRenderableComponents(); + this.createContent(); + this.hide(); + this.textAnimator.searchProperties(this.dynamicProperties); +}; + +ITextElement.prototype.prepareFrame = function (num) { + this._mdf = false; + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + if (this.textProperty._mdf || this.textProperty._isFirstFrame) { + this.buildNewText(); + this.textProperty._isFirstFrame = false; + this.textProperty._mdf = false; + } +}; + +ITextElement.prototype.createPathShape = function (matrixHelper, shapes) { + var j; + var jLen = shapes.length; + var pathNodes; + var shapeStr = ''; + for (j = 0; j < jLen; j += 1) { + if (shapes[j].ty === 'sh') { + pathNodes = shapes[j].ks.k; + shapeStr += buildShapeString(pathNodes, pathNodes.i.length, true, matrixHelper); + } + } + return shapeStr; +}; + +ITextElement.prototype.updateDocumentData = function (newData, index) { + this.textProperty.updateDocumentData(newData, index); +}; + +ITextElement.prototype.canResizeFont = function (_canResize) { + this.textProperty.canResizeFont(_canResize); +}; + +ITextElement.prototype.setMinimumFontSize = function (_fontSize) { + this.textProperty.setMinimumFontSize(_fontSize); +}; + +ITextElement.prototype.applyTextPropertiesToMatrix = function (documentData, matrixHelper, lineNumber, xPos, yPos) { + if (documentData.ps) { + matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); + } + matrixHelper.translate(0, -documentData.ls, 0); + switch (documentData.j) { + case 1: + matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]), 0, 0); + break; + case 2: + matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]) / 2, 0, 0); + break; + default: + break; + } + matrixHelper.translate(xPos, yPos, 0); +}; + +ITextElement.prototype.buildColor = function (colorData) { + return 'rgb(' + Math.round(colorData[0] * 255) + ',' + Math.round(colorData[1] * 255) + ',' + Math.round(colorData[2] * 255) + ')'; +}; + +ITextElement.prototype.emptyProp = new LetterProps(); + +ITextElement.prototype.destroy = function () { + +}; + +var emptyShapeData = { + shapes: [], +}; + +function SVGTextLottieElement(data, globalData, comp) { + this.textSpans = []; + this.renderType = 'svg'; + this.initElement(data, globalData, comp); +} + +extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement, ITextElement], SVGTextLottieElement); + +SVGTextLottieElement.prototype.createContent = function () { + if (this.data.singleShape && !this.globalData.fontManager.chars) { + this.textContainer = createNS('text'); + } +}; + +SVGTextLottieElement.prototype.buildTextContents = function (textArray) { + var i = 0; + var len = textArray.length; + var textContents = []; + var currentTextContent = ''; + while (i < len) { + if (textArray[i] === String.fromCharCode(13) || textArray[i] === String.fromCharCode(3)) { + textContents.push(currentTextContent); + currentTextContent = ''; + } else { + currentTextContent += textArray[i]; + } + i += 1; + } + textContents.push(currentTextContent); + return textContents; +}; + +SVGTextLottieElement.prototype.buildShapeData = function (data, scale) { + // data should probably be cloned to apply scale separately to each instance of a text on different layers + // but since text internal content gets only rendered once and then it's never rerendered, + // it's probably safe not to clone data and reuse always the same instance even if the object is mutated. + // Avoiding cloning is preferred since cloning each character shape data is expensive + if (data.shapes && data.shapes.length) { + var shape = data.shapes[0]; + if (shape.it) { + var shapeItem = shape.it[shape.it.length - 1]; + if (shapeItem.s) { + shapeItem.s.k[0] = scale; + shapeItem.s.k[1] = scale; + } + } + } + return data; +}; + +SVGTextLottieElement.prototype.buildNewText = function () { + this.addDynamicProperty(this); + var i; + var len; + + var documentData = this.textProperty.currentData; + this.renderedLetters = createSizedArray(documentData ? documentData.l.length : 0); + if (documentData.fc) { + this.layerElement.setAttribute('fill', this.buildColor(documentData.fc)); + } else { + this.layerElement.setAttribute('fill', 'rgba(0,0,0,0)'); + } + if (documentData.sc) { + this.layerElement.setAttribute('stroke', this.buildColor(documentData.sc)); + this.layerElement.setAttribute('stroke-width', documentData.sw); + } + this.layerElement.setAttribute('font-size', documentData.finalSize); + var fontData = this.globalData.fontManager.getFontByName(documentData.f); + if (fontData.fClass) { + this.layerElement.setAttribute('class', fontData.fClass); + } else { + this.layerElement.setAttribute('font-family', fontData.fFamily); + var fWeight = documentData.fWeight; + var fStyle = documentData.fStyle; + this.layerElement.setAttribute('font-style', fStyle); + this.layerElement.setAttribute('font-weight', fWeight); + } + this.layerElement.setAttribute('aria-label', documentData.t); + + var letters = documentData.l || []; + var usesGlyphs = !!this.globalData.fontManager.chars; + len = letters.length; + + var tSpan; + var matrixHelper = this.mHelper; + var shapeStr = ''; + var singleShape = this.data.singleShape; + var xPos = 0; + var yPos = 0; + var firstLine = true; + var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; + if (singleShape && !usesGlyphs && !documentData.sz) { + var tElement = this.textContainer; + var justify = 'start'; + switch (documentData.j) { + case 1: + justify = 'end'; + break; + case 2: + justify = 'middle'; + break; + default: + justify = 'start'; + break; + } + tElement.setAttribute('text-anchor', justify); + tElement.setAttribute('letter-spacing', trackingOffset); + var textContent = this.buildTextContents(documentData.finalText); + len = textContent.length; + yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0; + for (i = 0; i < len; i += 1) { + tSpan = this.textSpans[i].span || createNS('tspan'); + tSpan.textContent = textContent[i]; + tSpan.setAttribute('x', 0); + tSpan.setAttribute('y', yPos); + tSpan.style.display = 'inherit'; + tElement.appendChild(tSpan); + if (!this.textSpans[i]) { + this.textSpans[i] = { + span: null, + glyph: null, + }; + } + this.textSpans[i].span = tSpan; + yPos += documentData.finalLineHeight; + } - if (loadedCount !== 0 && Date.now() - this.initTime < maxWaitingTime) { - setTimeout(this.checkLoadedFontsBinded, 20); - } else { - setTimeout(this.setIsLoadedBinded, 10); - } - } - - function createHelper(fontData, def) { - var engine = (document.body && def) ? 'svg' : 'canvas'; - var helper; - var fontProps = getFontProperties(fontData); - if (engine === 'svg') { - var tHelper = createNS('text'); - tHelper.style.fontSize = '100px'; - // tHelper.style.fontFamily = fontData.fFamily; - tHelper.setAttribute('font-family', fontData.fFamily); - tHelper.setAttribute('font-style', fontProps.style); - tHelper.setAttribute('font-weight', fontProps.weight); - tHelper.textContent = '1'; - if (fontData.fClass) { - tHelper.style.fontFamily = 'inherit'; - tHelper.setAttribute('class', fontData.fClass); - } else { - tHelper.style.fontFamily = fontData.fFamily; - } - def.appendChild(tHelper); - helper = tHelper; - } else { - var tCanvasHelper = new OffscreenCanvas(500, 500).getContext('2d'); - tCanvasHelper.font = fontProps.style + ' ' + fontProps.weight + ' 100px ' + fontData.fFamily; - helper = tCanvasHelper; - } - function measure(text) { - if (engine === 'svg') { - helper.textContent = text; - return helper.getComputedTextLength(); + this.layerElement.appendChild(tElement); + } else { + var cachedSpansLength = this.textSpans.length; + var charData; + for (i = 0; i < len; i += 1) { + if (!this.textSpans[i]) { + this.textSpans[i] = { + span: null, + childSpan: null, + glyph: null, + }; + } + if (!usesGlyphs || !singleShape || i === 0) { + tSpan = cachedSpansLength > i ? this.textSpans[i].span : createNS(usesGlyphs ? 'g' : 'text'); + if (cachedSpansLength <= i) { + tSpan.setAttribute('stroke-linecap', 'butt'); + tSpan.setAttribute('stroke-linejoin', 'round'); + tSpan.setAttribute('stroke-miterlimit', '4'); + this.textSpans[i].span = tSpan; + if (usesGlyphs) { + var childSpan = createNS('g'); + tSpan.appendChild(childSpan); + this.textSpans[i].childSpan = childSpan; } - return helper.measureText(text).width; + this.textSpans[i].span = tSpan; + this.layerElement.appendChild(tSpan); } - return { - measureText: measure, - }; + tSpan.style.display = 'inherit'; } - function addFonts(fontData, defs) { - if (!fontData) { - this.isLoaded = true; - return; + matrixHelper.reset(); + if (singleShape) { + if (letters[i].n) { + xPos = -trackingOffset; + yPos += documentData.yOffset; + yPos += firstLine ? 1 : 0; + firstLine = false; } - if (this.chars) { - this.isLoaded = true; - this.fonts = fontData.list; - return; + this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); + xPos += letters[i].l || 0; + // xPos += letters[i].val === ' ' ? 0 : trackingOffset; + xPos += trackingOffset; + } + if (usesGlyphs) { + charData = this.globalData.fontManager.getCharData( + documentData.finalText[i], + fontData.fStyle, + this.globalData.fontManager.getFontByName(documentData.f).fFamily + ); + var glyphElement; + // t === 1 means the character has been replaced with an animated shaped + if (charData.t === 1) { + glyphElement = new SVGCompElement(charData.data, this.globalData, this); + } else { + var data = emptyShapeData; + if (charData.data && charData.data.shapes) { + data = this.buildShapeData(charData.data, documentData.finalSize); + } + glyphElement = new SVGShapeElement(data, this.globalData, this); + } + if (this.textSpans[i].glyph) { + var glyph = this.textSpans[i].glyph; + this.textSpans[i].childSpan.removeChild(glyph.layerElement); + glyph.destroy(); + } + this.textSpans[i].glyph = glyphElement; + glyphElement._debug = true; + glyphElement.prepareFrame(0); + glyphElement.renderFrame(); + this.textSpans[i].childSpan.appendChild(glyphElement.layerElement); + // when using animated shapes, the layer will be scaled instead of replacing the internal scale + // this might have issues with strokes and might need a different solution + if (charData.t === 1) { + this.textSpans[i].childSpan.setAttribute('transform', 'scale(' + documentData.finalSize / 100 + ',' + documentData.finalSize / 100 + ')'); } - if (!document.body) { - this.isLoaded = true; - fontData.list.forEach((data) => { - data.helper = createHelper(data); - data.cache = {}; - }); - this.fonts = fontData.list; - return; + } else { + if (singleShape) { + tSpan.setAttribute('transform', 'translate(' + matrixHelper.props[12] + ',' + matrixHelper.props[13] + ')'); } + tSpan.textContent = letters[i].val; + tSpan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + } + // + } + if (singleShape && tSpan) { + tSpan.setAttribute('d', shapeStr); + } + } + while (i < this.textSpans.length) { + this.textSpans[i].span.style.display = 'none'; + i += 1; + } + + this._sizeChanged = true; +}; + +SVGTextLottieElement.prototype.sourceRectAtTime = function () { + this.prepareFrame(this.comp.renderedFrame - this.data.st); + this.renderInnerContent(); + if (this._sizeChanged) { + this._sizeChanged = false; + var textBox = this.layerElement.getBBox(); + this.bbox = { + top: textBox.y, + left: textBox.x, + width: textBox.width, + height: textBox.height, + }; + } + return this.bbox; +}; + +SVGTextLottieElement.prototype.getValue = function () { + var i; + var len = this.textSpans.length; + var glyphElement; + this.renderedFrame = this.comp.renderedFrame; + for (i = 0; i < len; i += 1) { + glyphElement = this.textSpans[i].glyph; + if (glyphElement) { + glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st); + if (glyphElement._mdf) { + this._mdf = true; + } + } + } +}; - var fontArr = fontData.list; - var i; - var len = fontArr.length; - var _pendingFonts = len; - for (i = 0; i < len; i += 1) { - var shouldLoadFont = true; - var loadedSelector; - var j; - fontArr[i].loaded = false; - fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace'); - fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif'); - if (!fontArr[i].fPath) { - fontArr[i].loaded = true; - _pendingFonts -= 1; - } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) { - loadedSelector = document.querySelectorAll('style[f-forigin="p"][f-family="' + fontArr[i].fFamily + '"], style[f-origin="3"][f-family="' + fontArr[i].fFamily + '"]'); - - if (loadedSelector.length > 0) { - shouldLoadFont = false; - } - - if (shouldLoadFont) { - var s = createTag('style'); - s.setAttribute('f-forigin', fontArr[i].fOrigin); - s.setAttribute('f-origin', fontArr[i].origin); - s.setAttribute('f-family', fontArr[i].fFamily); - s.type = 'text/css'; - s.innerText = '@font-face {font-family: ' + fontArr[i].fFamily + "; font-style: normal; src: url('" + fontArr[i].fPath + "');}"; - defs.appendChild(s); - } - } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) { - loadedSelector = document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'); - - for (j = 0; j < loadedSelector.length; j += 1) { - if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) { - // Font is already loaded - shouldLoadFont = false; - } - } - - if (shouldLoadFont) { - var l = createTag('link'); - l.setAttribute('f-forigin', fontArr[i].fOrigin); - l.setAttribute('f-origin', fontArr[i].origin); - l.type = 'text/css'; - l.rel = 'stylesheet'; - l.href = fontArr[i].fPath; - document.body.appendChild(l); - } - } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) { - loadedSelector = document.querySelectorAll('script[f-forigin="t"], script[f-origin="2"]'); +SVGTextLottieElement.prototype.renderInnerContent = function () { + if (!this.data.singleShape || this._mdf) { + this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); + if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) { + this._sizeChanged = true; + var i; + var len; + var renderedLetters = this.textAnimator.renderedLetters; - for (j = 0; j < loadedSelector.length; j += 1) { - if (fontArr[i].fPath === loadedSelector[j].src) { - // Font is already loaded - shouldLoadFont = false; - } - } + var letters = this.textProperty.currentData.l; - if (shouldLoadFont) { - var sc = createTag('link'); - sc.setAttribute('f-forigin', fontArr[i].fOrigin); - sc.setAttribute('f-origin', fontArr[i].origin); - sc.setAttribute('rel', 'stylesheet'); - sc.setAttribute('href', fontArr[i].fPath); - defs.appendChild(sc); - } + len = letters.length; + var renderedLetter; + var textSpan; + var glyphElement; + for (i = 0; i < len; i += 1) { + if (!letters[i].n) { + renderedLetter = renderedLetters[i]; + textSpan = this.textSpans[i].span; + glyphElement = this.textSpans[i].glyph; + if (glyphElement) { + glyphElement.renderFrame(); } - fontArr[i].helper = createHelper(fontArr[i], defs); - fontArr[i].cache = {}; - this.fonts.push(fontArr[i]); - } - if (_pendingFonts === 0) { - this.isLoaded = true; - } else { - // On some cases even if the font is loaded, it won't load correctly when measuring text on canvas. - // Adding this timeout seems to fix it - setTimeout(this.checkLoadedFonts.bind(this), 100); - } - } - - function addChars(chars) { - if (!chars) { - return; - } - if (!this.chars) { - this.chars = []; - } - var i; - var len = chars.length; - var j; - var jLen = this.chars.length; - var found; - for (i = 0; i < len; i += 1) { - j = 0; - found = false; - while (j < jLen) { - if (this.chars[j].style === chars[i].style && this.chars[j].fFamily === chars[i].fFamily && this.chars[j].ch === chars[i].ch) { - found = true; - } - j += 1; + if (renderedLetter._mdf.m) { + textSpan.setAttribute('transform', renderedLetter.m); } - if (!found) { - this.chars.push(chars[i]); - jLen += 1; + if (renderedLetter._mdf.o) { + textSpan.setAttribute('opacity', renderedLetter.o); } - } - } - - function getCharData(char, style, font) { - var i = 0; - var len = this.chars.length; - while (i < len) { - if (this.chars[i].ch === char && this.chars[i].style === style && this.chars[i].fFamily === font) { - return this.chars[i]; + if (renderedLetter._mdf.sw) { + textSpan.setAttribute('stroke-width', renderedLetter.sw); } - i += 1; - } - if (((typeof char === 'string' && char.charCodeAt(0) !== 13) || !char) - && console - && console.warn // eslint-disable-line no-console - && !this._warned - ) { - this._warned = true; - console.warn('Missing character from exported characters list: ', char, style, font); // eslint-disable-line no-console - } - return emptyChar; - } - - function measureText(char, fontName, size) { - var fontData = this.getFontByName(fontName); - var index = char.charCodeAt(0); - if (!fontData.cache[index + 1]) { - var tHelper = fontData.helper; - if (char === ' ') { - var doubleSize = tHelper.measureText('|' + char + '|'); - var singleSize = tHelper.measureText('||'); - fontData.cache[index + 1] = (doubleSize - singleSize) / 100; - } else { - fontData.cache[index + 1] = tHelper.measureText(char) / 100; + if (renderedLetter._mdf.sc) { + textSpan.setAttribute('stroke', renderedLetter.sc); } - } - return fontData.cache[index + 1] * size; - } - - function getFontByName(name) { - var i = 0; - var len = this.fonts.length; - while (i < len) { - if (this.fonts[i].fName === name) { - return this.fonts[i]; + if (renderedLetter._mdf.fc) { + textSpan.setAttribute('fill', renderedLetter.fc); } - i += 1; } - return this.fonts[0]; - } - - function isModifier(firstCharCode, secondCharCode) { - var sum = firstCharCode.toString(16) + secondCharCode.toString(16); - return surrogateModifiers.indexOf(sum) !== -1; } - - function isZeroWidthJoiner(firstCharCode, secondCharCode) { - if (!secondCharCode) { - return firstCharCode === zeroWidthJoiner[1]; + } + } +}; + +function ISolidElement(data, globalData, comp) { + this.initElement(data, globalData, comp); +} +extendPrototype([IImageElement], ISolidElement); + +ISolidElement.prototype.createContent = function () { + var rect = createNS('rect'); + /// /rect.style.width = this.data.sw; + /// /rect.style.height = this.data.sh; + /// /rect.style.fill = this.data.sc; + rect.setAttribute('width', this.data.sw); + rect.setAttribute('height', this.data.sh); + rect.setAttribute('fill', this.data.sc); + this.layerElement.appendChild(rect); +}; + +function NullElement(data, globalData, comp) { + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.initFrame(); + this.initTransform(data, globalData, comp); + this.initHierarchy(); +} + +NullElement.prototype.prepareFrame = function (num) { + this.prepareProperties(num, true); +}; + +NullElement.prototype.renderFrame = function () { +}; + +NullElement.prototype.getBaseElement = function () { + return null; +}; + +NullElement.prototype.destroy = function () { +}; + +NullElement.prototype.sourceRectAtTime = function () { +}; + +NullElement.prototype.hide = function () { +}; + +extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement], NullElement); + +function SVGRendererBase() { +} + +extendPrototype([BaseRenderer], SVGRendererBase); + +SVGRendererBase.prototype.createNull = function (data) { + return new NullElement(data, this.globalData, this); +}; + +SVGRendererBase.prototype.createShape = function (data) { + return new SVGShapeElement(data, this.globalData, this); +}; + +SVGRendererBase.prototype.createText = function (data) { + return new SVGTextLottieElement(data, this.globalData, this); +}; + +SVGRendererBase.prototype.createImage = function (data) { + return new IImageElement(data, this.globalData, this); +}; + +SVGRendererBase.prototype.createSolid = function (data) { + return new ISolidElement(data, this.globalData, this); +}; + +SVGRendererBase.prototype.configAnimation = function (animData) { + this.svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + if (this.renderConfig.viewBoxSize) { + this.svgElement.setAttribute('viewBox', this.renderConfig.viewBoxSize); + } else { + this.svgElement.setAttribute('viewBox', '0 0 ' + animData.w + ' ' + animData.h); + } + + if (!this.renderConfig.viewBoxOnly) { + this.svgElement.setAttribute('width', animData.w); + this.svgElement.setAttribute('height', animData.h); + this.svgElement.style.width = '100%'; + this.svgElement.style.height = '100%'; + this.svgElement.style.transform = 'translate3d(0,0,0)'; + this.svgElement.style.contentVisibility = this.renderConfig.contentVisibility; + } + if (this.renderConfig.width) { + this.svgElement.setAttribute('width', this.renderConfig.width); + } + if (this.renderConfig.height) { + this.svgElement.setAttribute('height', this.renderConfig.height); + } + if (this.renderConfig.className) { + this.svgElement.setAttribute('class', this.renderConfig.className); + } + if (this.renderConfig.id) { + this.svgElement.setAttribute('id', this.renderConfig.id); + } + if (this.renderConfig.focusable !== undefined) { + this.svgElement.setAttribute('focusable', this.renderConfig.focusable); + } + this.svgElement.setAttribute('preserveAspectRatio', this.renderConfig.preserveAspectRatio); + // this.layerElement.style.transform = 'translate3d(0,0,0)'; + // this.layerElement.style.transformOrigin = this.layerElement.style.mozTransformOrigin = this.layerElement.style.webkitTransformOrigin = this.layerElement.style['-webkit-transform'] = "0px 0px 0px"; + this.animationItem.wrapper.appendChild(this.svgElement); + // Mask animation + var defs = this.globalData.defs; + + this.setupGlobalData(animData, defs); + this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; + this.data = animData; + + var maskElement = createNS('clipPath'); + var rect = createNS('rect'); + rect.setAttribute('width', animData.w); + rect.setAttribute('height', animData.h); + rect.setAttribute('x', 0); + rect.setAttribute('y', 0); + var maskId = createElementID(); + maskElement.setAttribute('id', maskId); + maskElement.appendChild(rect); + this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + maskId + ')'); + + defs.appendChild(maskElement); + this.layers = animData.layers; + this.elements = createSizedArray(animData.layers.length); +}; + +SVGRendererBase.prototype.destroy = function () { + if (this.animationItem.wrapper) { + this.animationItem.wrapper.innerText = ''; + } + this.layerElement = null; + this.globalData.defs = null; + var i; + var len = this.layers ? this.layers.length : 0; + for (i = 0; i < len; i += 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + this.elements.length = 0; + this.destroyed = true; + this.animationItem = null; +}; + +SVGRendererBase.prototype.updateContainerSize = function () { +}; + +SVGRendererBase.prototype.buildItem = function (pos) { + var elements = this.elements; + if (elements[pos] || this.layers[pos].ty === 99) { + return; + } + elements[pos] = true; + var element = this.createItem(this.layers[pos]); + + elements[pos] = element; + if (getExpressionsPlugin()) { + if (this.layers[pos].ty === 0) { + this.globalData.projectInterface.registerComposition(element); + } + element.initExpressions(); + } + this.appendElementInPos(element, pos); + if (this.layers[pos].tt) { + if (!this.elements[pos - 1] || this.elements[pos - 1] === true) { + this.buildItem(pos - 1); + this.addPendingElement(element); + } else { + element.setMatte(elements[pos - 1].layerId); + } + } +}; + +SVGRendererBase.prototype.checkPendingElements = function () { + while (this.pendingElements.length) { + var element = this.pendingElements.pop(); + element.checkParenting(); + if (element.data.tt) { + var i = 0; + var len = this.elements.length; + while (i < len) { + if (this.elements[i] === element) { + element.setMatte(this.elements[i - 1].layerId); + break; } - return firstCharCode === zeroWidthJoiner[0] && secondCharCode === zeroWidthJoiner[1]; + i += 1; } - - function isCombinedCharacter(char) { - return combinedCharacters.indexOf(char) !== -1; + } + } +}; + +SVGRendererBase.prototype.renderFrame = function (num) { + if (this.renderedFrame === num || this.destroyed) { + return; + } + if (num === null) { + num = this.renderedFrame; + } else { + this.renderedFrame = num; + } + // console.log('-------'); + // console.log('FRAME ',num); + this.globalData.frameNum = num; + this.globalData.frameId += 1; + this.globalData.projectInterface.currentFrame = num; + this.globalData._mdf = false; + var i; + var len = this.layers.length; + if (!this.completeLayers) { + this.checkLayers(num); + } + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].prepareFrame(num - this.layers[i].st); + } + } + if (this.globalData._mdf) { + for (i = 0; i < len; i += 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); } - - function setIsLoaded() { - this.isLoaded = true; + } + } +}; + +SVGRendererBase.prototype.appendElementInPos = function (element, pos) { + var newElement = element.getBaseElement(); + if (!newElement) { + return; + } + var i = 0; + var nextElement; + while (i < pos) { + if (this.elements[i] && this.elements[i] !== true && this.elements[i].getBaseElement()) { + nextElement = this.elements[i].getBaseElement(); + } + i += 1; + } + if (nextElement) { + this.layerElement.insertBefore(newElement, nextElement); + } else { + this.layerElement.appendChild(newElement); + } +}; + +SVGRendererBase.prototype.hide = function () { + this.layerElement.style.display = 'none'; +}; + +SVGRendererBase.prototype.show = function () { + this.layerElement.style.display = 'block'; +}; + +function ICompElement() {} + +extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement, RenderableDOMElement], ICompElement); + +ICompElement.prototype.initElement = function (data, globalData, comp) { + this.initFrame(); + this.initBaseData(data, globalData, comp); + this.initTransform(data, globalData, comp); + this.initRenderable(); + this.initHierarchy(); + this.initRendererElement(); + this.createContainerElements(); + this.createRenderableComponents(); + if (this.data.xt || !globalData.progressiveLoad) { + this.buildAllItems(); + } + this.hide(); +}; + +/* ICompElement.prototype.hide = function(){ + if(!this.hidden){ + this.hideElement(); + var i,len = this.elements.length; + for( i = 0; i < len; i+=1 ){ + if(this.elements[i]){ + this.elements[i].hide(); + } + } + } +}; */ + +ICompElement.prototype.prepareFrame = function (num) { + this._mdf = false; + this.prepareRenderableFrame(num); + this.prepareProperties(num, this.isInRange); + if (!this.isInRange && !this.data.xt) { + return; + } + + if (!this.tm._placeholder) { + var timeRemapped = this.tm.v; + if (timeRemapped === this.data.op) { + timeRemapped = this.data.op - 1; + } + this.renderedFrame = timeRemapped; + } else { + this.renderedFrame = num / this.data.sr; + } + var i; + var len = this.elements.length; + if (!this.completeLayers) { + this.checkLayers(this.renderedFrame); + } + // This iteration needs to be backwards because of how expressions connect between each other + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].prepareFrame(this.renderedFrame - this.layers[i].st); + if (this.elements[i]._mdf) { + this._mdf = true; } - - var Font = function () { - this.fonts = []; - this.chars = null; - this.typekitLoaded = 0; - this.isLoaded = false; - this._warned = false; - this.initTime = Date.now(); - this.setIsLoadedBinded = this.setIsLoaded.bind(this); - this.checkLoadedFontsBinded = this.checkLoadedFonts.bind(this); - }; - Font.isModifier = isModifier; - Font.isZeroWidthJoiner = isZeroWidthJoiner; - Font.isCombinedCharacter = isCombinedCharacter; - - var fontPrototype = { - addChars: addChars, - addFonts: addFonts, - getCharData: getCharData, - getFontByName: getFontByName, - measureText: measureText, - checkLoadedFonts: checkLoadedFonts, - setIsLoaded: setIsLoaded, + } + } +}; + +ICompElement.prototype.renderInnerContent = function () { + var i; + var len = this.layers.length; + for (i = 0; i < len; i += 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } +}; + +ICompElement.prototype.setElements = function (elems) { + this.elements = elems; +}; + +ICompElement.prototype.getElements = function () { + return this.elements; +}; + +ICompElement.prototype.destroyElements = function () { + var i; + var len = this.layers.length; + for (i = 0; i < len; i += 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } +}; + +ICompElement.prototype.destroy = function () { + this.destroyElements(); + this.destroyBaseElement(); +}; + +function SVGCompElement(data, globalData, comp) { + this.layers = data.layers; + this.supports3d = true; + this.completeLayers = false; + this.pendingElements = []; + this.elements = this.layers ? createSizedArray(this.layers.length) : []; + this.initElement(data, globalData, comp); + this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; +} + +extendPrototype([SVGRendererBase, ICompElement, SVGBaseElement], SVGCompElement); + +SVGCompElement.prototype.createComp = function (data) { + return new SVGCompElement(data, this.globalData, this); +}; + +function SVGRenderer(animationItem, config) { + this.animationItem = animationItem; + this.layers = null; + this.renderedFrame = -1; + this.svgElement = createNS('svg'); + var ariaLabel = ''; + if (config && config.title) { + var titleElement = createNS('title'); + var titleId = createElementID(); + titleElement.setAttribute('id', titleId); + titleElement.textContent = config.title; + this.svgElement.appendChild(titleElement); + ariaLabel += titleId; + } + if (config && config.description) { + var descElement = createNS('desc'); + var descId = createElementID(); + descElement.setAttribute('id', descId); + descElement.textContent = config.description; + this.svgElement.appendChild(descElement); + ariaLabel += ' ' + descId; + } + if (ariaLabel) { + this.svgElement.setAttribute('aria-labelledby', ariaLabel); + } + var defs = createNS('defs'); + this.svgElement.appendChild(defs); + var maskElement = createNS('g'); + this.svgElement.appendChild(maskElement); + this.layerElement = maskElement; + this.renderConfig = { + preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', + imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', + contentVisibility: (config && config.contentVisibility) || 'visible', + progressiveLoad: (config && config.progressiveLoad) || false, + hideOnTransparent: !((config && config.hideOnTransparent === false)), + viewBoxOnly: (config && config.viewBoxOnly) || false, + viewBoxSize: (config && config.viewBoxSize) || false, + className: (config && config.className) || '', + id: (config && config.id) || '', + focusable: config && config.focusable, + filterSize: { + width: (config && config.filterSize && config.filterSize.width) || '100%', + height: (config && config.filterSize && config.filterSize.height) || '100%', + x: (config && config.filterSize && config.filterSize.x) || '0%', + y: (config && config.filterSize && config.filterSize.y) || '0%', + }, + width: (config && config.width), + height: (config && config.height), + }; + + this.globalData = { + _mdf: false, + frameNum: -1, + defs: defs, + renderConfig: this.renderConfig, + }; + this.elements = []; + this.pendingElements = []; + this.destroyed = false; + this.rendererType = 'svg'; +} + +extendPrototype([SVGRendererBase], SVGRenderer); + +SVGRenderer.prototype.createComp = function (data) { + return new SVGCompElement(data, this.globalData, this); +}; + +function CVContextData() { + this.saved = []; + this.cArrPos = 0; + this.cTr = new Matrix(); + this.cO = 1; + var i; + var len = 15; + this.savedOp = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + this.saved[i] = createTypedArray('float32', 16); + } + this._length = len; +} + +CVContextData.prototype.duplicate = function () { + var newLength = this._length * 2; + var currentSavedOp = this.savedOp; + this.savedOp = createTypedArray('float32', newLength); + this.savedOp.set(currentSavedOp); + var i = 0; + for (i = this._length; i < newLength; i += 1) { + this.saved[i] = createTypedArray('float32', 16); + } + this._length = newLength; +}; + +CVContextData.prototype.reset = function () { + this.cArrPos = 0; + this.cTr.reset(); + this.cO = 1; +}; + +function ShapeTransformManager() { + this.sequences = {}; + this.sequenceList = []; + this.transform_key_count = 0; +} + +ShapeTransformManager.prototype = { + addTransformSequence: function (transforms) { + var i; + var len = transforms.length; + var key = '_'; + for (i = 0; i < len; i += 1) { + key += transforms[i].transform.key + '_'; + } + var sequence = this.sequences[key]; + if (!sequence) { + sequence = { + transforms: [].concat(transforms), + finalTransform: new Matrix(), + _mdf: false, }; - - Font.prototype = fontPrototype; - - return Font; - }()); - - function RenderableElement() { - + this.sequences[key] = sequence; + this.sequenceList.push(sequence); } - - RenderableElement.prototype = { - initRenderable: function () { - // layer's visibility related to inpoint and outpoint. Rename isVisible to isInRange - this.isInRange = false; - // layer's display state - this.hidden = false; - // If layer's transparency equals 0, it can be hidden - this.isTransparent = false; - // list of animated components - this.renderableComponents = []; - }, - addRenderableComponent: function (component) { - if (this.renderableComponents.indexOf(component) === -1) { - this.renderableComponents.push(component); - } - }, - removeRenderableComponent: function (component) { - if (this.renderableComponents.indexOf(component) !== -1) { - this.renderableComponents.splice(this.renderableComponents.indexOf(component), 1); - } - }, - prepareRenderableFrame: function (num) { - this.checkLayerLimits(num); - }, - checkTransparency: function () { - if (this.finalTransform.mProp.o.v <= 0) { - if (!this.isTransparent && this.globalData.renderConfig.hideOnTransparent) { - this.isTransparent = true; - this.hide(); - } - } else if (this.isTransparent) { - this.isTransparent = false; - this.show(); - } - }, - /** - * @function - * Initializes frame related properties. - * - * @param {number} num - * current frame number in Layer's time - * - */ - checkLayerLimits: function (num) { - if (this.data.ip - this.data.st <= num && this.data.op - this.data.st > num) { - if (this.isInRange !== true) { - this.globalData._mdf = true; - this._mdf = true; - this.isInRange = true; - this.show(); - } - } else if (this.isInRange !== false) { - this.globalData._mdf = true; - this.isInRange = false; - this.hide(); - } - }, - renderRenderable: function () { - var i; - var len = this.renderableComponents.length; - for (i = 0; i < len; i += 1) { - this.renderableComponents[i].renderFrame(this._isFirstFrame); - } - /* this.maskManager.renderFrame(this.finalTransform.mat); - this.renderableEffectsManager.renderFrame(this._isFirstFrame); */ - }, - sourceRectAtTime: function () { - return { - top: 0, - left: 0, - width: 100, - height: 100, - }; - }, - getLayerSize: function () { - if (this.data.ty === 5) { - return { w: this.data.textData.width, h: this.data.textData.height }; - } - return { w: this.data.width, h: this.data.height }; - }, - }; - - const MaskManagerInterface = (function () { - function MaskInterface(mask, data) { - this._mask = mask; - this._data = data; - } - Object.defineProperty(MaskInterface.prototype, 'maskPath', { - get: function () { - if (this._mask.prop.k) { - this._mask.prop.getValue(); - } - return this._mask.prop; - }, - }); - Object.defineProperty(MaskInterface.prototype, 'maskOpacity', { - get: function () { - if (this._mask.op.k) { - this._mask.op.getValue(); - } - return this._mask.op.v * 100; - }, - }); - - var MaskManager = function (maskManager) { - var _masksInterfaces = createSizedArray(maskManager.viewData.length); - var i; - var len = maskManager.viewData.length; - for (i = 0; i < len; i += 1) { - _masksInterfaces[i] = new MaskInterface(maskManager.viewData[i], maskManager.masksProperties[i]); - } - - var maskFunction = function (name) { - i = 0; - while (i < len) { - if (maskManager.masksProperties[i].nm === name) { - return _masksInterfaces[i]; - } - i += 1; - } - return null; - }; - return maskFunction; - }; - return MaskManager; - }()); - - const ExpressionPropertyInterface = (function () { - var defaultUnidimensionalValue = { pv: 0, v: 0, mult: 1 }; - var defaultMultidimensionalValue = { pv: [0, 0, 0], v: [0, 0, 0], mult: 1 }; - - function completeProperty(expressionValue, property, type) { - Object.defineProperty(expressionValue, 'velocity', { - get: function () { - return property.getVelocityAtTime(property.comp.currentFrame); - }, - }); - expressionValue.numKeys = property.keyframes ? property.keyframes.length : 0; - expressionValue.key = function (pos) { - if (!expressionValue.numKeys) { - return 0; - } - var value = ''; - if ('s' in property.keyframes[pos - 1]) { - value = property.keyframes[pos - 1].s; - } else if ('e' in property.keyframes[pos - 2]) { - value = property.keyframes[pos - 2].e; - } else { - value = property.keyframes[pos - 2].s; - } - var valueProp = type === 'unidimensional' ? new Number(value) : Object.assign({}, value); // eslint-disable-line no-new-wrappers - valueProp.time = property.keyframes[pos - 1].t / property.elem.comp.globalData.frameRate; - valueProp.value = type === 'unidimensional' ? value[0] : value; - return valueProp; - }; - expressionValue.valueAtTime = property.getValueAtTime; - expressionValue.speedAtTime = property.getSpeedAtTime; - expressionValue.velocityAtTime = property.getVelocityAtTime; - expressionValue.propertyGroup = property.propertyGroup; - } - - function UnidimensionalPropertyInterface(property) { - if (!property || !('pv' in property)) { - property = defaultUnidimensionalValue; - } - var mult = 1 / property.mult; - var val = property.pv * mult; - var expressionValue = new Number(val); // eslint-disable-line no-new-wrappers - expressionValue.value = val; - completeProperty(expressionValue, property, 'unidimensional'); - - return function () { - if (property.k) { - property.getValue(); - } - val = property.v * mult; - if (expressionValue.value !== val) { - expressionValue = new Number(val); // eslint-disable-line no-new-wrappers - expressionValue.value = val; - completeProperty(expressionValue, property, 'unidimensional'); - } - return expressionValue; - }; - } - - function MultidimensionalPropertyInterface(property) { - if (!property || !('pv' in property)) { - property = defaultMultidimensionalValue; - } - var mult = 1 / property.mult; - var len = (property.data && property.data.l) || property.pv.length; - var expressionValue = createTypedArray('float32', len); - var arrValue = createTypedArray('float32', len); - expressionValue.value = arrValue; - completeProperty(expressionValue, property, 'multidimensional'); - - return function () { - if (property.k) { - property.getValue(); - } - for (var i = 0; i < len; i += 1) { - arrValue[i] = property.v[i] * mult; - expressionValue[i] = arrValue[i]; - } - return expressionValue; - }; - } - - // TODO: try to avoid using this getter - function defaultGetter() { - return defaultUnidimensionalValue; - } - - return function (property) { - if (!property) { - return defaultGetter; - } if (property.propType === 'unidimensional') { - return UnidimensionalPropertyInterface(property); - } - return MultidimensionalPropertyInterface(property); - }; - }()); - - const TransformExpressionInterface = (function () { - return function (transform) { - function _thisFunction(name) { - switch (name) { - case 'scale': - case 'Scale': - case 'ADBE Scale': - case 6: - return _thisFunction.scale; - case 'rotation': - case 'Rotation': - case 'ADBE Rotation': - case 'ADBE Rotate Z': - case 10: - return _thisFunction.rotation; - case 'ADBE Rotate X': - return _thisFunction.xRotation; - case 'ADBE Rotate Y': - return _thisFunction.yRotation; - case 'position': - case 'Position': - case 'ADBE Position': - case 2: - return _thisFunction.position; - case 'ADBE Position_0': - return _thisFunction.xPosition; - case 'ADBE Position_1': - return _thisFunction.yPosition; - case 'ADBE Position_2': - return _thisFunction.zPosition; - case 'anchorPoint': - case 'AnchorPoint': - case 'Anchor Point': - case 'ADBE AnchorPoint': - case 1: - return _thisFunction.anchorPoint; - case 'opacity': - case 'Opacity': - case 11: - return _thisFunction.opacity; - default: - return null; - } - } - Object.defineProperty(_thisFunction, 'rotation', { - get: ExpressionPropertyInterface(transform.r || transform.rz), - }); - - Object.defineProperty(_thisFunction, 'zRotation', { - get: ExpressionPropertyInterface(transform.rz || transform.r), - }); - - Object.defineProperty(_thisFunction, 'xRotation', { - get: ExpressionPropertyInterface(transform.rx), - }); - - Object.defineProperty(_thisFunction, 'yRotation', { - get: ExpressionPropertyInterface(transform.ry), - }); - Object.defineProperty(_thisFunction, 'scale', { - get: ExpressionPropertyInterface(transform.s), - }); - var _px; - var _py; - var _pz; - var _transformFactory; - if (transform.p) { - _transformFactory = ExpressionPropertyInterface(transform.p); - } else { - _px = ExpressionPropertyInterface(transform.px); - _py = ExpressionPropertyInterface(transform.py); - if (transform.pz) { - _pz = ExpressionPropertyInterface(transform.pz); - } - } - Object.defineProperty(_thisFunction, 'position', { - get: function () { - if (transform.p) { - return _transformFactory(); - } - return [ - _px(), - _py(), - _pz ? _pz() : 0]; - }, - }); - - Object.defineProperty(_thisFunction, 'xPosition', { - get: ExpressionPropertyInterface(transform.px), - }); - - Object.defineProperty(_thisFunction, 'yPosition', { - get: ExpressionPropertyInterface(transform.py), - }); - - Object.defineProperty(_thisFunction, 'zPosition', { - get: ExpressionPropertyInterface(transform.pz), - }); - - Object.defineProperty(_thisFunction, 'anchorPoint', { - get: ExpressionPropertyInterface(transform.a), - }); - - Object.defineProperty(_thisFunction, 'opacity', { - get: ExpressionPropertyInterface(transform.o), - }); - - Object.defineProperty(_thisFunction, 'skew', { - get: ExpressionPropertyInterface(transform.sk), - }); - - Object.defineProperty(_thisFunction, 'skewAxis', { - get: ExpressionPropertyInterface(transform.sa), - }); - - Object.defineProperty(_thisFunction, 'orientation', { - get: ExpressionPropertyInterface(transform.or), - }); - - return _thisFunction; - }; - }()); - - const LayerExpressionInterface = (function () { - function getMatrix(time) { - var toWorldMat = new Matrix(); - if (time !== undefined) { - var propMatrix = this._elem.finalTransform.mProp.getValueAtTime(time); - propMatrix.clone(toWorldMat); - } else { - var transformMat = this._elem.finalTransform.mProp; - transformMat.applyToMatrix(toWorldMat); - } - return toWorldMat; - } - - function toWorldVec(arr, time) { - var toWorldMat = this.getMatrix(time); - toWorldMat.props[12] = 0; - toWorldMat.props[13] = 0; - toWorldMat.props[14] = 0; - return this.applyPoint(toWorldMat, arr); - } - - function toWorld(arr, time) { - var toWorldMat = this.getMatrix(time); - return this.applyPoint(toWorldMat, arr); - } - - function fromWorldVec(arr, time) { - var toWorldMat = this.getMatrix(time); - toWorldMat.props[12] = 0; - toWorldMat.props[13] = 0; - toWorldMat.props[14] = 0; - return this.invertPoint(toWorldMat, arr); - } - - function fromWorld(arr, time) { - var toWorldMat = this.getMatrix(time); - return this.invertPoint(toWorldMat, arr); - } - - function applyPoint(matrix, arr) { - if (this._elem.hierarchy && this._elem.hierarchy.length) { - var i; - var len = this._elem.hierarchy.length; - for (i = 0; i < len; i += 1) { - this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); - } - } - return matrix.applyToPointArray(arr[0], arr[1], arr[2] || 0); - } - - function invertPoint(matrix, arr) { - if (this._elem.hierarchy && this._elem.hierarchy.length) { - var i; - var len = this._elem.hierarchy.length; - for (i = 0; i < len; i += 1) { - this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix); - } - } - return matrix.inversePoint(arr); - } - - function fromComp(arr) { - var toWorldMat = new Matrix(); - toWorldMat.reset(); - this._elem.finalTransform.mProp.applyToMatrix(toWorldMat); - if (this._elem.hierarchy && this._elem.hierarchy.length) { - var i; - var len = this._elem.hierarchy.length; - for (i = 0; i < len; i += 1) { - this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(toWorldMat); - } - return toWorldMat.inversePoint(arr); - } - return toWorldMat.inversePoint(arr); - } - - function sampleImage() { - return [1, 1, 1, 1]; - } - - return function (elem) { - var transformInterface; - - function _registerMaskInterface(maskManager) { - _thisLayerFunction.mask = new MaskManagerInterface(maskManager, elem); - } - function _registerEffectsInterface(effects) { - _thisLayerFunction.effect = effects; - } - - function _thisLayerFunction(name) { - switch (name) { - case 'ADBE Root Vectors Group': - case 'Contents': - case 2: - return _thisLayerFunction.shapeInterface; - case 1: - case 6: - case 'Transform': - case 'transform': - case 'ADBE Transform Group': - return transformInterface; - case 4: - case 'ADBE Effect Parade': - case 'effects': - case 'Effects': - return _thisLayerFunction.effect; - case 'ADBE Text Properties': - return _thisLayerFunction.textInterface; - default: - return null; - } - } - _thisLayerFunction.getMatrix = getMatrix; - _thisLayerFunction.invertPoint = invertPoint; - _thisLayerFunction.applyPoint = applyPoint; - _thisLayerFunction.toWorld = toWorld; - _thisLayerFunction.toWorldVec = toWorldVec; - _thisLayerFunction.fromWorld = fromWorld; - _thisLayerFunction.fromWorldVec = fromWorldVec; - _thisLayerFunction.toComp = toWorld; - _thisLayerFunction.fromComp = fromComp; - _thisLayerFunction.sampleImage = sampleImage; - _thisLayerFunction.sourceRectAtTime = elem.sourceRectAtTime.bind(elem); - _thisLayerFunction._elem = elem; - transformInterface = TransformExpressionInterface(elem.finalTransform.mProp); - var anchorPointDescriptor = getDescriptor(transformInterface, 'anchorPoint'); - Object.defineProperties(_thisLayerFunction, { - hasParent: { - get: function () { - return elem.hierarchy.length; - }, - }, - parent: { - get: function () { - return elem.hierarchy[0].layerInterface; - }, - }, - rotation: getDescriptor(transformInterface, 'rotation'), - scale: getDescriptor(transformInterface, 'scale'), - position: getDescriptor(transformInterface, 'position'), - opacity: getDescriptor(transformInterface, 'opacity'), - anchorPoint: anchorPointDescriptor, - anchor_point: anchorPointDescriptor, - transform: { - get: function () { - return transformInterface; - }, - }, - active: { - get: function () { - return elem.isInRange; - }, - }, - }); - - _thisLayerFunction.startTime = elem.data.st; - _thisLayerFunction.index = elem.data.ind; - _thisLayerFunction.source = elem.data.refId; - _thisLayerFunction.height = elem.data.ty === 0 ? elem.data.h : 100; - _thisLayerFunction.width = elem.data.ty === 0 ? elem.data.w : 100; - _thisLayerFunction.inPoint = elem.data.ip / elem.comp.globalData.frameRate; - _thisLayerFunction.outPoint = elem.data.op / elem.comp.globalData.frameRate; - _thisLayerFunction._name = elem.data.nm; - - _thisLayerFunction.registerMaskInterface = _registerMaskInterface; - _thisLayerFunction.registerEffectsInterface = _registerEffectsInterface; - return _thisLayerFunction; - }; - }()); - - const propertyGroupFactory = (function () { - return function (interfaceFunction, parentPropertyGroup) { - return function (val) { - val = val === undefined ? 1 : val; - if (val <= 0) { - return interfaceFunction; - } - return parentPropertyGroup(val - 1); - }; - }; - }()); - - const PropertyInterface = (function () { - return function (propertyName, propertyGroup) { - var interfaceFunction = { - _name: propertyName, - }; - - function _propertyGroup(val) { - val = val === undefined ? 1 : val; - if (val <= 0) { - return interfaceFunction; - } - return propertyGroup(val - 1); - } - - return _propertyGroup; - }; - }()); - - const EffectsExpressionInterface = (function () { - var ob = { - createEffectsInterface: createEffectsInterface, - }; - - function createEffectsInterface(elem, propertyGroup) { - if (elem.effectsManager) { - var effectElements = []; - var effectsData = elem.data.ef; - var i; - var len = elem.effectsManager.effectElements.length; - for (i = 0; i < len; i += 1) { - effectElements.push(createGroupInterface(effectsData[i], elem.effectsManager.effectElements[i], propertyGroup, elem)); - } - - var effects = elem.data.ef || []; - var groupInterface = function (name) { - i = 0; - len = effects.length; - while (i < len) { - if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { - return effectElements[i]; - } - i += 1; - } - return null; - }; - Object.defineProperty(groupInterface, 'numProperties', { - get: function () { - return effects.length; - }, - }); - return groupInterface; - } - return null; - } - - function createGroupInterface(data, elements, propertyGroup, elem) { - function groupInterface(name) { - var effects = data.ef; - var i = 0; - var len = effects.length; - while (i < len) { - if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) { - if (effects[i].ty === 5) { - return effectElements[i]; - } - return effectElements[i](); - } - i += 1; - } - throw new Error(); - } - var _propertyGroup = propertyGroupFactory(groupInterface, propertyGroup); - - var effectElements = []; - var i; - var len = data.ef.length; - for (i = 0; i < len; i += 1) { - if (data.ef[i].ty === 5) { - effectElements.push(createGroupInterface(data.ef[i], elements.effectElements[i], elements.effectElements[i].propertyGroup, elem)); - } else { - effectElements.push(createValueInterface(elements.effectElements[i], data.ef[i].ty, elem, _propertyGroup)); - } - } - - if (data.mn === 'ADBE Color Control') { - Object.defineProperty(groupInterface, 'color', { - get: function () { - return effectElements[0](); - }, - }); - } - Object.defineProperties(groupInterface, { - numProperties: { - get: function () { - return data.np; - }, - }, - _name: { value: data.nm }, - propertyGroup: { value: _propertyGroup }, - }); - groupInterface.enabled = data.en !== 0; - groupInterface.active = groupInterface.enabled; - return groupInterface; - } - - function createValueInterface(element, type, elem, propertyGroup) { - var expressionProperty = ExpressionPropertyInterface(element.p); - function interfaceFunction() { - if (type === 10) { - return elem.comp.compInterface(element.p.v); - } - return expressionProperty(); - } - - if (element.p.setGroupProperty) { - element.p.setGroupProperty(PropertyInterface('', propertyGroup)); - } - - return interfaceFunction; - } - - return ob; - }()); - - const CompExpressionInterface = (function () { - return function (comp) { - function _thisLayerFunction(name) { - var i = 0; - var len = comp.layers.length; - while (i < len) { - if (comp.layers[i].nm === name || comp.layers[i].ind === name) { - return comp.elements[i].layerInterface; - } - i += 1; - } - return null; - // return {active:false}; - } - Object.defineProperty(_thisLayerFunction, '_name', { value: comp.data.nm }); - _thisLayerFunction.layer = _thisLayerFunction; - _thisLayerFunction.pixelAspect = 1; - _thisLayerFunction.height = comp.data.h || comp.globalData.compSize.h; - _thisLayerFunction.width = comp.data.w || comp.globalData.compSize.w; - _thisLayerFunction.pixelAspect = 1; - _thisLayerFunction.frameDuration = 1 / comp.globalData.frameRate; - _thisLayerFunction.displayStartTime = 0; - _thisLayerFunction.numLayers = comp.layers.length; - return _thisLayerFunction; - }; - }()); - - const ShapePathInterface = ( - - function () { - return function pathInterfaceFactory(shape, view, propertyGroup) { - var prop = view.sh; - - function interfaceFunction(val) { - if (val === 'Shape' || val === 'shape' || val === 'Path' || val === 'path' || val === 'ADBE Vector Shape' || val === 2) { - return interfaceFunction.path; - } - return null; - } - - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - prop.setGroupProperty(PropertyInterface('Path', _propertyGroup)); - Object.defineProperties(interfaceFunction, { - path: { - get: function () { - if (prop.k) { - prop.getValue(); - } - return prop; - }, - }, - shape: { - get: function () { - if (prop.k) { - prop.getValue(); - } - return prop; - }, - }, - _name: { value: shape.nm }, - ix: { value: shape.ix }, - propertyIndex: { value: shape.ix }, - mn: { value: shape.mn }, - propertyGroup: { value: propertyGroup }, - }); - return interfaceFunction; - }; - }() - ); - - const ShapeExpressionInterface = (function () { - function iterateElements(shapes, view, propertyGroup) { - var arr = []; - var i; - var len = shapes ? shapes.length : 0; - for (i = 0; i < len; i += 1) { - if (shapes[i].ty === 'gr') { - arr.push(groupInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'fl') { - arr.push(fillInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'st') { - arr.push(strokeInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'tm') { - arr.push(trimInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'tr') { - // arr.push(transformInterfaceFactory(shapes[i],view[i],propertyGroup)); - } else if (shapes[i].ty === 'el') { - arr.push(ellipseInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'sr') { - arr.push(starInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'sh') { - arr.push(ShapePathInterface(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'rc') { - arr.push(rectInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'rd') { - arr.push(roundedInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'rp') { - arr.push(repeaterInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else if (shapes[i].ty === 'gf') { - arr.push(gradientFillInterfaceFactory(shapes[i], view[i], propertyGroup)); - } else { - arr.push(defaultInterfaceFactory(shapes[i], view[i], propertyGroup)); - } - } - return arr; - } - - function contentsInterfaceFactory(shape, view, propertyGroup) { - var interfaces; - var interfaceFunction = function _interfaceFunction(value) { - var i = 0; - var len = interfaces.length; - while (i < len) { - if (interfaces[i]._name === value || interfaces[i].mn === value || interfaces[i].propertyIndex === value || interfaces[i].ix === value || interfaces[i].ind === value) { - return interfaces[i]; - } - i += 1; - } - if (typeof value === 'number') { - return interfaces[value - 1]; - } - return null; - }; - - interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - interfaces = iterateElements(shape.it, view.it, interfaceFunction.propertyGroup); - interfaceFunction.numProperties = interfaces.length; - var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); - interfaceFunction.transform = transformInterface; - interfaceFunction.propertyIndex = shape.cix; - interfaceFunction._name = shape.nm; - - return interfaceFunction; - } - - function groupInterfaceFactory(shape, view, propertyGroup) { - var interfaceFunction = function _interfaceFunction(value) { - switch (value) { - case 'ADBE Vectors Group': - case 'Contents': - case 2: - return interfaceFunction.content; - // Not necessary for now. Keeping them here in case a new case appears - // case 'ADBE Vector Transform Group': - // case 3: - default: - return interfaceFunction.transform; - } - }; - interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var content = contentsInterfaceFactory(shape, view, interfaceFunction.propertyGroup); - var transformInterface = transformInterfaceFactory(shape.it[shape.it.length - 1], view.it[view.it.length - 1], interfaceFunction.propertyGroup); - interfaceFunction.content = content; - interfaceFunction.transform = transformInterface; - Object.defineProperty(interfaceFunction, '_name', { - get: function () { - return shape.nm; - }, - }); - // interfaceFunction.content = interfaceFunction; - interfaceFunction.numProperties = shape.np; - interfaceFunction.propertyIndex = shape.ix; - interfaceFunction.nm = shape.nm; - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function fillInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(val) { - if (val === 'Color' || val === 'color') { - return interfaceFunction.color; - } if (val === 'Opacity' || val === 'opacity') { - return interfaceFunction.opacity; - } - return null; - } - Object.defineProperties(interfaceFunction, { - color: { - get: ExpressionPropertyInterface(view.c), - }, - opacity: { - get: ExpressionPropertyInterface(view.o), - }, - _name: { value: shape.nm }, - mn: { value: shape.mn }, - }); - - view.c.setGroupProperty(PropertyInterface('Color', propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); - return interfaceFunction; - } - - function gradientFillInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(val) { - if (val === 'Start Point' || val === 'start point') { - return interfaceFunction.startPoint; - } - if (val === 'End Point' || val === 'end point') { - return interfaceFunction.endPoint; - } - if (val === 'Opacity' || val === 'opacity') { - return interfaceFunction.opacity; - } - return null; - } - Object.defineProperties(interfaceFunction, { - startPoint: { - get: ExpressionPropertyInterface(view.s), - }, - endPoint: { - get: ExpressionPropertyInterface(view.e), - }, - opacity: { - get: ExpressionPropertyInterface(view.o), - }, - type: { - get: function () { - return 'a'; - }, - }, - _name: { value: shape.nm }, - mn: { value: shape.mn }, - }); - - view.s.setGroupProperty(PropertyInterface('Start Point', propertyGroup)); - view.e.setGroupProperty(PropertyInterface('End Point', propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup)); - return interfaceFunction; - } - function defaultInterfaceFactory() { - function interfaceFunction() { - return null; - } - return interfaceFunction; - } - - function strokeInterfaceFactory(shape, view, propertyGroup) { - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var _dashPropertyGroup = propertyGroupFactory(dashOb, _propertyGroup); - function addPropertyToDashOb(i) { - Object.defineProperty(dashOb, shape.d[i].nm, { - get: ExpressionPropertyInterface(view.d.dataProps[i].p), - }); - } - var i; - var len = shape.d ? shape.d.length : 0; - var dashOb = {}; - for (i = 0; i < len; i += 1) { - addPropertyToDashOb(i); - view.d.dataProps[i].p.setGroupProperty(_dashPropertyGroup); - } - - function interfaceFunction(val) { - if (val === 'Color' || val === 'color') { - return interfaceFunction.color; - } if (val === 'Opacity' || val === 'opacity') { - return interfaceFunction.opacity; - } if (val === 'Stroke Width' || val === 'stroke width') { - return interfaceFunction.strokeWidth; - } - return null; - } - Object.defineProperties(interfaceFunction, { - color: { - get: ExpressionPropertyInterface(view.c), - }, - opacity: { - get: ExpressionPropertyInterface(view.o), - }, - strokeWidth: { - get: ExpressionPropertyInterface(view.w), - }, - dash: { - get: function () { - return dashOb; - }, - }, - _name: { value: shape.nm }, - mn: { value: shape.mn }, - }); - - view.c.setGroupProperty(PropertyInterface('Color', _propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); - view.w.setGroupProperty(PropertyInterface('Stroke Width', _propertyGroup)); - return interfaceFunction; - } - - function trimInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(val) { - if (val === shape.e.ix || val === 'End' || val === 'end') { - return interfaceFunction.end; - } - if (val === shape.s.ix) { - return interfaceFunction.start; - } - if (val === shape.o.ix) { - return interfaceFunction.offset; - } - return null; - } - - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - interfaceFunction.propertyIndex = shape.ix; - - view.s.setGroupProperty(PropertyInterface('Start', _propertyGroup)); - view.e.setGroupProperty(PropertyInterface('End', _propertyGroup)); - view.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); - interfaceFunction.propertyIndex = shape.ix; - interfaceFunction.propertyGroup = propertyGroup; - - Object.defineProperties(interfaceFunction, { - start: { - get: ExpressionPropertyInterface(view.s), - }, - end: { - get: ExpressionPropertyInterface(view.e), - }, - offset: { - get: ExpressionPropertyInterface(view.o), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function transformInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.a.ix === value || value === 'Anchor Point') { - return interfaceFunction.anchorPoint; - } - if (shape.o.ix === value || value === 'Opacity') { - return interfaceFunction.opacity; - } - if (shape.p.ix === value || value === 'Position') { - return interfaceFunction.position; - } - if (shape.r.ix === value || value === 'Rotation' || value === 'ADBE Vector Rotation') { - return interfaceFunction.rotation; - } - if (shape.s.ix === value || value === 'Scale') { - return interfaceFunction.scale; - } - if ((shape.sk && shape.sk.ix === value) || value === 'Skew') { - return interfaceFunction.skew; - } - if ((shape.sa && shape.sa.ix === value) || value === 'Skew Axis') { - return interfaceFunction.skewAxis; - } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - view.transform.mProps.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); - view.transform.mProps.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - view.transform.mProps.a.setGroupProperty(PropertyInterface('Anchor Point', _propertyGroup)); - view.transform.mProps.s.setGroupProperty(PropertyInterface('Scale', _propertyGroup)); - view.transform.mProps.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); - if (view.transform.mProps.sk) { - view.transform.mProps.sk.setGroupProperty(PropertyInterface('Skew', _propertyGroup)); - view.transform.mProps.sa.setGroupProperty(PropertyInterface('Skew Angle', _propertyGroup)); - } - view.transform.op.setGroupProperty(PropertyInterface('Opacity', _propertyGroup)); - Object.defineProperties(interfaceFunction, { - opacity: { - get: ExpressionPropertyInterface(view.transform.mProps.o), - }, - position: { - get: ExpressionPropertyInterface(view.transform.mProps.p), - }, - anchorPoint: { - get: ExpressionPropertyInterface(view.transform.mProps.a), - }, - scale: { - get: ExpressionPropertyInterface(view.transform.mProps.s), - }, - rotation: { - get: ExpressionPropertyInterface(view.transform.mProps.r), - }, - skew: { - get: ExpressionPropertyInterface(view.transform.mProps.sk), - }, - skewAxis: { - get: ExpressionPropertyInterface(view.transform.mProps.sa), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.ty = 'tr'; - interfaceFunction.mn = shape.mn; - interfaceFunction.propertyGroup = propertyGroup; - return interfaceFunction; - } - - function ellipseInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.p.ix === value) { - return interfaceFunction.position; - } - if (shape.s.ix === value) { - return interfaceFunction.size; - } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - interfaceFunction.propertyIndex = shape.ix; - var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; - prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); - prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - - Object.defineProperties(interfaceFunction, { - size: { - get: ExpressionPropertyInterface(prop.s), - }, - position: { - get: ExpressionPropertyInterface(prop.p), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function starInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.p.ix === value) { - return interfaceFunction.position; - } - if (shape.r.ix === value) { - return interfaceFunction.rotation; - } - if (shape.pt.ix === value) { - return interfaceFunction.points; - } - if (shape.or.ix === value || value === 'ADBE Vector Star Outer Radius') { - return interfaceFunction.outerRadius; - } - if (shape.os.ix === value) { - return interfaceFunction.outerRoundness; - } - if (shape.ir && (shape.ir.ix === value || value === 'ADBE Vector Star Inner Radius')) { - return interfaceFunction.innerRadius; - } - if (shape.is && shape.is.ix === value) { - return interfaceFunction.innerRoundness; - } - return null; - } - - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; - interfaceFunction.propertyIndex = shape.ix; - prop.or.setGroupProperty(PropertyInterface('Outer Radius', _propertyGroup)); - prop.os.setGroupProperty(PropertyInterface('Outer Roundness', _propertyGroup)); - prop.pt.setGroupProperty(PropertyInterface('Points', _propertyGroup)); - prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); - if (shape.ir) { - prop.ir.setGroupProperty(PropertyInterface('Inner Radius', _propertyGroup)); - prop.is.setGroupProperty(PropertyInterface('Inner Roundness', _propertyGroup)); - } - - Object.defineProperties(interfaceFunction, { - position: { - get: ExpressionPropertyInterface(prop.p), - }, - rotation: { - get: ExpressionPropertyInterface(prop.r), - }, - points: { - get: ExpressionPropertyInterface(prop.pt), - }, - outerRadius: { - get: ExpressionPropertyInterface(prop.or), - }, - outerRoundness: { - get: ExpressionPropertyInterface(prop.os), - }, - innerRadius: { - get: ExpressionPropertyInterface(prop.ir), - }, - innerRoundness: { - get: ExpressionPropertyInterface(prop.is), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function rectInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.p.ix === value) { - return interfaceFunction.position; - } - if (shape.r.ix === value) { - return interfaceFunction.roundness; - } - if (shape.s.ix === value || value === 'Size' || value === 'ADBE Vector Rect Size') { - return interfaceFunction.size; - } - return null; - } - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - - var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh; - interfaceFunction.propertyIndex = shape.ix; - prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup)); - prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup)); - prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup)); - - Object.defineProperties(interfaceFunction, { - position: { - get: ExpressionPropertyInterface(prop.p), - }, - roundness: { - get: ExpressionPropertyInterface(prop.r), - }, - size: { - get: ExpressionPropertyInterface(prop.s), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function roundedInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.r.ix === value || value === 'Round Corners 1') { - return interfaceFunction.radius; - } - return null; - } - - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view; - interfaceFunction.propertyIndex = shape.ix; - prop.rd.setGroupProperty(PropertyInterface('Radius', _propertyGroup)); - - Object.defineProperties(interfaceFunction, { - radius: { - get: ExpressionPropertyInterface(prop.rd), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - function repeaterInterfaceFactory(shape, view, propertyGroup) { - function interfaceFunction(value) { - if (shape.c.ix === value || value === 'Copies') { - return interfaceFunction.copies; - } if (shape.o.ix === value || value === 'Offset') { - return interfaceFunction.offset; - } - return null; - } - - var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup); - var prop = view; - interfaceFunction.propertyIndex = shape.ix; - prop.c.setGroupProperty(PropertyInterface('Copies', _propertyGroup)); - prop.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup)); - Object.defineProperties(interfaceFunction, { - copies: { - get: ExpressionPropertyInterface(prop.c), - }, - offset: { - get: ExpressionPropertyInterface(prop.o), - }, - _name: { value: shape.nm }, - }); - interfaceFunction.mn = shape.mn; - return interfaceFunction; - } - - return function (shapes, view, propertyGroup) { - var interfaces; - function _interfaceFunction(value) { - if (typeof value === 'number') { - value = value === undefined ? 1 : value; - if (value === 0) { - return propertyGroup; - } - return interfaces[value - 1]; - } - var i = 0; - var len = interfaces.length; - while (i < len) { - if (interfaces[i]._name === value) { - return interfaces[i]; - } - i += 1; - } - return null; - } - function parentGroupWrapper() { - return propertyGroup; - } - _interfaceFunction.propertyGroup = propertyGroupFactory(_interfaceFunction, parentGroupWrapper); - interfaces = iterateElements(shapes, view, _interfaceFunction.propertyGroup); - _interfaceFunction.numProperties = interfaces.length; - _interfaceFunction._name = 'Contents'; - return _interfaceFunction; - }; - }()); - - const TextExpressionInterface = (function () { - return function (elem) { - var _prevValue; - var _sourceText; - function _thisLayerFunction(name) { - switch (name) { - case 'ADBE Text Document': - return _thisLayerFunction.sourceText; - default: - return null; - } - } - Object.defineProperty(_thisLayerFunction, 'sourceText', { - get: function () { - elem.textProperty.getValue(); - var stringValue = elem.textProperty.currentData.t; - if (stringValue !== _prevValue) { - elem.textProperty.currentData.t = _prevValue; - _sourceText = new String(stringValue); // eslint-disable-line no-new-wrappers - // If stringValue is an empty string, eval returns undefined, so it has to be returned as a String primitive - _sourceText.value = stringValue || new String(stringValue); // eslint-disable-line no-new-wrappers - } - return _sourceText; - }, - }); - return _thisLayerFunction; - }; - }()); - - const getBlendMode = (function () { - var blendModeEnums = { - 0: 'source-over', - 1: 'multiply', - 2: 'screen', - 3: 'overlay', - 4: 'darken', - 5: 'lighten', - 6: 'color-dodge', - 7: 'color-burn', - 8: 'hard-light', - 9: 'soft-light', - 10: 'difference', - 11: 'exclusion', - 12: 'hue', - 13: 'saturation', - 14: 'color', - 15: 'luminosity', - }; - - return function (mode) { - return blendModeEnums[mode] || ''; - }; - }()); - - function SliderEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); - } - function AngleEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); - } - function ColorEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); - } - function PointEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container); - } - function LayerIndexEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); - } - function MaskIndexEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); - } - function CheckboxEffect(data, elem, container) { - this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container); - } - function NoValueEffect() { - this.p = {}; - } - - function EffectsManager(data, element) { - var effects = data.ef || []; - this.effectElements = []; - var i; - var len = effects.length; - var effectItem; - for (i = 0; i < len; i += 1) { - effectItem = new GroupEffect(effects[i], element); - this.effectElements.push(effectItem); - } - } - - function GroupEffect(data, element) { - this.init(data, element); - } - - extendPrototype([DynamicPropertyContainer], GroupEffect); - - GroupEffect.prototype.getValue = GroupEffect.prototype.iterateDynamicProperties; - - GroupEffect.prototype.init = function (data, element) { - this.data = data; - this.effectElements = []; - this.initDynamicPropertyContainer(element); - var i; - var len = this.data.ef.length; - var eff; - var effects = this.data.ef; - for (i = 0; i < len; i += 1) { - eff = null; - switch (effects[i].ty) { - case 0: - eff = new SliderEffect(effects[i], element, this); - break; - case 1: - eff = new AngleEffect(effects[i], element, this); - break; - case 2: - eff = new ColorEffect(effects[i], element, this); - break; - case 3: - eff = new PointEffect(effects[i], element, this); - break; - case 4: - case 7: - eff = new CheckboxEffect(effects[i], element, this); - break; - case 10: - eff = new LayerIndexEffect(effects[i], element, this); - break; - case 11: - eff = new MaskIndexEffect(effects[i], element, this); - break; - case 5: - eff = new EffectsManager(effects[i], element, this); - break; - // case 6: - default: - eff = new NoValueEffect(effects[i], element, this); - break; - } - if (eff) { - this.effectElements.push(eff); - } - } - }; - - function BaseElement() { - } - - BaseElement.prototype = { - checkMasks: function () { - if (!this.data.hasMask) { - return false; - } - var i = 0; - var len = this.data.masksProperties.length; - while (i < len) { - if ((this.data.masksProperties[i].mode !== 'n' && this.data.masksProperties[i].cl !== false)) { - return true; - } - i += 1; - } - return false; - }, - initExpressions: function () { - this.layerInterface = LayerExpressionInterface(this); - if (this.data.hasMask && this.maskManager) { - this.layerInterface.registerMaskInterface(this.maskManager); - } - var effectsInterface = EffectsExpressionInterface.createEffectsInterface(this, this.layerInterface); - this.layerInterface.registerEffectsInterface(effectsInterface); - - if (this.data.ty === 0 || this.data.xt) { - this.compInterface = CompExpressionInterface(this); - } else if (this.data.ty === 4) { - this.layerInterface.shapeInterface = ShapeExpressionInterface(this.shapesData, this.itemsData, this.layerInterface); - this.layerInterface.content = this.layerInterface.shapeInterface; - } else if (this.data.ty === 5) { - this.layerInterface.textInterface = TextExpressionInterface(this); - this.layerInterface.text = this.layerInterface.textInterface; - } - }, - setBlendMode: function () { - var blendModeValue = getBlendMode(this.data.bm); - var elem = this.baseElement || this.layerElement; - - elem.style['mix-blend-mode'] = blendModeValue; - }, - initBaseData: function (data, globalData, comp) { - this.globalData = globalData; - this.comp = comp; - this.data = data; - this.layerId = createElementID(); - - // Stretch factor for old animations missing this property. - if (!this.data.sr) { - this.data.sr = 1; - } - // effects manager - this.effectsManager = new EffectsManager(this.data, this, this.dynamicProperties); - }, - getType: function () { - return this.type; - }, - sourceRectAtTime: function () {}, - }; - - /** - * @file - * Handles element's layer frame update. - * Checks layer in point and out point - * - */ - - function FrameElement() {} - - FrameElement.prototype = { - /** - * @function - * Initializes frame related properties. - * - */ - initFrame: function () { - // set to true when inpoint is rendered - this._isFirstFrame = false; - // list of animated properties - this.dynamicProperties = []; - // If layer has been modified in current tick this will be true - this._mdf = false; - }, - /** - * @function - * Calculates all dynamic values - * - * @param {number} num - * current frame number in Layer's time - * @param {boolean} isVisible - * if layers is currently in range - * - */ - prepareProperties: function (num, isVisible) { - var i; - var len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - if (isVisible || (this._isParent && this.dynamicProperties[i].propType === 'transform')) { - this.dynamicProperties[i].getValue(); - if (this.dynamicProperties[i]._mdf) { - this.globalData._mdf = true; - this._mdf = true; - } - } - } - }, - addDynamicProperty: function (prop) { - if (this.dynamicProperties.indexOf(prop) === -1) { - this.dynamicProperties.push(prop); - } - }, - }; - - const FootageInterface = (function () { - var outlineInterfaceFactory = (function (elem) { - var currentPropertyName = ''; - var currentProperty = elem.getFootageData(); - function init() { - currentPropertyName = ''; - currentProperty = elem.getFootageData(); - return searchProperty; - } - function searchProperty(value) { - if (currentProperty[value]) { - currentPropertyName = value; - currentProperty = currentProperty[value]; - if (typeof currentProperty === 'object') { - return searchProperty; - } - return currentProperty; - } - var propertyNameIndex = value.indexOf(currentPropertyName); - if (propertyNameIndex !== -1) { - var index = parseInt(value.substr(propertyNameIndex + currentPropertyName.length), 10); - currentProperty = currentProperty[index]; - if (typeof currentProperty === 'object') { - return searchProperty; - } - return currentProperty; - } - return ''; - } - return init; - }); - - var dataInterfaceFactory = function (elem) { - function interfaceFunction(value) { - if (value === 'Outline') { - return interfaceFunction.outlineInterface(); - } - return null; - } - - interfaceFunction._name = 'Outline'; - interfaceFunction.outlineInterface = outlineInterfaceFactory(elem); - return interfaceFunction; - }; - - return function (elem) { - function _interfaceFunction(value) { - if (value === 'Data') { - return _interfaceFunction.dataInterface; - } - return null; - } - - _interfaceFunction._name = 'Data'; - _interfaceFunction.dataInterface = dataInterfaceFactory(elem); - return _interfaceFunction; - }; - }()); - - function FootageElement(data, globalData, comp) { - this.initFrame(); - this.initRenderable(); - this.assetData = globalData.getAssetData(data.refId); - this.footageData = globalData.imageLoader.getAsset(this.assetData); - this.initBaseData(data, globalData, comp); - } - - FootageElement.prototype.prepareFrame = function () { - }; - - extendPrototype([RenderableElement, BaseElement, FrameElement], FootageElement); - - FootageElement.prototype.getBaseElement = function () { - return null; - }; - - FootageElement.prototype.renderFrame = function () { - }; - - FootageElement.prototype.destroy = function () { - }; - - FootageElement.prototype.initExpressions = function () { - this.layerInterface = FootageInterface(this); - }; - - FootageElement.prototype.getFootageData = function () { - return this.footageData; - }; - - function AudioElement(data, globalData, comp) { - this.initFrame(); - this.initRenderable(); - this.assetData = globalData.getAssetData(data.refId); - this.initBaseData(data, globalData, comp); - this._isPlaying = false; - this._canPlay = false; - var assetPath = this.globalData.getAssetsPath(this.assetData); - this.audio = this.globalData.audioController.createAudio(assetPath); - this._currentTime = 0; - this.globalData.audioController.addAudio(this); - this._volumeMultiplier = 1; - this._volume = 1; - this._previousVolume = null; - this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; - this.lv = PropertyFactory.getProp(this, data.au && data.au.lv ? data.au.lv : { k: [100] }, 1, 0.01, this); - } - - AudioElement.prototype.prepareFrame = function (num) { - this.prepareRenderableFrame(num, true); - this.prepareProperties(num, true); - if (!this.tm._placeholder) { - var timeRemapped = this.tm.v; - this._currentTime = timeRemapped; - } else { - this._currentTime = num / this.data.sr; - } - this._volume = this.lv.v[0]; - var totalVolume = this._volume * this._volumeMultiplier; - if (this._previousVolume !== totalVolume) { - this._previousVolume = totalVolume; - this.audio.volume(totalVolume); - } - }; - - extendPrototype([RenderableElement, BaseElement, FrameElement], AudioElement); - - AudioElement.prototype.renderFrame = function () { - if (this.isInRange && this._canPlay) { - if (!this._isPlaying) { - this.audio.play(); - this.audio.seek(this._currentTime / this.globalData.frameRate); - this._isPlaying = true; - } else if (!this.audio.playing() - || Math.abs(this._currentTime / this.globalData.frameRate - this.audio.seek()) > 0.1 - ) { - this.audio.seek(this._currentTime / this.globalData.frameRate); - } - } - }; - - AudioElement.prototype.show = function () { - // this.audio.play() - }; - - AudioElement.prototype.hide = function () { - this.audio.pause(); - this._isPlaying = false; - }; - - AudioElement.prototype.pause = function () { - this.audio.pause(); - this._isPlaying = false; - this._canPlay = false; - }; - - AudioElement.prototype.resume = function () { - this._canPlay = true; - }; - - AudioElement.prototype.setRate = function (rateValue) { - this.audio.rate(rateValue); - }; - - AudioElement.prototype.volume = function (volumeValue) { - this._volumeMultiplier = volumeValue; - this._previousVolume = volumeValue * this._volume; - this.audio.volume(this._previousVolume); - }; - - AudioElement.prototype.getBaseElement = function () { - return null; - }; - - AudioElement.prototype.destroy = function () { - }; - - AudioElement.prototype.sourceRectAtTime = function () { - }; - - AudioElement.prototype.initExpressions = function () { - }; - - function BaseRenderer() {} - BaseRenderer.prototype.checkLayers = function (num) { - var i; - var len = this.layers.length; - var data; - this.completeLayers = true; - for (i = len - 1; i >= 0; i -= 1) { - if (!this.elements[i]) { - data = this.layers[i]; - if (data.ip - data.st <= (num - this.layers[i].st) && data.op - data.st > (num - this.layers[i].st)) { - this.buildItem(i); - } - } - this.completeLayers = this.elements[i] ? this.completeLayers : false; - } - this.checkPendingElements(); - }; - - BaseRenderer.prototype.createItem = function (layer) { - switch (layer.ty) { - case 2: - return this.createImage(layer); - case 0: - return this.createComp(layer); - case 1: - return this.createSolid(layer); - case 3: - return this.createNull(layer); - case 4: - return this.createShape(layer); - case 5: - return this.createText(layer); - case 6: - return this.createAudio(layer); - case 13: - return this.createCamera(layer); - case 15: - return this.createFootage(layer); - default: - return this.createNull(layer); - } - }; - - BaseRenderer.prototype.createCamera = function () { - throw new Error('You\'re using a 3d camera. Try the html renderer.'); - }; - - BaseRenderer.prototype.createAudio = function (data) { - return new AudioElement(data, this.globalData, this); - }; - - BaseRenderer.prototype.createFootage = function (data) { - return new FootageElement(data, this.globalData, this); - }; - - BaseRenderer.prototype.buildAllItems = function () { - var i; - var len = this.layers.length; - for (i = 0; i < len; i += 1) { - this.buildItem(i); - } - this.checkPendingElements(); - }; - - BaseRenderer.prototype.includeLayers = function (newLayers) { - this.completeLayers = false; - var i; - var len = newLayers.length; - var j; - var jLen = this.layers.length; - for (i = 0; i < len; i += 1) { - j = 0; - while (j < jLen) { - if (this.layers[j].id === newLayers[i].id) { - this.layers[j] = newLayers[i]; - break; - } - j += 1; - } - } - }; - - BaseRenderer.prototype.setProjectInterface = function (pInterface) { - this.globalData.projectInterface = pInterface; - }; - - BaseRenderer.prototype.initItems = function () { - if (!this.globalData.progressiveLoad) { - this.buildAllItems(); - } - }; - BaseRenderer.prototype.buildElementParenting = function (element, parentName, hierarchy) { - var elements = this.elements; - var layers = this.layers; - var i = 0; - var len = layers.length; - while (i < len) { - if (layers[i].ind == parentName) { // eslint-disable-line eqeqeq - if (!elements[i] || elements[i] === true) { - this.buildItem(i); - this.addPendingElement(element); - } else { - hierarchy.push(elements[i]); - elements[i].setAsParent(); - if (layers[i].parent !== undefined) { - this.buildElementParenting(element, layers[i].parent, hierarchy); - } else { - element.setHierarchy(hierarchy); - } - } - } - i += 1; - } - }; - - BaseRenderer.prototype.addPendingElement = function (element) { - this.pendingElements.push(element); - }; - - BaseRenderer.prototype.searchExtraCompositions = function (assets) { - var i; - var len = assets.length; - for (i = 0; i < len; i += 1) { - if (assets[i].xt) { - var comp = this.createComp(assets[i]); - comp.initExpressions(); - this.globalData.projectInterface.registerComposition(comp); - } - } - }; - - BaseRenderer.prototype.getElementByPath = function (path) { - var pathValue = path.shift(); - var element; - if (typeof pathValue === 'number') { - element = this.elements[pathValue]; - } else { - var i; - var len = this.elements.length; - for (i = 0; i < len; i += 1) { - if (this.elements[i].data.nm === pathValue) { - element = this.elements[i]; - break; - } - } - } - if (path.length === 0) { - return element; - } - return element.getElementByPath(path); - }; - - BaseRenderer.prototype.setupGlobalData = function (animData, fontsContainer) { - this.globalData.fontManager = new FontManager(); - this.globalData.fontManager.addChars(animData.chars); - this.globalData.fontManager.addFonts(animData.fonts, fontsContainer); - this.globalData.getAssetData = this.animationItem.getAssetData.bind(this.animationItem); - this.globalData.getAssetsPath = this.animationItem.getAssetsPath.bind(this.animationItem); - this.globalData.imageLoader = this.animationItem.imagePreloader; - this.globalData.audioController = this.animationItem.audioController; - this.globalData.frameId = 0; - this.globalData.frameRate = animData.fr; - this.globalData.nm = animData.nm; - this.globalData.compSize = { - w: animData.w, - h: animData.h, - }; - }; - - function TransformElement() {} - - TransformElement.prototype = { - initTransform: function () { - this.finalTransform = { - mProp: this.data.ks ? TransformPropertyFactory.getTransformProperty(this, this.data.ks, this) : { o: 0 }, - _matMdf: false, - _opMdf: false, - mat: new Matrix(), - }; - if (this.data.ao) { - this.finalTransform.mProp.autoOriented = true; - } - - // TODO: check TYPE 11: Guided elements - if (this.data.ty !== 11) { - // this.createElements(); - } - }, - renderTransform: function () { - this.finalTransform._opMdf = this.finalTransform.mProp.o._mdf || this._isFirstFrame; - this.finalTransform._matMdf = this.finalTransform.mProp._mdf || this._isFirstFrame; - - if (this.hierarchy) { - var mat; - var finalMat = this.finalTransform.mat; - var i = 0; - var len = this.hierarchy.length; - // Checking if any of the transformation matrices in the hierarchy chain has changed. - if (!this.finalTransform._matMdf) { - while (i < len) { - if (this.hierarchy[i].finalTransform.mProp._mdf) { - this.finalTransform._matMdf = true; - break; - } - i += 1; - } - } - - if (this.finalTransform._matMdf) { - mat = this.finalTransform.mProp.v.props; - finalMat.cloneFromProps(mat); - for (i = 0; i < len; i += 1) { - mat = this.hierarchy[i].finalTransform.mProp.v.props; - finalMat.transform(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5], mat[6], mat[7], mat[8], mat[9], mat[10], mat[11], mat[12], mat[13], mat[14], mat[15]); - } - } - } - }, - globalToLocal: function (pt) { - var transforms = []; - transforms.push(this.finalTransform); - var flag = true; - var comp = this.comp; - while (flag) { - if (comp.finalTransform) { - if (comp.data.hasMask) { - transforms.splice(0, 0, comp.finalTransform); - } - comp = comp.comp; - } else { - flag = false; - } - } - var i; - var len = transforms.length; - var ptNew; - for (i = 0; i < len; i += 1) { - ptNew = transforms[i].mat.applyToPointArray(0, 0, 0); - // ptNew = transforms[i].mat.applyToPointArray(pt[0],pt[1],pt[2]); - pt = [pt[0] - ptNew[0], pt[1] - ptNew[1], 0]; - } - return pt; - }, - mHelper: new Matrix(), - }; - - function MaskElement(data, element, globalData) { - this.data = data; - this.element = element; - this.globalData = globalData; - this.storedData = []; - this.masksProperties = this.data.masksProperties || []; - this.maskElement = null; - var defs = this.globalData.defs; - var i; - var len = this.masksProperties ? this.masksProperties.length : 0; - this.viewData = createSizedArray(len); - this.solidPath = ''; - - var path; - var properties = this.masksProperties; - var count = 0; - var currentMasks = []; - var j; - var jLen; - var layerId = createElementID(); - var rect; - var expansor; - var feMorph; - var x; - var maskType = 'clipPath'; - var maskRef = 'clip-path'; - for (i = 0; i < len; i += 1) { - if ((properties[i].mode !== 'a' && properties[i].mode !== 'n') || properties[i].inv || properties[i].o.k !== 100 || properties[i].o.x) { - maskType = 'mask'; - maskRef = 'mask'; - } - - if ((properties[i].mode === 's' || properties[i].mode === 'i') && count === 0) { - rect = createNS('rect'); - rect.setAttribute('fill', '#ffffff'); - rect.setAttribute('width', this.element.comp.data.w || 0); - rect.setAttribute('height', this.element.comp.data.h || 0); - currentMasks.push(rect); - } else { - rect = null; - } - - path = createNS('path'); - if (properties[i].mode === 'n') { - // TODO move this to a factory or to a constructor - this.viewData[i] = { - op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), - prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), - elem: path, - lastPath: '', - }; - defs.appendChild(path); - } else { - count += 1; - - path.setAttribute('fill', properties[i].mode === 's' ? '#000000' : '#ffffff'); - path.setAttribute('clip-rule', 'nonzero'); - var filterID; - - if (properties[i].x.k !== 0) { - maskType = 'mask'; - maskRef = 'mask'; - x = PropertyFactory.getProp(this.element, properties[i].x, 0, null, this.element); - filterID = createElementID(); - expansor = createNS('filter'); - expansor.setAttribute('id', filterID); - feMorph = createNS('feMorphology'); - feMorph.setAttribute('operator', 'erode'); - feMorph.setAttribute('in', 'SourceGraphic'); - feMorph.setAttribute('radius', '0'); - expansor.appendChild(feMorph); - defs.appendChild(expansor); - path.setAttribute('stroke', properties[i].mode === 's' ? '#000000' : '#ffffff'); - } else { - feMorph = null; - x = null; - } - - // TODO move this to a factory or to a constructor - this.storedData[i] = { - elem: path, - x: x, - expan: feMorph, - lastPath: '', - lastOperator: '', - filterId: filterID, - lastRadius: 0, - }; - if (properties[i].mode === 'i') { - jLen = currentMasks.length; - var g = createNS('g'); - for (j = 0; j < jLen; j += 1) { - g.appendChild(currentMasks[j]); - } - var mask = createNS('mask'); - mask.setAttribute('mask-type', 'alpha'); - mask.setAttribute('id', layerId + '_' + count); - mask.appendChild(path); - defs.appendChild(mask); - g.setAttribute('mask', 'url(' + getLocationHref() + '#' + layerId + '_' + count + ')'); - - currentMasks.length = 0; - currentMasks.push(g); - } else { - currentMasks.push(path); - } - if (properties[i].inv && !this.solidPath) { - this.solidPath = this.createLayerSolidPath(); - } - // TODO move this to a factory or to a constructor - this.viewData[i] = { - elem: path, - lastPath: '', - op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element), - prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3), - invRect: rect, - }; - if (!this.viewData[i].prop.k) { - this.drawPath(properties[i], this.viewData[i].prop.v, this.viewData[i]); - } - } - } - - this.maskElement = createNS(maskType); - - len = currentMasks.length; - for (i = 0; i < len; i += 1) { - this.maskElement.appendChild(currentMasks[i]); - } - - if (count > 0) { - this.maskElement.setAttribute('id', layerId); - this.element.maskedElement.setAttribute(maskRef, 'url(' + getLocationHref() + '#' + layerId + ')'); - defs.appendChild(this.maskElement); - } - if (this.viewData.length) { - this.element.addRenderableComponent(this); - } - } - - MaskElement.prototype.getMaskProperty = function (pos) { - return this.viewData[pos].prop; - }; - - MaskElement.prototype.renderFrame = function (isFirstFrame) { - var finalMat = this.element.finalTransform.mat; - var i; - var len = this.masksProperties.length; - for (i = 0; i < len; i += 1) { - if (this.viewData[i].prop._mdf || isFirstFrame) { - this.drawPath(this.masksProperties[i], this.viewData[i].prop.v, this.viewData[i]); - } - if (this.viewData[i].op._mdf || isFirstFrame) { - this.viewData[i].elem.setAttribute('fill-opacity', this.viewData[i].op.v); - } - if (this.masksProperties[i].mode !== 'n') { - if (this.viewData[i].invRect && (this.element.finalTransform.mProp._mdf || isFirstFrame)) { - this.viewData[i].invRect.setAttribute('transform', finalMat.getInverseMatrix().to2dCSS()); - } - if (this.storedData[i].x && (this.storedData[i].x._mdf || isFirstFrame)) { - var feMorph = this.storedData[i].expan; - if (this.storedData[i].x.v < 0) { - if (this.storedData[i].lastOperator !== 'erode') { - this.storedData[i].lastOperator = 'erode'; - this.storedData[i].elem.setAttribute('filter', 'url(' + getLocationHref() + '#' + this.storedData[i].filterId + ')'); - } - feMorph.setAttribute('radius', -this.storedData[i].x.v); - } else { - if (this.storedData[i].lastOperator !== 'dilate') { - this.storedData[i].lastOperator = 'dilate'; - this.storedData[i].elem.setAttribute('filter', null); - } - this.storedData[i].elem.setAttribute('stroke-width', this.storedData[i].x.v * 2); - } - } - } - } - }; - - MaskElement.prototype.getMaskelement = function () { - return this.maskElement; - }; - - MaskElement.prototype.createLayerSolidPath = function () { - var path = 'M0,0 '; - path += ' h' + this.globalData.compSize.w; - path += ' v' + this.globalData.compSize.h; - path += ' h-' + this.globalData.compSize.w; - path += ' v-' + this.globalData.compSize.h + ' '; - return path; - }; - - MaskElement.prototype.drawPath = function (pathData, pathNodes, viewData) { - var pathString = ' M' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; - var i; - var len; - len = pathNodes._length; - for (i = 1; i < len; i += 1) { - // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[i][0]+','+pathNodes.i[i][1] + " "+pathNodes.v[i][0]+','+pathNodes.v[i][1]; - pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[i][0] + ',' + pathNodes.i[i][1] + ' ' + pathNodes.v[i][0] + ',' + pathNodes.v[i][1]; - } - // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[0][0]+','+pathNodes.i[0][1] + " "+pathNodes.v[0][0]+','+pathNodes.v[0][1]; - if (pathNodes.c && len > 1) { - pathString += ' C' + pathNodes.o[i - 1][0] + ',' + pathNodes.o[i - 1][1] + ' ' + pathNodes.i[0][0] + ',' + pathNodes.i[0][1] + ' ' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]; - } - // pathNodes.__renderedString = pathString; - - if (viewData.lastPath !== pathString) { - var pathShapeValue = ''; - if (viewData.elem) { - if (pathNodes.c) { - pathShapeValue = pathData.inv ? this.solidPath + pathString : pathString; - } - viewData.elem.setAttribute('d', pathShapeValue); - } - viewData.lastPath = pathString; - } - }; - - MaskElement.prototype.destroy = function () { - this.element = null; - this.globalData = null; - this.maskElement = null; - this.data = null; - this.masksProperties = null; - }; - - const filtersFactory = (function () { - var ob = {}; - ob.createFilter = createFilter; - ob.createAlphaToLuminanceFilter = createAlphaToLuminanceFilter; - - function createFilter(filId, skipCoordinates) { - var fil = createNS('filter'); - fil.setAttribute('id', filId); - if (skipCoordinates !== true) { - fil.setAttribute('filterUnits', 'objectBoundingBox'); - fil.setAttribute('x', '0%'); - fil.setAttribute('y', '0%'); - fil.setAttribute('width', '100%'); - fil.setAttribute('height', '100%'); - } - return fil; - } - - function createAlphaToLuminanceFilter() { - var feColorMatrix = createNS('feColorMatrix'); - feColorMatrix.setAttribute('type', 'matrix'); - feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); - feColorMatrix.setAttribute('values', '0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1'); - return feColorMatrix; - } - - return ob; - }()); - - const featureSupport = (function () { - var ob = { - maskType: true, - }; - if (/MSIE 10/i.test(navigator.userAgent) || /MSIE 9/i.test(navigator.userAgent) || /rv:11.0/i.test(navigator.userAgent) || /Edge\/\d./i.test(navigator.userAgent)) { - ob.maskType = false; - } - return ob; - }()); - - var registeredEffects = {}; - var idPrefix = 'filter_result_'; - - function SVGEffects(elem) { - var i; - var source = 'SourceGraphic'; - var len = elem.data.ef ? elem.data.ef.length : 0; - var filId = createElementID(); - var fil = filtersFactory.createFilter(filId, true); - var count = 0; - this.filters = []; - var filterManager; - for (i = 0; i < len; i += 1) { - filterManager = null; - var type = elem.data.ef[i].ty; - if (registeredEffects[type]) { - var Effect = registeredEffects[type].effect; - filterManager = new Effect(fil, elem.effectsManager.effectElements[i], elem, idPrefix + count, source); - source = idPrefix + count; - if (registeredEffects[type].countsAsEffect) { - count += 1; - } - } - if (filterManager) { - this.filters.push(filterManager); - } - } - if (count) { - elem.globalData.defs.appendChild(fil); - elem.layerElement.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); - } - if (this.filters.length) { - elem.addRenderableComponent(this); - } - } - - SVGEffects.prototype.renderFrame = function (_isFirstFrame) { - var i; - var len = this.filters.length; - for (i = 0; i < len; i += 1) { - this.filters[i].renderFrame(_isFirstFrame); - } - }; - - function registerEffect(id, effect, countsAsEffect) { - registeredEffects[id] = { - effect, - countsAsEffect, - }; - } - - function SVGBaseElement() { - } - - SVGBaseElement.prototype = { - initRendererElement: function () { - this.layerElement = createNS('g'); - }, - createContainerElements: function () { - this.matteElement = createNS('g'); - this.transformedElement = this.layerElement; - this.maskedElement = this.layerElement; - this._sizeChanged = false; - var layerElementParent = null; - // If this layer acts as a mask for the following layer - var filId; - var fil; - var gg; - if (this.data.td) { - if (this.data.td == 3 || this.data.td == 1) { // eslint-disable-line eqeqeq - var masker = createNS('mask'); - masker.setAttribute('id', this.layerId); - masker.setAttribute('mask-type', this.data.td == 3 ? 'luminance' : 'alpha'); // eslint-disable-line eqeqeq - masker.appendChild(this.layerElement); - layerElementParent = masker; - this.globalData.defs.appendChild(masker); - // This is only for IE and Edge when mask if of type alpha - if (!featureSupport.maskType && this.data.td == 1) { // eslint-disable-line eqeqeq - masker.setAttribute('mask-type', 'luminance'); - filId = createElementID(); - fil = filtersFactory.createFilter(filId); - this.globalData.defs.appendChild(fil); - fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); - gg = createNS('g'); - gg.appendChild(this.layerElement); - layerElementParent = gg; - masker.appendChild(gg); - gg.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); - } - } else if (this.data.td == 2) { // eslint-disable-line eqeqeq - var maskGroup = createNS('mask'); - maskGroup.setAttribute('id', this.layerId); - maskGroup.setAttribute('mask-type', 'alpha'); - var maskGrouper = createNS('g'); - maskGroup.appendChild(maskGrouper); - filId = createElementID(); - fil = filtersFactory.createFilter(filId); - /// / - - // This solution doesn't work on Android when meta tag with viewport attribute is set - /* var feColorMatrix = createNS('feColorMatrix'); - feColorMatrix.setAttribute('type', 'matrix'); - feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB'); - feColorMatrix.setAttribute('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 -1 1'); - fil.appendChild(feColorMatrix); */ - /// / - var feCTr = createNS('feComponentTransfer'); - feCTr.setAttribute('in', 'SourceGraphic'); - fil.appendChild(feCTr); - var feFunc = createNS('feFuncA'); - feFunc.setAttribute('type', 'table'); - feFunc.setAttribute('tableValues', '1.0 0.0'); - feCTr.appendChild(feFunc); - /// / - this.globalData.defs.appendChild(fil); - var alphaRect = createNS('rect'); - alphaRect.setAttribute('width', this.comp.data.w); - alphaRect.setAttribute('height', this.comp.data.h); - alphaRect.setAttribute('x', '0'); - alphaRect.setAttribute('y', '0'); - alphaRect.setAttribute('fill', '#ffffff'); - alphaRect.setAttribute('opacity', '0'); - maskGrouper.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')'); - maskGrouper.appendChild(alphaRect); - maskGrouper.appendChild(this.layerElement); - layerElementParent = maskGrouper; - if (!featureSupport.maskType) { - maskGroup.setAttribute('mask-type', 'luminance'); - fil.appendChild(filtersFactory.createAlphaToLuminanceFilter()); - gg = createNS('g'); - maskGrouper.appendChild(alphaRect); - gg.appendChild(this.layerElement); - layerElementParent = gg; - maskGrouper.appendChild(gg); - } - this.globalData.defs.appendChild(maskGroup); - } - } else if (this.data.tt) { - this.matteElement.appendChild(this.layerElement); - layerElementParent = this.matteElement; - this.baseElement = this.matteElement; - } else { - this.baseElement = this.layerElement; - } - if (this.data.ln) { - this.layerElement.setAttribute('id', this.data.ln); - } - if (this.data.cl) { - this.layerElement.setAttribute('class', this.data.cl); - } - // Clipping compositions to hide content that exceeds boundaries. If collapsed transformations is on, component should not be clipped - if (this.data.ty === 0 && !this.data.hd) { - var cp = createNS('clipPath'); - var pt = createNS('path'); - pt.setAttribute('d', 'M0,0 L' + this.data.w + ',0 L' + this.data.w + ',' + this.data.h + ' L0,' + this.data.h + 'z'); - var clipId = createElementID(); - cp.setAttribute('id', clipId); - cp.appendChild(pt); - this.globalData.defs.appendChild(cp); - - if (this.checkMasks()) { - var cpGroup = createNS('g'); - cpGroup.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); - cpGroup.appendChild(this.layerElement); - this.transformedElement = cpGroup; - if (layerElementParent) { - layerElementParent.appendChild(this.transformedElement); - } else { - this.baseElement = this.transformedElement; - } - } else { - this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')'); - } - } - if (this.data.bm !== 0) { - this.setBlendMode(); - } - }, - renderElement: function () { - if (this.finalTransform._matMdf) { - this.transformedElement.setAttribute('transform', this.finalTransform.mat.to2dCSS()); - } - if (this.finalTransform._opMdf) { - this.transformedElement.setAttribute('opacity', this.finalTransform.mProp.o.v); - } - }, - destroyBaseElement: function () { - this.layerElement = null; - this.matteElement = null; - this.maskManager.destroy(); - }, - getBaseElement: function () { - if (this.data.hd) { - return null; - } - return this.baseElement; - }, - createRenderableComponents: function () { - this.maskManager = new MaskElement(this.data, this, this.globalData); - this.renderableEffectsManager = new SVGEffects(this); - }, - setMatte: function (id) { - if (!this.matteElement) { - return; - } - this.matteElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + id + ')'); - }, - }; - - /** - * @file - * Handles AE's layer parenting property. - * - */ - - function HierarchyElement() {} - - HierarchyElement.prototype = { - /** - * @function - * Initializes hierarchy properties - * - */ - initHierarchy: function () { - // element's parent list - this.hierarchy = []; - // if element is parent of another layer _isParent will be true - this._isParent = false; - this.checkParenting(); - }, - /** - * @function - * Sets layer's hierarchy. - * @param {array} hierarch - * layer's parent list - * - */ - setHierarchy: function (hierarchy) { - this.hierarchy = hierarchy; - }, - /** - * @function - * Sets layer as parent. - * - */ - setAsParent: function () { - this._isParent = true; - }, - /** - * @function - * Searches layer's parenting chain - * - */ - checkParenting: function () { - if (this.data.parent !== undefined) { - this.comp.buildElementParenting(this, this.data.parent, []); - } - }, - }; - - function RenderableDOMElement() {} - - (function () { - var _prototype = { - initElement: function (data, globalData, comp) { - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.initTransform(data, globalData, comp); - this.initHierarchy(); - this.initRenderable(); - this.initRendererElement(); - this.createContainerElements(); - this.createRenderableComponents(); - this.createContent(); - this.hide(); - }, - hide: function () { - // console.log('HIDE', this); - if (!this.hidden && (!this.isInRange || this.isTransparent)) { - var elem = this.baseElement || this.layerElement; - elem.style.display = 'none'; - this.hidden = true; - } - }, - show: function () { - // console.log('SHOW', this); - if (this.isInRange && !this.isTransparent) { - if (!this.data.hd) { - var elem = this.baseElement || this.layerElement; - elem.style.display = 'block'; - } - this.hidden = false; - this._isFirstFrame = true; - } - }, - renderFrame: function () { - // If it is exported as hidden (data.hd === true) no need to render - // If it is not visible no need to render - if (this.data.hd || this.hidden) { - return; - } - this.renderTransform(); - this.renderRenderable(); - this.renderElement(); - this.renderInnerContent(); - if (this._isFirstFrame) { - this._isFirstFrame = false; - } - }, - renderInnerContent: function () {}, - prepareFrame: function (num) { - this._mdf = false; - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - this.checkTransparency(); - }, - destroy: function () { - this.innerElem = null; - this.destroyBaseElement(); - }, - }; - extendPrototype([RenderableElement, createProxyFunction(_prototype)], RenderableDOMElement); - }()); - - function IImageElement(data, globalData, comp) { - this.assetData = globalData.getAssetData(data.refId); - this.initElement(data, globalData, comp); - this.sourceRect = { - top: 0, left: 0, width: this.assetData.w, height: this.assetData.h, - }; - } - - extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement], IImageElement); - - IImageElement.prototype.createContent = function () { - var assetPath = this.globalData.getAssetsPath(this.assetData); - - this.innerElem = createNS('image'); - this.innerElem.setAttribute('width', this.assetData.w + 'px'); - this.innerElem.setAttribute('height', this.assetData.h + 'px'); - this.innerElem.setAttribute('preserveAspectRatio', this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio); - this.innerElem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', assetPath); - - this.layerElement.appendChild(this.innerElem); - }; - - IImageElement.prototype.sourceRectAtTime = function () { - return this.sourceRect; - }; - - function ProcessedElement(element, position) { - this.elem = element; - this.pos = position; - } - - function IShapeElement() { - } - - IShapeElement.prototype = { - addShapeToModifiers: function (data) { - var i; - var len = this.shapeModifiers.length; - for (i = 0; i < len; i += 1) { - this.shapeModifiers[i].addShape(data); - } - }, - isShapeInAnimatedModifiers: function (data) { - var i = 0; - var len = this.shapeModifiers.length; - while (i < len) { - if (this.shapeModifiers[i].isAnimatedWithShape(data)) { - return true; - } - } - return false; - }, - renderModifiers: function () { - if (!this.shapeModifiers.length) { - return; - } - var i; - var len = this.shapes.length; - for (i = 0; i < len; i += 1) { - this.shapes[i].sh.reset(); - } - - len = this.shapeModifiers.length; - var shouldBreakProcess; - for (i = len - 1; i >= 0; i -= 1) { - shouldBreakProcess = this.shapeModifiers[i].processShapes(this._isFirstFrame); - // workaround to fix cases where a repeater resets the shape so the following processes get called twice - // TODO: find a better solution for this - if (shouldBreakProcess) { - break; - } - } - }, - - searchProcessedElement: function (elem) { - var elements = this.processedElements; - var i = 0; - var len = elements.length; - while (i < len) { - if (elements[i].elem === elem) { - return elements[i].pos; - } - i += 1; - } - return 0; - }, - addProcessedElement: function (elem, pos) { - var elements = this.processedElements; - var i = elements.length; - while (i) { - i -= 1; - if (elements[i].elem === elem) { - elements[i].pos = pos; - return; - } - } - elements.push(new ProcessedElement(elem, pos)); - }, - prepareFrame: function (num) { - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - }, - }; - - const lineCapEnum = { - 1: 'butt', - 2: 'round', - 3: 'square', - }; - - const lineJoinEnum = { - 1: 'miter', - 2: 'round', - 3: 'bevel', - }; - - function SVGShapeData(transformers, level, shape) { - this.caches = []; - this.styles = []; - this.transformers = transformers; - this.lStr = ''; - this.sh = shape; - this.lvl = level; - // TODO find if there are some cases where _isAnimated can be false. - // For now, since shapes add up with other shapes. They have to be calculated every time. - // One way of finding out is checking if all styles associated to this shape depend only of this shape - this._isAnimated = !!shape.k; - // TODO: commenting this for now since all shapes are animated - var i = 0; - var len = transformers.length; - while (i < len) { - if (transformers[i].mProps.dynamicProperties.length) { - this._isAnimated = true; - break; - } - i += 1; - } - } - - SVGShapeData.prototype.setAsAnimated = function () { - this._isAnimated = true; - }; - - function SVGStyleData(data, level) { - this.data = data; - this.type = data.ty; - this.d = ''; - this.lvl = level; - this._mdf = false; - this.closed = data.hd === true; - this.pElem = createNS('path'); - this.msElem = null; - } - - SVGStyleData.prototype.reset = function () { - this.d = ''; - this._mdf = false; - }; - - function DashProperty(elem, data, renderer, container) { - this.elem = elem; - this.frameId = -1; - this.dataProps = createSizedArray(data.length); - this.renderer = renderer; - this.k = false; - this.dashStr = ''; - this.dashArray = createTypedArray('float32', data.length ? data.length - 1 : 0); - this.dashoffset = createTypedArray('float32', 1); - this.initDynamicPropertyContainer(container); - var i; - var len = data.length || 0; - var prop; - for (i = 0; i < len; i += 1) { - prop = PropertyFactory.getProp(elem, data[i].v, 0, 0, this); - this.k = prop.k || this.k; - this.dataProps[i] = { n: data[i].n, p: prop }; - } - if (!this.k) { - this.getValue(true); - } - this._isAnimated = this.k; - } - - DashProperty.prototype.getValue = function (forceRender) { - if (this.elem.globalData.frameId === this.frameId && !forceRender) { - return; - } - this.frameId = this.elem.globalData.frameId; - this.iterateDynamicProperties(); - this._mdf = this._mdf || forceRender; - if (this._mdf) { - var i = 0; - var len = this.dataProps.length; - if (this.renderer === 'svg') { - this.dashStr = ''; - } - for (i = 0; i < len; i += 1) { - if (this.dataProps[i].n !== 'o') { - if (this.renderer === 'svg') { - this.dashStr += ' ' + this.dataProps[i].p.v; - } else { - this.dashArray[i] = this.dataProps[i].p.v; - } - } else { - this.dashoffset[0] = this.dataProps[i].p.v; - } - } - } - }; - extendPrototype([DynamicPropertyContainer], DashProperty); - - function SVGStrokeStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); - this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); - this.d = new DashProperty(elem, data.d || {}, 'svg', this); - this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); - this.style = styleOb; - this._isAnimated = !!this._isAnimated; - } - - extendPrototype([DynamicPropertyContainer], SVGStrokeStyleData); - - function SVGFillStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); - this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this); - this.style = styleOb; - } - - extendPrototype([DynamicPropertyContainer], SVGFillStyleData); - - function SVGNoStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.style = styleOb; - } - - extendPrototype([DynamicPropertyContainer], SVGNoStyleData); - - function GradientProperty(elem, data, container) { - this.data = data; - this.c = createTypedArray('uint8c', data.p * 4); - var cLength = data.k.k[0].s ? (data.k.k[0].s.length - data.p * 4) : data.k.k.length - data.p * 4; - this.o = createTypedArray('float32', cLength); - this._cmdf = false; - this._omdf = false; - this._collapsable = this.checkCollapsable(); - this._hasOpacity = cLength; - this.initDynamicPropertyContainer(container); - this.prop = PropertyFactory.getProp(elem, data.k, 1, null, this); - this.k = this.prop.k; - this.getValue(true); - } - - GradientProperty.prototype.comparePoints = function (values, points) { - var i = 0; - var len = this.o.length / 2; - var diff; - while (i < len) { - diff = Math.abs(values[i * 4] - values[points * 4 + i * 2]); - if (diff > 0.01) { - return false; - } - i += 1; - } - return true; - }; - - GradientProperty.prototype.checkCollapsable = function () { - if (this.o.length / 2 !== this.c.length / 4) { - return false; - } - if (this.data.k.k[0].s) { - var i = 0; - var len = this.data.k.k.length; - while (i < len) { - if (!this.comparePoints(this.data.k.k[i].s, this.data.p)) { - return false; - } - i += 1; - } - } else if (!this.comparePoints(this.data.k.k, this.data.p)) { - return false; - } - return true; - }; - - GradientProperty.prototype.getValue = function (forceRender) { - this.prop.getValue(); - this._mdf = false; - this._cmdf = false; - this._omdf = false; - if (this.prop._mdf || forceRender) { - var i; - var len = this.data.p * 4; - var mult; - var val; - for (i = 0; i < len; i += 1) { - mult = i % 4 === 0 ? 100 : 255; - val = Math.round(this.prop.v[i] * mult); - if (this.c[i] !== val) { - this.c[i] = val; - this._cmdf = !forceRender; - } - } - if (this.o.length) { - len = this.prop.v.length; - for (i = this.data.p * 4; i < len; i += 1) { - mult = i % 2 === 0 ? 100 : 1; - val = i % 2 === 0 ? Math.round(this.prop.v[i] * 100) : this.prop.v[i]; - if (this.o[i - this.data.p * 4] !== val) { - this.o[i - this.data.p * 4] = val; - this._omdf = !forceRender; - } - } - } - this._mdf = !forceRender; - } - }; - - extendPrototype([DynamicPropertyContainer], GradientProperty); - - function SVGGradientFillStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.initGradientData(elem, data, styleOb); - } - - SVGGradientFillStyleData.prototype.initGradientData = function (elem, data, styleOb) { - this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this); - this.s = PropertyFactory.getProp(elem, data.s, 1, null, this); - this.e = PropertyFactory.getProp(elem, data.e, 1, null, this); - this.h = PropertyFactory.getProp(elem, data.h || { k: 0 }, 0, 0.01, this); - this.a = PropertyFactory.getProp(elem, data.a || { k: 0 }, 0, degToRads, this); - this.g = new GradientProperty(elem, data.g, this); - this.style = styleOb; - this.stops = []; - this.setGradientData(styleOb.pElem, data); - this.setGradientOpacity(data, styleOb); - this._isAnimated = !!this._isAnimated; - }; - - SVGGradientFillStyleData.prototype.setGradientData = function (pathElement, data) { - var gradientId = createElementID(); - var gfill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); - gfill.setAttribute('id', gradientId); - gfill.setAttribute('spreadMethod', 'pad'); - gfill.setAttribute('gradientUnits', 'userSpaceOnUse'); - var stops = []; - var stop; - var j; - var jLen; - jLen = data.g.p * 4; - for (j = 0; j < jLen; j += 4) { - stop = createNS('stop'); - gfill.appendChild(stop); - stops.push(stop); - } - pathElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + gradientId + ')'); - this.gf = gfill; - this.cst = stops; - }; - - SVGGradientFillStyleData.prototype.setGradientOpacity = function (data, styleOb) { - if (this.g._hasOpacity && !this.g._collapsable) { - var stop; - var j; - var jLen; - var mask = createNS('mask'); - var maskElement = createNS('path'); - mask.appendChild(maskElement); - var opacityId = createElementID(); - var maskId = createElementID(); - mask.setAttribute('id', maskId); - var opFill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient'); - opFill.setAttribute('id', opacityId); - opFill.setAttribute('spreadMethod', 'pad'); - opFill.setAttribute('gradientUnits', 'userSpaceOnUse'); - jLen = data.g.k.k[0].s ? data.g.k.k[0].s.length : data.g.k.k.length; - var stops = this.stops; - for (j = data.g.p * 4; j < jLen; j += 2) { - stop = createNS('stop'); - stop.setAttribute('stop-color', 'rgb(255,255,255)'); - opFill.appendChild(stop); - stops.push(stop); - } - maskElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + opacityId + ')'); - if (data.ty === 'gs') { - maskElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); - maskElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); - if (data.lj === 1) { - maskElement.setAttribute('stroke-miterlimit', data.ml); - } - } - this.of = opFill; - this.ms = mask; - this.ost = stops; - this.maskId = maskId; - styleOb.msElem = maskElement; - } - }; - - extendPrototype([DynamicPropertyContainer], SVGGradientFillStyleData); - - function SVGGradientStrokeStyleData(elem, data, styleOb) { - this.initDynamicPropertyContainer(elem); - this.getValue = this.iterateDynamicProperties; - this.w = PropertyFactory.getProp(elem, data.w, 0, null, this); - this.d = new DashProperty(elem, data.d || {}, 'svg', this); - this.initGradientData(elem, data, styleOb); - this._isAnimated = !!this._isAnimated; - } - - extendPrototype([SVGGradientFillStyleData, DynamicPropertyContainer], SVGGradientStrokeStyleData); - - function ShapeGroupData() { - this.it = []; - this.prevViewData = []; - this.gr = createNS('g'); - } - - function SVGTransformData(mProps, op, container) { - this.transform = { - mProps: mProps, - op: op, - container: container, - }; - this.elements = []; - this._isAnimated = this.transform.mProps.dynamicProperties.length || this.transform.op.effectsSequence.length; - } - - const buildShapeString = function (pathNodes, length, closed, mat) { - if (length === 0) { - return ''; - } - var _o = pathNodes.o; - var _i = pathNodes.i; - var _v = pathNodes.v; - var i; - var shapeString = ' M' + mat.applyToPointStringified(_v[0][0], _v[0][1]); - for (i = 1; i < length; i += 1) { - shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[i][0], _i[i][1]) + ' ' + mat.applyToPointStringified(_v[i][0], _v[i][1]); - } - if (closed && length) { - shapeString += ' C' + mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) + ' ' + mat.applyToPointStringified(_i[0][0], _i[0][1]) + ' ' + mat.applyToPointStringified(_v[0][0], _v[0][1]); - shapeString += 'z'; - } - return shapeString; - }; - - const SVGElementsRenderer = (function () { - var _identityMatrix = new Matrix(); - var _matrixHelper = new Matrix(); - - var ob = { - createRenderFunction: createRenderFunction, - }; - - function createRenderFunction(data) { - switch (data.ty) { - case 'fl': - return renderFill; - case 'gf': - return renderGradient; - case 'gs': - return renderGradientStroke; - case 'st': - return renderStroke; - case 'sh': - case 'el': - case 'rc': - case 'sr': - return renderPath; - case 'tr': - return renderContentTransform; - case 'no': - return renderNoop; - default: - return null; - } - } - - function renderContentTransform(styleData, itemData, isFirstFrame) { - if (isFirstFrame || itemData.transform.op._mdf) { - itemData.transform.container.setAttribute('opacity', itemData.transform.op.v); - } - if (isFirstFrame || itemData.transform.mProps._mdf) { - itemData.transform.container.setAttribute('transform', itemData.transform.mProps.v.to2dCSS()); - } - } - - function renderNoop() { - - } - - function renderPath(styleData, itemData, isFirstFrame) { - var j; - var jLen; - var pathStringTransformed; - var redraw; - var pathNodes; - var l; - var lLen = itemData.styles.length; - var lvl = itemData.lvl; - var paths; - var mat; - var props; - var iterations; - var k; - for (l = 0; l < lLen; l += 1) { - redraw = itemData.sh._mdf || isFirstFrame; - if (itemData.styles[l].lvl < lvl) { - mat = _matrixHelper.reset(); - iterations = lvl - itemData.styles[l].lvl; - k = itemData.transformers.length - 1; - while (!redraw && iterations > 0) { - redraw = itemData.transformers[k].mProps._mdf || redraw; - iterations -= 1; - k -= 1; - } - if (redraw) { - iterations = lvl - itemData.styles[l].lvl; - k = itemData.transformers.length - 1; - while (iterations > 0) { - props = itemData.transformers[k].mProps.v.props; - mat.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); - iterations -= 1; - k -= 1; - } - } - } else { - mat = _identityMatrix; - } - paths = itemData.sh.paths; - jLen = paths._length; - if (redraw) { - pathStringTransformed = ''; - for (j = 0; j < jLen; j += 1) { - pathNodes = paths.shapes[j]; - if (pathNodes && pathNodes._length) { - pathStringTransformed += buildShapeString(pathNodes, pathNodes._length, pathNodes.c, mat); - } - } - itemData.caches[l] = pathStringTransformed; - } else { - pathStringTransformed = itemData.caches[l]; - } - itemData.styles[l].d += styleData.hd === true ? '' : pathStringTransformed; - itemData.styles[l]._mdf = redraw || itemData.styles[l]._mdf; - } - } - - function renderFill(styleData, itemData, isFirstFrame) { - var styleElem = itemData.style; - - if (itemData.c._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('fill', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); - } - if (itemData.o._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('fill-opacity', itemData.o.v); - } - } - - function renderGradientStroke(styleData, itemData, isFirstFrame) { - renderGradient(styleData, itemData, isFirstFrame); - renderStroke(styleData, itemData, isFirstFrame); - } - - function renderGradient(styleData, itemData, isFirstFrame) { - var gfill = itemData.gf; - var hasOpacity = itemData.g._hasOpacity; - var pt1 = itemData.s.v; - var pt2 = itemData.e.v; - - if (itemData.o._mdf || isFirstFrame) { - var attr = styleData.ty === 'gf' ? 'fill-opacity' : 'stroke-opacity'; - itemData.style.pElem.setAttribute(attr, itemData.o.v); - } - if (itemData.s._mdf || isFirstFrame) { - var attr1 = styleData.t === 1 ? 'x1' : 'cx'; - var attr2 = attr1 === 'x1' ? 'y1' : 'cy'; - gfill.setAttribute(attr1, pt1[0]); - gfill.setAttribute(attr2, pt1[1]); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute(attr1, pt1[0]); - itemData.of.setAttribute(attr2, pt1[1]); - } - } - var stops; - var i; - var len; - var stop; - if (itemData.g._cmdf || isFirstFrame) { - stops = itemData.cst; - var cValues = itemData.g.c; - len = stops.length; - for (i = 0; i < len; i += 1) { - stop = stops[i]; - stop.setAttribute('offset', cValues[i * 4] + '%'); - stop.setAttribute('stop-color', 'rgb(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ')'); - } - } - if (hasOpacity && (itemData.g._omdf || isFirstFrame)) { - var oValues = itemData.g.o; - if (itemData.g._collapsable) { - stops = itemData.cst; - } else { - stops = itemData.ost; - } - len = stops.length; - for (i = 0; i < len; i += 1) { - stop = stops[i]; - if (!itemData.g._collapsable) { - stop.setAttribute('offset', oValues[i * 2] + '%'); - } - stop.setAttribute('stop-opacity', oValues[i * 2 + 1]); - } - } - if (styleData.t === 1) { - if (itemData.e._mdf || isFirstFrame) { - gfill.setAttribute('x2', pt2[0]); - gfill.setAttribute('y2', pt2[1]); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute('x2', pt2[0]); - itemData.of.setAttribute('y2', pt2[1]); - } - } - } else { - var rad; - if (itemData.s._mdf || itemData.e._mdf || isFirstFrame) { - rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); - gfill.setAttribute('r', rad); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute('r', rad); - } - } - if (itemData.e._mdf || itemData.h._mdf || itemData.a._mdf || isFirstFrame) { - if (!rad) { - rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); - } - var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); - - var percent = itemData.h.v; - if (percent >= 1) { - percent = 0.99; - } else if (percent <= -1) { - percent = -0.99; - } - var dist = rad * percent; - var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; - var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; - gfill.setAttribute('fx', x); - gfill.setAttribute('fy', y); - if (hasOpacity && !itemData.g._collapsable) { - itemData.of.setAttribute('fx', x); - itemData.of.setAttribute('fy', y); - } - } - // gfill.setAttribute('fy','200'); - } - } - - function renderStroke(styleData, itemData, isFirstFrame) { - var styleElem = itemData.style; - var d = itemData.d; - if (d && (d._mdf || isFirstFrame) && d.dashStr) { - styleElem.pElem.setAttribute('stroke-dasharray', d.dashStr); - styleElem.pElem.setAttribute('stroke-dashoffset', d.dashoffset[0]); - } - if (itemData.c && (itemData.c._mdf || isFirstFrame)) { - styleElem.pElem.setAttribute('stroke', 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'); - } - if (itemData.o._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('stroke-opacity', itemData.o.v); - } - if (itemData.w._mdf || isFirstFrame) { - styleElem.pElem.setAttribute('stroke-width', itemData.w.v); - if (styleElem.msElem) { - styleElem.msElem.setAttribute('stroke-width', itemData.w.v); - } - } - } - - return ob; - }()); - - function SVGShapeElement(data, globalData, comp) { - // List of drawable elements - this.shapes = []; - // Full shape data - this.shapesData = data.shapes; - // List of styles that will be applied to shapes - this.stylesList = []; - // List of modifiers that will be applied to shapes - this.shapeModifiers = []; - // List of items in shape tree - this.itemsData = []; - // List of items in previous shape tree - this.processedElements = []; - // List of animated components - this.animatedContents = []; - this.initElement(data, globalData, comp); - // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. - // List of elements that have been created - this.prevViewData = []; - // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. - } - - extendPrototype([BaseElement, TransformElement, SVGBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableDOMElement], SVGShapeElement); - - SVGShapeElement.prototype.initSecondaryElement = function () { - }; - - SVGShapeElement.prototype.identityMatrix = new Matrix(); - - SVGShapeElement.prototype.buildExpressionInterface = function () {}; - - SVGShapeElement.prototype.createContent = function () { - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); - this.filterUniqueShapes(); - }; - - /* - This method searches for multiple shapes that affect a single element and one of them is animated - */ - SVGShapeElement.prototype.filterUniqueShapes = function () { - var i; - var len = this.shapes.length; - var shape; - var j; - var jLen = this.stylesList.length; - var style; - var tempShapes = []; - var areAnimated = false; - for (j = 0; j < jLen; j += 1) { - style = this.stylesList[j]; - areAnimated = false; - tempShapes.length = 0; - for (i = 0; i < len; i += 1) { - shape = this.shapes[i]; - if (shape.styles.indexOf(style) !== -1) { - tempShapes.push(shape); - areAnimated = shape._isAnimated || areAnimated; - } - } - if (tempShapes.length > 1 && areAnimated) { - this.setShapesAsAnimated(tempShapes); - } - } - }; - - SVGShapeElement.prototype.setShapesAsAnimated = function (shapes) { - var i; - var len = shapes.length; - for (i = 0; i < len; i += 1) { - shapes[i].setAsAnimated(); - } - }; - - SVGShapeElement.prototype.createStyleElement = function (data, level) { - // TODO: prevent drawing of hidden styles - var elementData; - var styleOb = new SVGStyleData(data, level); - - var pathElement = styleOb.pElem; - if (data.ty === 'st') { - elementData = new SVGStrokeStyleData(this, data, styleOb); - } else if (data.ty === 'fl') { - elementData = new SVGFillStyleData(this, data, styleOb); - } else if (data.ty === 'gf' || data.ty === 'gs') { - var GradientConstructor = data.ty === 'gf' ? SVGGradientFillStyleData : SVGGradientStrokeStyleData; - elementData = new GradientConstructor(this, data, styleOb); - this.globalData.defs.appendChild(elementData.gf); - if (elementData.maskId) { - this.globalData.defs.appendChild(elementData.ms); - this.globalData.defs.appendChild(elementData.of); - pathElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + elementData.maskId + ')'); - } - } else if (data.ty === 'no') { - elementData = new SVGNoStyleData(this, data, styleOb); - } - - if (data.ty === 'st' || data.ty === 'gs') { - pathElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2]); - pathElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2]); - pathElement.setAttribute('fill-opacity', '0'); - if (data.lj === 1) { - pathElement.setAttribute('stroke-miterlimit', data.ml); - } - } - - if (data.r === 2) { - pathElement.setAttribute('fill-rule', 'evenodd'); - } - - if (data.ln) { - pathElement.setAttribute('id', data.ln); - } - if (data.cl) { - pathElement.setAttribute('class', data.cl); - } - if (data.bm) { - pathElement.style['mix-blend-mode'] = getBlendMode(data.bm); - } - this.stylesList.push(styleOb); - this.addToAnimatedContents(data, elementData); - return elementData; - }; - - SVGShapeElement.prototype.createGroupElement = function (data) { - var elementData = new ShapeGroupData(); - if (data.ln) { - elementData.gr.setAttribute('id', data.ln); - } - if (data.cl) { - elementData.gr.setAttribute('class', data.cl); - } - if (data.bm) { - elementData.gr.style['mix-blend-mode'] = getBlendMode(data.bm); - } - return elementData; - }; - - SVGShapeElement.prototype.createTransformElement = function (data, container) { - var transformProperty = TransformPropertyFactory.getTransformProperty(this, data, this); - var elementData = new SVGTransformData(transformProperty, transformProperty.o, container); - this.addToAnimatedContents(data, elementData); - return elementData; - }; - - SVGShapeElement.prototype.createShapeElement = function (data, ownTransformers, level) { - var ty = 4; - if (data.ty === 'rc') { - ty = 5; - } else if (data.ty === 'el') { - ty = 6; - } else if (data.ty === 'sr') { - ty = 7; - } - var shapeProperty = ShapePropertyFactory.getShapeProp(this, data, ty, this); - var elementData = new SVGShapeData(ownTransformers, level, shapeProperty); - this.shapes.push(elementData); - this.addShapeToModifiers(elementData); - this.addToAnimatedContents(data, elementData); - return elementData; - }; - - SVGShapeElement.prototype.addToAnimatedContents = function (data, element) { - var i = 0; - var len = this.animatedContents.length; - while (i < len) { - if (this.animatedContents[i].element === element) { - return; - } - i += 1; - } - this.animatedContents.push({ - fn: SVGElementsRenderer.createRenderFunction(data), - element: element, - data: data, - }); - }; - - SVGShapeElement.prototype.setElementStyles = function (elementData) { - var arr = elementData.styles; - var j; - var jLen = this.stylesList.length; - for (j = 0; j < jLen; j += 1) { - if (!this.stylesList[j].closed) { - arr.push(this.stylesList[j]); - } - } - }; - - SVGShapeElement.prototype.reloadShapes = function () { - this._isFirstFrame = true; - var i; - var len = this.itemsData.length; - for (i = 0; i < len; i += 1) { - this.prevViewData[i] = this.itemsData[i]; - } - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true); - this.filterUniqueShapes(); - len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - this.dynamicProperties[i].getValue(); - } - this.renderModifiers(); - }; - - SVGShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, container, level, transformers, render) { - var ownTransformers = [].concat(transformers); - var i; - var len = arr.length - 1; - var j; - var jLen; - var ownStyles = []; - var ownModifiers = []; - var currentTransform; - var modifier; - var processedPos; - for (i = len; i >= 0; i -= 1) { - processedPos = this.searchProcessedElement(arr[i]); - if (!processedPos) { - arr[i]._render = render; - } else { - itemsData[i] = prevViewData[processedPos - 1]; - } - if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs' || arr[i].ty === 'no') { - if (!processedPos) { - itemsData[i] = this.createStyleElement(arr[i], level); - } else { - itemsData[i].style.closed = false; - } - if (arr[i]._render) { - if (itemsData[i].style.pElem.parentNode !== container) { - container.appendChild(itemsData[i].style.pElem); - } - } - ownStyles.push(itemsData[i].style); - } else if (arr[i].ty === 'gr') { - if (!processedPos) { - itemsData[i] = this.createGroupElement(arr[i]); - } else { - jLen = itemsData[i].it.length; - for (j = 0; j < jLen; j += 1) { - itemsData[i].prevViewData[j] = itemsData[i].it[j]; - } - } - this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, itemsData[i].gr, level + 1, ownTransformers, render); - if (arr[i]._render) { - if (itemsData[i].gr.parentNode !== container) { - container.appendChild(itemsData[i].gr); - } - } - } else if (arr[i].ty === 'tr') { - if (!processedPos) { - itemsData[i] = this.createTransformElement(arr[i], container); - } - currentTransform = itemsData[i].transform; - ownTransformers.push(currentTransform); - } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { - if (!processedPos) { - itemsData[i] = this.createShapeElement(arr[i], ownTransformers, level); - } - this.setElementStyles(itemsData[i]); - } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'ms' || arr[i].ty === 'pb') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - modifier.init(this, arr[i]); - itemsData[i] = modifier; - this.shapeModifiers.push(modifier); - } else { - modifier = itemsData[i]; - modifier.closed = false; - } - ownModifiers.push(modifier); - } else if (arr[i].ty === 'rp') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - itemsData[i] = modifier; - modifier.init(this, arr, i, itemsData); - this.shapeModifiers.push(modifier); - render = false; - } else { - modifier = itemsData[i]; - modifier.closed = true; - } - ownModifiers.push(modifier); - } - this.addProcessedElement(arr[i], i + 1); - } - len = ownStyles.length; - for (i = 0; i < len; i += 1) { - ownStyles[i].closed = true; - } - len = ownModifiers.length; - for (i = 0; i < len; i += 1) { - ownModifiers[i].closed = true; - } - }; - - SVGShapeElement.prototype.renderInnerContent = function () { - this.renderModifiers(); - var i; - var len = this.stylesList.length; - for (i = 0; i < len; i += 1) { - this.stylesList[i].reset(); - } - this.renderShape(); - for (i = 0; i < len; i += 1) { - if (this.stylesList[i]._mdf || this._isFirstFrame) { - if (this.stylesList[i].msElem) { - this.stylesList[i].msElem.setAttribute('d', this.stylesList[i].d); - // Adding M0 0 fixes same mask bug on all browsers - this.stylesList[i].d = 'M0 0' + this.stylesList[i].d; - } - this.stylesList[i].pElem.setAttribute('d', this.stylesList[i].d || 'M0 0'); - } - } - }; - - SVGShapeElement.prototype.renderShape = function () { - var i; - var len = this.animatedContents.length; - var animatedContent; - for (i = 0; i < len; i += 1) { - animatedContent = this.animatedContents[i]; - if ((this._isFirstFrame || animatedContent.element._isAnimated) && animatedContent.data !== true) { - animatedContent.fn(animatedContent.data, animatedContent.element, this._isFirstFrame); - } - } - }; - - SVGShapeElement.prototype.destroy = function () { - this.destroyBaseElement(); - this.shapesData = null; - this.itemsData = null; - }; - - function LetterProps(o, sw, sc, fc, m, p) { - this.o = o; - this.sw = sw; - this.sc = sc; - this.fc = fc; - this.m = m; - this.p = p; - this._mdf = { - o: true, - sw: !!sw, - sc: !!sc, - fc: !!fc, - m: true, - p: true, - }; - } - - LetterProps.prototype.update = function (o, sw, sc, fc, m, p) { - this._mdf.o = false; - this._mdf.sw = false; - this._mdf.sc = false; - this._mdf.fc = false; - this._mdf.m = false; - this._mdf.p = false; - var updated = false; - - if (this.o !== o) { - this.o = o; - this._mdf.o = true; - updated = true; - } - if (this.sw !== sw) { - this.sw = sw; - this._mdf.sw = true; - updated = true; - } - if (this.sc !== sc) { - this.sc = sc; - this._mdf.sc = true; - updated = true; - } - if (this.fc !== fc) { - this.fc = fc; - this._mdf.fc = true; - updated = true; - } - if (this.m !== m) { - this.m = m; - this._mdf.m = true; - updated = true; - } - if (p.length && (this.p[0] !== p[0] || this.p[1] !== p[1] || this.p[4] !== p[4] || this.p[5] !== p[5] || this.p[12] !== p[12] || this.p[13] !== p[13])) { - this.p = p; - this._mdf.p = true; - updated = true; - } - return updated; - }; - - function TextProperty(elem, data) { - this._frameId = initialDefaultFrame; - this.pv = ''; - this.v = ''; - this.kf = false; - this._isFirstFrame = true; - this._mdf = false; - this.data = data; - this.elem = elem; - this.comp = this.elem.comp; - this.keysIndex = 0; - this.canResize = false; - this.minimumFontSize = 1; - this.effectsSequence = []; - this.currentData = { - ascent: 0, - boxWidth: this.defaultBoxWidth, - f: '', - fStyle: '', - fWeight: '', - fc: '', - j: '', - justifyOffset: '', - l: [], - lh: 0, - lineWidths: [], - ls: '', - of: '', - s: '', - sc: '', - sw: 0, - t: 0, - tr: 0, - sz: 0, - ps: null, - fillColorAnim: false, - strokeColorAnim: false, - strokeWidthAnim: false, - yOffset: 0, - finalSize: 0, - finalText: [], - finalLineHeight: 0, - __complete: false, - - }; - this.copyData(this.currentData, this.data.d.k[0].s); - - if (!this.searchProperty()) { - this.completeTextData(this.currentData); - } - } - - TextProperty.prototype.defaultBoxWidth = [0, 0]; - - TextProperty.prototype.copyData = function (obj, data) { - for (var s in data) { - if (Object.prototype.hasOwnProperty.call(data, s)) { - obj[s] = data[s]; - } - } - return obj; - }; - - TextProperty.prototype.setCurrentData = function (data) { - if (!data.__complete) { - this.completeTextData(data); - } - this.currentData = data; - this.currentData.boxWidth = this.currentData.boxWidth || this.defaultBoxWidth; - this._mdf = true; - }; - - TextProperty.prototype.searchProperty = function () { - return this.searchKeyframes(); - }; - - TextProperty.prototype.searchKeyframes = function () { - this.kf = this.data.d.k.length > 1; - if (this.kf) { - this.addEffect(this.getKeyframeValue.bind(this)); - } - return this.kf; - }; - - TextProperty.prototype.addEffect = function (effectFunction) { - this.effectsSequence.push(effectFunction); - this.elem.addDynamicProperty(this); - }; - - TextProperty.prototype.getValue = function (_finalValue) { - if ((this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) && !_finalValue) { - return; - } - this.currentData.t = this.data.d.k[this.keysIndex].s.t; - var currentValue = this.currentData; - var currentIndex = this.keysIndex; - if (this.lock) { - this.setCurrentData(this.currentData); - return; - } - this.lock = true; - this._mdf = false; - var i; var - len = this.effectsSequence.length; - var finalValue = _finalValue || this.data.d.k[this.keysIndex].s; - for (i = 0; i < len; i += 1) { - // Checking if index changed to prevent creating a new object every time the expression updates. - if (currentIndex !== this.keysIndex) { - finalValue = this.effectsSequence[i](finalValue, finalValue.t); - } else { - finalValue = this.effectsSequence[i](this.currentData, finalValue.t); - } - } - if (currentValue !== finalValue) { - this.setCurrentData(finalValue); - } - this.v = this.currentData; - this.pv = this.v; - this.lock = false; - this.frameId = this.elem.globalData.frameId; - }; - - TextProperty.prototype.getKeyframeValue = function () { - var textKeys = this.data.d.k; - var frameNum = this.elem.comp.renderedFrame; - var i = 0; var - len = textKeys.length; - while (i <= len - 1) { - if (i === len - 1 || textKeys[i + 1].t > frameNum) { - break; - } - i += 1; - } - if (this.keysIndex !== i) { - this.keysIndex = i; - } - return this.data.d.k[this.keysIndex].s; - }; - - TextProperty.prototype.buildFinalText = function (text) { - var charactersArray = []; - var i = 0; - var len = text.length; - var charCode; - var secondCharCode; - var shouldCombine = false; - while (i < len) { - charCode = text.charCodeAt(i); - if (FontManager.isCombinedCharacter(charCode)) { - charactersArray[charactersArray.length - 1] += text.charAt(i); - } else if (charCode >= 0xD800 && charCode <= 0xDBFF) { - secondCharCode = text.charCodeAt(i + 1); - if (secondCharCode >= 0xDC00 && secondCharCode <= 0xDFFF) { - if (shouldCombine || FontManager.isModifier(charCode, secondCharCode)) { - charactersArray[charactersArray.length - 1] += text.substr(i, 2); - shouldCombine = false; - } else { - charactersArray.push(text.substr(i, 2)); - } - i += 1; - } else { - charactersArray.push(text.charAt(i)); - } - } else if (charCode > 0xDBFF) { - secondCharCode = text.charCodeAt(i + 1); - if (FontManager.isZeroWidthJoiner(charCode, secondCharCode)) { - shouldCombine = true; - charactersArray[charactersArray.length - 1] += text.substr(i, 2); - i += 1; - } else { - charactersArray.push(text.charAt(i)); - } - } else if (FontManager.isZeroWidthJoiner(charCode)) { - charactersArray[charactersArray.length - 1] += text.charAt(i); - shouldCombine = true; - } else { - charactersArray.push(text.charAt(i)); - } - i += 1; - } - return charactersArray; - }; - - TextProperty.prototype.completeTextData = function (documentData) { - documentData.__complete = true; - var fontManager = this.elem.globalData.fontManager; - var data = this.data; - var letters = []; - var i; var - len; - var newLineFlag; var index = 0; var - val; - var anchorGrouping = data.m.g; - var currentSize = 0; var currentPos = 0; var currentLine = 0; var - lineWidths = []; - var lineWidth = 0; - var maxLineWidth = 0; - var j; var - jLen; - var fontData = fontManager.getFontByName(documentData.f); - var charData; var - cLength = 0; - - var fontProps = getFontProperties(fontData); - documentData.fWeight = fontProps.weight; - documentData.fStyle = fontProps.style; - documentData.finalSize = documentData.s; - documentData.finalText = this.buildFinalText(documentData.t); - len = documentData.finalText.length; - documentData.finalLineHeight = documentData.lh; - var trackingOffset = (documentData.tr / 1000) * documentData.finalSize; - var charCode; - if (documentData.sz) { - var flag = true; - var boxWidth = documentData.sz[0]; - var boxHeight = documentData.sz[1]; - var currentHeight; var - finalText; - while (flag) { - finalText = this.buildFinalText(documentData.t); - currentHeight = 0; - lineWidth = 0; - len = finalText.length; - trackingOffset = (documentData.tr / 1000) * documentData.finalSize; - var lastSpaceIndex = -1; - for (i = 0; i < len; i += 1) { - charCode = finalText[i].charCodeAt(0); - newLineFlag = false; - if (finalText[i] === ' ') { - lastSpaceIndex = i; - } else if (charCode === 13 || charCode === 3) { - lineWidth = 0; - newLineFlag = true; - currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; - } - if (fontManager.chars) { - charData = fontManager.getCharData(finalText[i], fontData.fStyle, fontData.fFamily); - cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; - } else { - // tCanvasHelper.font = documentData.s + 'px '+ fontData.fFamily; - cLength = fontManager.measureText(finalText[i], documentData.f, documentData.finalSize); - } - if (lineWidth + cLength > boxWidth && finalText[i] !== ' ') { - if (lastSpaceIndex === -1) { - len += 1; - } else { - i = lastSpaceIndex; - } - currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2; - finalText.splice(i, lastSpaceIndex === i ? 1 : 0, '\r'); - // finalText = finalText.substr(0,i) + "\r" + finalText.substr(i === lastSpaceIndex ? i + 1 : i); - lastSpaceIndex = -1; - lineWidth = 0; - } else { - lineWidth += cLength; - lineWidth += trackingOffset; - } - } - currentHeight += (fontData.ascent * documentData.finalSize) / 100; - if (this.canResize && documentData.finalSize > this.minimumFontSize && boxHeight < currentHeight) { - documentData.finalSize -= 1; - documentData.finalLineHeight = (documentData.finalSize * documentData.lh) / documentData.s; - } else { - documentData.finalText = finalText; - len = documentData.finalText.length; - flag = false; - } - } - } - lineWidth = -trackingOffset; - cLength = 0; - var uncollapsedSpaces = 0; - var currentChar; - for (i = 0; i < len; i += 1) { - newLineFlag = false; - currentChar = documentData.finalText[i]; - charCode = currentChar.charCodeAt(0); - if (charCode === 13 || charCode === 3) { - uncollapsedSpaces = 0; - lineWidths.push(lineWidth); - maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; - lineWidth = -2 * trackingOffset; - val = ''; - newLineFlag = true; - currentLine += 1; - } else { - val = currentChar; - } - if (fontManager.chars) { - charData = fontManager.getCharData(currentChar, fontData.fStyle, fontManager.getFontByName(documentData.f).fFamily); - cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100; - } else { - // var charWidth = fontManager.measureText(val, documentData.f, documentData.finalSize); - // tCanvasHelper.font = documentData.finalSize + 'px '+ fontManager.getFontByName(documentData.f).fFamily; - cLength = fontManager.measureText(val, documentData.f, documentData.finalSize); - } - - // - if (currentChar === ' ') { - uncollapsedSpaces += cLength + trackingOffset; - } else { - lineWidth += cLength + trackingOffset + uncollapsedSpaces; - uncollapsedSpaces = 0; - } - letters.push({ - l: cLength, an: cLength, add: currentSize, n: newLineFlag, anIndexes: [], val: val, line: currentLine, animatorJustifyOffset: 0, - }); - if (anchorGrouping == 2) { // eslint-disable-line eqeqeq - currentSize += cLength; - if (val === '' || val === ' ' || i === len - 1) { - if (val === '' || val === ' ') { - currentSize -= cLength; - } - while (currentPos <= i) { - letters[currentPos].an = currentSize; - letters[currentPos].ind = index; - letters[currentPos].extra = cLength; - currentPos += 1; - } - index += 1; - currentSize = 0; - } - } else if (anchorGrouping == 3) { // eslint-disable-line eqeqeq - currentSize += cLength; - if (val === '' || i === len - 1) { - if (val === '') { - currentSize -= cLength; - } - while (currentPos <= i) { - letters[currentPos].an = currentSize; - letters[currentPos].ind = index; - letters[currentPos].extra = cLength; - currentPos += 1; - } - currentSize = 0; - index += 1; - } - } else { - letters[index].ind = index; - letters[index].extra = 0; - index += 1; - } - } - documentData.l = letters; - maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth; - lineWidths.push(lineWidth); - if (documentData.sz) { - documentData.boxWidth = documentData.sz[0]; - documentData.justifyOffset = 0; - } else { - documentData.boxWidth = maxLineWidth; - switch (documentData.j) { - case 1: - documentData.justifyOffset = -documentData.boxWidth; - break; - case 2: - documentData.justifyOffset = -documentData.boxWidth / 2; - break; - default: - documentData.justifyOffset = 0; - } - } - documentData.lineWidths = lineWidths; - - var animators = data.a; var animatorData; var - letterData; - jLen = animators.length; - var based; var ind; var - indexes = []; - for (j = 0; j < jLen; j += 1) { - animatorData = animators[j]; - if (animatorData.a.sc) { - documentData.strokeColorAnim = true; - } - if (animatorData.a.sw) { - documentData.strokeWidthAnim = true; - } - if (animatorData.a.fc || animatorData.a.fh || animatorData.a.fs || animatorData.a.fb) { - documentData.fillColorAnim = true; - } - ind = 0; - based = animatorData.s.b; - for (i = 0; i < len; i += 1) { - letterData = letters[i]; - letterData.anIndexes[j] = ind; - if ((based == 1 && letterData.val !== '') || (based == 2 && letterData.val !== '' && letterData.val !== ' ') || (based == 3 && (letterData.n || letterData.val == ' ' || i == len - 1)) || (based == 4 && (letterData.n || i == len - 1))) { // eslint-disable-line eqeqeq - if (animatorData.s.rn === 1) { - indexes.push(ind); - } - ind += 1; - } - } - data.a[j].s.totalChars = ind; - var currentInd = -1; var - newInd; - if (animatorData.s.rn === 1) { - for (i = 0; i < len; i += 1) { - letterData = letters[i]; - if (currentInd != letterData.anIndexes[j]) { // eslint-disable-line eqeqeq - currentInd = letterData.anIndexes[j]; - newInd = indexes.splice(Math.floor(Math.random() * indexes.length), 1)[0]; - } - letterData.anIndexes[j] = newInd; - } - } - } - documentData.yOffset = documentData.finalLineHeight || documentData.finalSize * 1.2; - documentData.ls = documentData.ls || 0; - documentData.ascent = (fontData.ascent * documentData.finalSize) / 100; - }; - - TextProperty.prototype.updateDocumentData = function (newData, index) { - index = index === undefined ? this.keysIndex : index; - var dData = this.copyData({}, this.data.d.k[index].s); - dData = this.copyData(dData, newData); - this.data.d.k[index].s = dData; - this.recalculate(index); - this.elem.addDynamicProperty(this); - }; - - TextProperty.prototype.recalculate = function (index) { - var dData = this.data.d.k[index].s; - dData.__complete = false; - this.keysIndex = 0; - this._isFirstFrame = true; - this.getValue(dData); - }; - - TextProperty.prototype.canResizeFont = function (_canResize) { - this.canResize = _canResize; - this.recalculate(this.keysIndex); - this.elem.addDynamicProperty(this); - }; - - TextProperty.prototype.setMinimumFontSize = function (_fontValue) { - this.minimumFontSize = Math.floor(_fontValue) || 1; - this.recalculate(this.keysIndex); - this.elem.addDynamicProperty(this); - }; - - const TextSelectorProp = (function () { - var max = Math.max; - var min = Math.min; - var floor = Math.floor; - - function TextSelectorPropFactory(elem, data) { - this._currentTextLength = -1; - this.k = false; - this.data = data; - this.elem = elem; - this.comp = elem.comp; - this.finalS = 0; - this.finalE = 0; - this.initDynamicPropertyContainer(elem); - this.s = PropertyFactory.getProp(elem, data.s || { k: 0 }, 0, 0, this); - if ('e' in data) { - this.e = PropertyFactory.getProp(elem, data.e, 0, 0, this); - } else { - this.e = { v: 100 }; - } - this.o = PropertyFactory.getProp(elem, data.o || { k: 0 }, 0, 0, this); - this.xe = PropertyFactory.getProp(elem, data.xe || { k: 0 }, 0, 0, this); - this.ne = PropertyFactory.getProp(elem, data.ne || { k: 0 }, 0, 0, this); - this.sm = PropertyFactory.getProp(elem, data.sm || { k: 100 }, 0, 0, this); - this.a = PropertyFactory.getProp(elem, data.a, 0, 0.01, this); - if (!this.dynamicProperties.length) { - this.getValue(); - } - } - - TextSelectorPropFactory.prototype = { - getMult: function (ind) { - if (this._currentTextLength !== this.elem.textProperty.currentData.l.length) { - this.getValue(); - } - var x1 = 0; - var y1 = 0; - var x2 = 1; - var y2 = 1; - if (this.ne.v > 0) { - x1 = this.ne.v / 100.0; - } else { - y1 = -this.ne.v / 100.0; - } - if (this.xe.v > 0) { - x2 = 1.0 - this.xe.v / 100.0; - } else { - y2 = 1.0 + this.xe.v / 100.0; - } - var easer = BezierFactory.getBezierEasing(x1, y1, x2, y2).get; - - var mult = 0; - var s = this.finalS; - var e = this.finalE; - var type = this.data.sh; - if (type === 2) { - if (e === s) { - mult = ind >= e ? 1 : 0; - } else { - mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); - } - mult = easer(mult); - } else if (type === 3) { - if (e === s) { - mult = ind >= e ? 0 : 1; - } else { - mult = 1 - max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); - } - - mult = easer(mult); - } else if (type === 4) { - if (e === s) { - mult = 0; - } else { - mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1)); - if (mult < 0.5) { - mult *= 2; - } else { - mult = 1 - 2 * (mult - 0.5); - } - } - mult = easer(mult); - } else if (type === 5) { - if (e === s) { - mult = 0; - } else { - var tot = e - s; - /* ind += 0.5; - mult = -4/(tot*tot)*(ind*ind)+(4/tot)*ind; */ - ind = min(max(0, ind + 0.5 - s), e - s); - var x = -tot / 2 + ind; - var a = tot / 2; - mult = Math.sqrt(1 - (x * x) / (a * a)); - } - mult = easer(mult); - } else if (type === 6) { - if (e === s) { - mult = 0; - } else { - ind = min(max(0, ind + 0.5 - s), e - s); - mult = (1 + (Math.cos((Math.PI + Math.PI * 2 * (ind) / (e - s))))) / 2; // eslint-disable-line - } - mult = easer(mult); - } else { - if (ind >= floor(s)) { - if (ind - s < 0) { - mult = max(0, min(min(e, 1) - (s - ind), 1)); - } else { - mult = max(0, min(e - ind, 1)); - } - } - mult = easer(mult); - } - // Smoothness implementation. - // The smoothness represents a reduced range of the original [0; 1] range. - // if smoothness is 25%, the new range will be [0.375; 0.625] - // Steps are: - // - find the lower value of the new range (threshold) - // - if multiplier is smaller than that value, floor it to 0 - // - if it is larger, - // - subtract the threshold - // - divide it by the smoothness (this will return the range to [0; 1]) - // Note: If it doesn't work on some scenarios, consider applying it before the easer. - if (this.sm.v !== 100) { - var smoothness = this.sm.v * 0.01; - if (smoothness === 0) { - smoothness = 0.00000001; - } - var threshold = 0.5 - smoothness * 0.5; - if (mult < threshold) { - mult = 0; - } else { - mult = (mult - threshold) / smoothness; - if (mult > 1) { - mult = 1; - } - } - } - return mult * this.a.v; - }, - getValue: function (newCharsFlag) { - this.iterateDynamicProperties(); - this._mdf = newCharsFlag || this._mdf; - this._currentTextLength = this.elem.textProperty.currentData.l.length || 0; - if (newCharsFlag && this.data.r === 2) { - this.e.v = this._currentTextLength; - } - var divisor = this.data.r === 2 ? 1 : 100 / this.data.totalChars; - var o = this.o.v / divisor; - var s = this.s.v / divisor + o; - var e = (this.e.v / divisor) + o; - if (s > e) { - var _s = s; - s = e; - e = _s; - } - this.finalS = s; - this.finalE = e; - }, - }; - extendPrototype([DynamicPropertyContainer], TextSelectorPropFactory); - - function getTextSelectorProp(elem, data, arr) { - return new TextSelectorPropFactory(elem, data, arr); - } - - return { - getTextSelectorProp: getTextSelectorProp, - }; - }()); - - function TextAnimatorDataProperty(elem, animatorProps, container) { - var defaultData = { propType: false }; - var getProp = PropertyFactory.getProp; - var textAnimatorAnimatables = animatorProps.a; - this.a = { - r: textAnimatorAnimatables.r ? getProp(elem, textAnimatorAnimatables.r, 0, degToRads, container) : defaultData, - rx: textAnimatorAnimatables.rx ? getProp(elem, textAnimatorAnimatables.rx, 0, degToRads, container) : defaultData, - ry: textAnimatorAnimatables.ry ? getProp(elem, textAnimatorAnimatables.ry, 0, degToRads, container) : defaultData, - sk: textAnimatorAnimatables.sk ? getProp(elem, textAnimatorAnimatables.sk, 0, degToRads, container) : defaultData, - sa: textAnimatorAnimatables.sa ? getProp(elem, textAnimatorAnimatables.sa, 0, degToRads, container) : defaultData, - s: textAnimatorAnimatables.s ? getProp(elem, textAnimatorAnimatables.s, 1, 0.01, container) : defaultData, - a: textAnimatorAnimatables.a ? getProp(elem, textAnimatorAnimatables.a, 1, 0, container) : defaultData, - o: textAnimatorAnimatables.o ? getProp(elem, textAnimatorAnimatables.o, 0, 0.01, container) : defaultData, - p: textAnimatorAnimatables.p ? getProp(elem, textAnimatorAnimatables.p, 1, 0, container) : defaultData, - sw: textAnimatorAnimatables.sw ? getProp(elem, textAnimatorAnimatables.sw, 0, 0, container) : defaultData, - sc: textAnimatorAnimatables.sc ? getProp(elem, textAnimatorAnimatables.sc, 1, 0, container) : defaultData, - fc: textAnimatorAnimatables.fc ? getProp(elem, textAnimatorAnimatables.fc, 1, 0, container) : defaultData, - fh: textAnimatorAnimatables.fh ? getProp(elem, textAnimatorAnimatables.fh, 0, 0, container) : defaultData, - fs: textAnimatorAnimatables.fs ? getProp(elem, textAnimatorAnimatables.fs, 0, 0.01, container) : defaultData, - fb: textAnimatorAnimatables.fb ? getProp(elem, textAnimatorAnimatables.fb, 0, 0.01, container) : defaultData, - t: textAnimatorAnimatables.t ? getProp(elem, textAnimatorAnimatables.t, 0, 0, container) : defaultData, - }; - - this.s = TextSelectorProp.getTextSelectorProp(elem, animatorProps.s, container); - this.s.t = animatorProps.s.t; - } - - function TextAnimatorProperty(textData, renderType, elem) { - this._isFirstFrame = true; - this._hasMaskedPath = false; - this._frameId = -1; - this._textData = textData; - this._renderType = renderType; - this._elem = elem; - this._animatorsData = createSizedArray(this._textData.a.length); - this._pathData = {}; - this._moreOptions = { - alignment: {}, - }; - this.renderedLetters = []; - this.lettersChangedFlag = false; - this.initDynamicPropertyContainer(elem); - } - - TextAnimatorProperty.prototype.searchProperties = function () { - var i; - var len = this._textData.a.length; - var animatorProps; - var getProp = PropertyFactory.getProp; - for (i = 0; i < len; i += 1) { - animatorProps = this._textData.a[i]; - this._animatorsData[i] = new TextAnimatorDataProperty(this._elem, animatorProps, this); - } - if (this._textData.p && 'm' in this._textData.p) { - this._pathData = { - a: getProp(this._elem, this._textData.p.a, 0, 0, this), - f: getProp(this._elem, this._textData.p.f, 0, 0, this), - l: getProp(this._elem, this._textData.p.l, 0, 0, this), - r: getProp(this._elem, this._textData.p.r, 0, 0, this), - p: getProp(this._elem, this._textData.p.p, 0, 0, this), - m: this._elem.maskManager.getMaskProperty(this._textData.p.m), - }; - this._hasMaskedPath = true; - } else { - this._hasMaskedPath = false; - } - this._moreOptions.alignment = getProp(this._elem, this._textData.m.a, 1, 0, this); - }; - - TextAnimatorProperty.prototype.getMeasures = function (documentData, lettersChangedFlag) { - this.lettersChangedFlag = lettersChangedFlag; - if (!this._mdf && !this._isFirstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m._mdf)) { - return; - } - this._isFirstFrame = false; - var alignment = this._moreOptions.alignment.v; - var animators = this._animatorsData; - var textData = this._textData; - var matrixHelper = this.mHelper; - var renderType = this._renderType; - var renderedLettersCount = this.renderedLetters.length; - var xPos; - var yPos; - var i; - var len; - var letters = documentData.l; - var pathInfo; - var currentLength; - var currentPoint; - var segmentLength; - var flag; - var pointInd; - var segmentInd; - var prevPoint; - var points; - var segments; - var partialLength; - var totalLength; - var perc; - var tanAngle; - var mask; - if (this._hasMaskedPath) { - mask = this._pathData.m; - if (!this._pathData.n || this._pathData._mdf) { - var paths = mask.v; - if (this._pathData.r.v) { - paths = paths.reverse(); - } - // TODO: release bezier data cached from previous pathInfo: this._pathData.pi - pathInfo = { - tLength: 0, - segments: [], - }; - len = paths._length - 1; - var bezierData; - totalLength = 0; - for (i = 0; i < len; i += 1) { - bezierData = bez.buildBezierData(paths.v[i], - paths.v[i + 1], - [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], - [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]); - pathInfo.tLength += bezierData.segmentLength; - pathInfo.segments.push(bezierData); - totalLength += bezierData.segmentLength; - } - i = len; - if (mask.v.c) { - bezierData = bez.buildBezierData(paths.v[i], - paths.v[0], - [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], - [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]); - pathInfo.tLength += bezierData.segmentLength; - pathInfo.segments.push(bezierData); - totalLength += bezierData.segmentLength; - } - this._pathData.pi = pathInfo; - } - pathInfo = this._pathData.pi; - - currentLength = this._pathData.f.v; - segmentInd = 0; - pointInd = 1; - segmentLength = 0; - flag = true; - segments = pathInfo.segments; - if (currentLength < 0 && mask.v.c) { - if (pathInfo.tLength < Math.abs(currentLength)) { - currentLength = -Math.abs(currentLength) % pathInfo.tLength; - } - segmentInd = segments.length - 1; - points = segments[segmentInd].points; - pointInd = points.length - 1; - while (currentLength < 0) { - currentLength += points[pointInd].partialLength; - pointInd -= 1; - if (pointInd < 0) { - segmentInd -= 1; - points = segments[segmentInd].points; - pointInd = points.length - 1; - } - } - } - points = segments[segmentInd].points; - prevPoint = points[pointInd - 1]; - currentPoint = points[pointInd]; - partialLength = currentPoint.partialLength; - } - - len = letters.length; - xPos = 0; - yPos = 0; - var yOff = documentData.finalSize * 1.2 * 0.714; - var firstLine = true; - var animatorProps; - var animatorSelector; - var j; - var jLen; - var letterValue; - - jLen = animators.length; - - var mult; - var ind = -1; - var offf; - var xPathPos; - var yPathPos; - var initPathPos = currentLength; - var initSegmentInd = segmentInd; - var initPointInd = pointInd; - var currentLine = -1; - var elemOpacity; - var sc; - var sw; - var fc; - var k; - var letterSw; - var letterSc; - var letterFc; - var letterM = ''; - var letterP = this.defaultPropsArray; - var letterO; - - // - if (documentData.j === 2 || documentData.j === 1) { - var animatorJustifyOffset = 0; - var animatorFirstCharOffset = 0; - var justifyOffsetMult = documentData.j === 2 ? -0.5 : -1; - var lastIndex = 0; - var isNewLine = true; - - for (i = 0; i < len; i += 1) { - if (letters[i].n) { - if (animatorJustifyOffset) { - animatorJustifyOffset += animatorFirstCharOffset; - } - while (lastIndex < i) { - letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; - lastIndex += 1; - } - animatorJustifyOffset = 0; - isNewLine = true; - } else { - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.t.propType) { - if (isNewLine && documentData.j === 2) { - animatorFirstCharOffset += animatorProps.t.v * justifyOffsetMult; - } - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - animatorJustifyOffset += animatorProps.t.v * mult[0] * justifyOffsetMult; - } else { - animatorJustifyOffset += animatorProps.t.v * mult * justifyOffsetMult; - } - } - } - isNewLine = false; - } - } - if (animatorJustifyOffset) { - animatorJustifyOffset += animatorFirstCharOffset; - } - while (lastIndex < i) { - letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; - lastIndex += 1; - } - } - // - - for (i = 0; i < len; i += 1) { - matrixHelper.reset(); - elemOpacity = 1; - if (letters[i].n) { - xPos = 0; - yPos += documentData.yOffset; - yPos += firstLine ? 1 : 0; - currentLength = initPathPos; - firstLine = false; - if (this._hasMaskedPath) { - segmentInd = initSegmentInd; - pointInd = initPointInd; - points = segments[segmentInd].points; - prevPoint = points[pointInd - 1]; - currentPoint = points[pointInd]; - partialLength = currentPoint.partialLength; - segmentLength = 0; - } - letterM = ''; - letterFc = ''; - letterSw = ''; - letterO = ''; - letterP = this.defaultPropsArray; - } else { - if (this._hasMaskedPath) { - if (currentLine !== letters[i].line) { - switch (documentData.j) { - case 1: - currentLength += totalLength - documentData.lineWidths[letters[i].line]; - break; - case 2: - currentLength += (totalLength - documentData.lineWidths[letters[i].line]) / 2; - break; - default: - break; - } - currentLine = letters[i].line; - } - if (ind !== letters[i].ind) { - if (letters[ind]) { - currentLength += letters[ind].extra; - } - currentLength += letters[i].an / 2; - ind = letters[i].ind; - } - currentLength += (alignment[0] * letters[i].an) * 0.005; - var animatorOffset = 0; - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.p.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - animatorOffset += animatorProps.p.v[0] * mult[0]; - } else { - animatorOffset += animatorProps.p.v[0] * mult; - } - } - if (animatorProps.a.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - animatorOffset += animatorProps.a.v[0] * mult[0]; - } else { - animatorOffset += animatorProps.a.v[0] * mult; - } - } - } - flag = true; - // Force alignment only works with a single line for now - if (this._pathData.a.v) { - currentLength = letters[0].an * 0.5 + ((totalLength - this._pathData.f.v - letters[0].an * 0.5 - letters[letters.length - 1].an * 0.5) * ind) / (len - 1); - currentLength += this._pathData.f.v; - } - while (flag) { - if (segmentLength + partialLength >= currentLength + animatorOffset || !points) { - perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength; - xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc; - yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc; - matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, -(alignment[1] * yOff) * 0.01); - flag = false; - } else if (points) { - segmentLength += currentPoint.partialLength; - pointInd += 1; - if (pointInd >= points.length) { - pointInd = 0; - segmentInd += 1; - if (!segments[segmentInd]) { - if (mask.v.c) { - pointInd = 0; - segmentInd = 0; - points = segments[segmentInd].points; - } else { - segmentLength -= currentPoint.partialLength; - points = null; - } - } else { - points = segments[segmentInd].points; - } - } - if (points) { - prevPoint = currentPoint; - currentPoint = points[pointInd]; - partialLength = currentPoint.partialLength; - } - } - } - offf = letters[i].an / 2 - letters[i].add; - matrixHelper.translate(-offf, 0, 0); - } else { - offf = letters[i].an / 2 - letters[i].add; - matrixHelper.translate(-offf, 0, 0); - - // Grouping alignment - matrixHelper.translate((-alignment[0] * letters[i].an) * 0.005, (-alignment[1] * yOff) * 0.01, 0); - } - - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.t.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - // This condition is to prevent applying tracking to first character in each line. Might be better to use a boolean "isNewLine" - if (xPos !== 0 || documentData.j !== 0) { - if (this._hasMaskedPath) { - if (mult.length) { - currentLength += animatorProps.t.v * mult[0]; - } else { - currentLength += animatorProps.t.v * mult; - } - } else if (mult.length) { - xPos += animatorProps.t.v * mult[0]; - } else { - xPos += animatorProps.t.v * mult; - } - } - } - } - if (documentData.strokeWidthAnim) { - sw = documentData.sw || 0; - } - if (documentData.strokeColorAnim) { - if (documentData.sc) { - sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]]; - } else { - sc = [0, 0, 0]; - } - } - if (documentData.fillColorAnim && documentData.fc) { - fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]]; - } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.a.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - - if (mult.length) { - matrixHelper.translate(-animatorProps.a.v[0] * mult[0], -animatorProps.a.v[1] * mult[1], animatorProps.a.v[2] * mult[2]); - } else { - matrixHelper.translate(-animatorProps.a.v[0] * mult, -animatorProps.a.v[1] * mult, animatorProps.a.v[2] * mult); - } - } - } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - if (animatorProps.s.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (mult.length) { - matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult[0]), 1 + ((animatorProps.s.v[1] - 1) * mult[1]), 1); - } else { - matrixHelper.scale(1 + ((animatorProps.s.v[0] - 1) * mult), 1 + ((animatorProps.s.v[1] - 1) * mult), 1); - } - } - } - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (animatorProps.sk.propType) { - if (mult.length) { - matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1]); - } else { - matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult); - } - } - if (animatorProps.r.propType) { - if (mult.length) { - matrixHelper.rotateZ(-animatorProps.r.v * mult[2]); - } else { - matrixHelper.rotateZ(-animatorProps.r.v * mult); - } - } - if (animatorProps.ry.propType) { - if (mult.length) { - matrixHelper.rotateY(animatorProps.ry.v * mult[1]); - } else { - matrixHelper.rotateY(animatorProps.ry.v * mult); - } - } - if (animatorProps.rx.propType) { - if (mult.length) { - matrixHelper.rotateX(animatorProps.rx.v * mult[0]); - } else { - matrixHelper.rotateX(animatorProps.rx.v * mult); - } - } - if (animatorProps.o.propType) { - if (mult.length) { - elemOpacity += ((animatorProps.o.v) * mult[0] - elemOpacity) * mult[0]; - } else { - elemOpacity += ((animatorProps.o.v) * mult - elemOpacity) * mult; - } - } - if (documentData.strokeWidthAnim && animatorProps.sw.propType) { - if (mult.length) { - sw += animatorProps.sw.v * mult[0]; - } else { - sw += animatorProps.sw.v * mult; - } - } - if (documentData.strokeColorAnim && animatorProps.sc.propType) { - for (k = 0; k < 3; k += 1) { - if (mult.length) { - sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult[0]; - } else { - sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult; - } - } - } - if (documentData.fillColorAnim && documentData.fc) { - if (animatorProps.fc.propType) { - for (k = 0; k < 3; k += 1) { - if (mult.length) { - fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult[0]; - } else { - fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult; - } - } - } - if (animatorProps.fh.propType) { - if (mult.length) { - fc = addHueToRGB(fc, animatorProps.fh.v * mult[0]); - } else { - fc = addHueToRGB(fc, animatorProps.fh.v * mult); - } - } - if (animatorProps.fs.propType) { - if (mult.length) { - fc = addSaturationToRGB(fc, animatorProps.fs.v * mult[0]); - } else { - fc = addSaturationToRGB(fc, animatorProps.fs.v * mult); - } - } - if (animatorProps.fb.propType) { - if (mult.length) { - fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult[0]); - } else { - fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult); - } - } - } - } - - for (j = 0; j < jLen; j += 1) { - animatorProps = animators[j].a; - - if (animatorProps.p.propType) { - animatorSelector = animators[j].s; - mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); - if (this._hasMaskedPath) { - if (mult.length) { - matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1]); - } else { - matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); - } - } else if (mult.length) { - matrixHelper.translate(animatorProps.p.v[0] * mult[0], animatorProps.p.v[1] * mult[1], -animatorProps.p.v[2] * mult[2]); - } else { - matrixHelper.translate(animatorProps.p.v[0] * mult, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult); - } - } - } - if (documentData.strokeWidthAnim) { - letterSw = sw < 0 ? 0 : sw; - } - if (documentData.strokeColorAnim) { - letterSc = 'rgb(' + Math.round(sc[0] * 255) + ',' + Math.round(sc[1] * 255) + ',' + Math.round(sc[2] * 255) + ')'; - } - if (documentData.fillColorAnim && documentData.fc) { - letterFc = 'rgb(' + Math.round(fc[0] * 255) + ',' + Math.round(fc[1] * 255) + ',' + Math.round(fc[2] * 255) + ')'; - } - - if (this._hasMaskedPath) { - matrixHelper.translate(0, -documentData.ls); - - matrixHelper.translate(0, (alignment[1] * yOff) * 0.01 + yPos, 0); - if (this._pathData.p.v) { - tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0]); - var rot = (Math.atan(tanAngle) * 180) / Math.PI; - if (currentPoint.point[0] < prevPoint.point[0]) { - rot += 180; - } - matrixHelper.rotate((-rot * Math.PI) / 180); - } - matrixHelper.translate(xPathPos, yPathPos, 0); - currentLength -= (alignment[0] * letters[i].an) * 0.005; - if (letters[i + 1] && ind !== letters[i + 1].ind) { - currentLength += letters[i].an / 2; - currentLength += (documentData.tr * 0.001) * documentData.finalSize; - } - } else { - matrixHelper.translate(xPos, yPos, 0); - - if (documentData.ps) { - // matrixHelper.translate(documentData.ps[0],documentData.ps[1],0); - matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); - } - switch (documentData.j) { - case 1: - matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]), 0, 0); - break; - case 2: - matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]) / 2, 0, 0); - break; - default: - break; - } - matrixHelper.translate(0, -documentData.ls); - matrixHelper.translate(offf, 0, 0); - matrixHelper.translate((alignment[0] * letters[i].an) * 0.005, (alignment[1] * yOff) * 0.01, 0); - xPos += letters[i].l + (documentData.tr * 0.001) * documentData.finalSize; - } - if (renderType === 'html') { - letterM = matrixHelper.toCSS(); - } else if (renderType === 'svg') { - letterM = matrixHelper.to2dCSS(); - } else { - letterP = [matrixHelper.props[0], matrixHelper.props[1], matrixHelper.props[2], matrixHelper.props[3], matrixHelper.props[4], matrixHelper.props[5], matrixHelper.props[6], matrixHelper.props[7], matrixHelper.props[8], matrixHelper.props[9], matrixHelper.props[10], matrixHelper.props[11], matrixHelper.props[12], matrixHelper.props[13], matrixHelper.props[14], matrixHelper.props[15]]; - } - letterO = elemOpacity; - } - - if (renderedLettersCount <= i) { - letterValue = new LetterProps(letterO, letterSw, letterSc, letterFc, letterM, letterP); - this.renderedLetters.push(letterValue); - renderedLettersCount += 1; - this.lettersChangedFlag = true; - } else { - letterValue = this.renderedLetters[i]; - this.lettersChangedFlag = letterValue.update(letterO, letterSw, letterSc, letterFc, letterM, letterP) || this.lettersChangedFlag; - } - } - }; - - TextAnimatorProperty.prototype.getValue = function () { - if (this._elem.globalData.frameId === this._frameId) { - return; - } - this._frameId = this._elem.globalData.frameId; - this.iterateDynamicProperties(); - }; - - TextAnimatorProperty.prototype.mHelper = new Matrix(); - TextAnimatorProperty.prototype.defaultPropsArray = []; - extendPrototype([DynamicPropertyContainer], TextAnimatorProperty); - - function ITextElement() { - } - - ITextElement.prototype.initElement = function (data, globalData, comp) { - this.lettersChangedFlag = true; - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.textProperty = new TextProperty(this, data.t, this.dynamicProperties); - this.textAnimator = new TextAnimatorProperty(data.t, this.renderType, this); - this.initTransform(data, globalData, comp); - this.initHierarchy(); - this.initRenderable(); - this.initRendererElement(); - this.createContainerElements(); - this.createRenderableComponents(); - this.createContent(); - this.hide(); - this.textAnimator.searchProperties(this.dynamicProperties); - }; - - ITextElement.prototype.prepareFrame = function (num) { - this._mdf = false; - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - if (this.textProperty._mdf || this.textProperty._isFirstFrame) { - this.buildNewText(); - this.textProperty._isFirstFrame = false; - this.textProperty._mdf = false; - } - }; - - ITextElement.prototype.createPathShape = function (matrixHelper, shapes) { - var j; - var jLen = shapes.length; - var pathNodes; - var shapeStr = ''; - for (j = 0; j < jLen; j += 1) { - if (shapes[j].ty === 'sh') { - pathNodes = shapes[j].ks.k; - shapeStr += buildShapeString(pathNodes, pathNodes.i.length, true, matrixHelper); - } - } - return shapeStr; - }; - - ITextElement.prototype.updateDocumentData = function (newData, index) { - this.textProperty.updateDocumentData(newData, index); - }; - - ITextElement.prototype.canResizeFont = function (_canResize) { - this.textProperty.canResizeFont(_canResize); - }; - - ITextElement.prototype.setMinimumFontSize = function (_fontSize) { - this.textProperty.setMinimumFontSize(_fontSize); - }; - - ITextElement.prototype.applyTextPropertiesToMatrix = function (documentData, matrixHelper, lineNumber, xPos, yPos) { - if (documentData.ps) { - matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0); - } - matrixHelper.translate(0, -documentData.ls, 0); - switch (documentData.j) { - case 1: - matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]), 0, 0); - break; - case 2: - matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]) / 2, 0, 0); - break; - default: - break; - } - matrixHelper.translate(xPos, yPos, 0); - }; - - ITextElement.prototype.buildColor = function (colorData) { - return 'rgb(' + Math.round(colorData[0] * 255) + ',' + Math.round(colorData[1] * 255) + ',' + Math.round(colorData[2] * 255) + ')'; - }; - - ITextElement.prototype.emptyProp = new LetterProps(); - - ITextElement.prototype.destroy = function () { - - }; - - var emptyShapeData = { - shapes: [], - }; - - function SVGTextLottieElement(data, globalData, comp) { - this.textSpans = []; - this.renderType = 'svg'; - this.initElement(data, globalData, comp); - } - - extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement, ITextElement], SVGTextLottieElement); - - SVGTextLottieElement.prototype.createContent = function () { - if (this.data.singleShape && !this.globalData.fontManager.chars) { - this.textContainer = createNS('text'); - } - }; - - SVGTextLottieElement.prototype.buildTextContents = function (textArray) { - var i = 0; - var len = textArray.length; - var textContents = []; - var currentTextContent = ''; - while (i < len) { - if (textArray[i] === String.fromCharCode(13) || textArray[i] === String.fromCharCode(3)) { - textContents.push(currentTextContent); - currentTextContent = ''; - } else { - currentTextContent += textArray[i]; - } - i += 1; - } - textContents.push(currentTextContent); - return textContents; - }; - - SVGTextLottieElement.prototype.buildShapeData = function (data, scale) { - // data should probably be cloned to apply scale separately to each instance of a text on different layers - // but since text internal content gets only rendered once and then it's never rerendered, - // it's probably safe not to clone data and reuse always the same instance even if the object is mutated. - // Avoiding cloning is preferred since cloning each character shape data is expensive - if (data.shapes && data.shapes.length) { - var shape = data.shapes[0]; - if (shape.it) { - var shapeItem = shape.it[shape.it.length - 1]; - if (shapeItem.s) { - shapeItem.s.k[0] = scale; - shapeItem.s.k[1] = scale; - } - } - } - return data; - }; - - SVGTextLottieElement.prototype.buildNewText = function () { - this.addDynamicProperty(this); - var i; - var len; - - var documentData = this.textProperty.currentData; - this.renderedLetters = createSizedArray(documentData ? documentData.l.length : 0); - if (documentData.fc) { - this.layerElement.setAttribute('fill', this.buildColor(documentData.fc)); - } else { - this.layerElement.setAttribute('fill', 'rgba(0,0,0,0)'); - } - if (documentData.sc) { - this.layerElement.setAttribute('stroke', this.buildColor(documentData.sc)); - this.layerElement.setAttribute('stroke-width', documentData.sw); - } - this.layerElement.setAttribute('font-size', documentData.finalSize); - var fontData = this.globalData.fontManager.getFontByName(documentData.f); - if (fontData.fClass) { - this.layerElement.setAttribute('class', fontData.fClass); - } else { - this.layerElement.setAttribute('font-family', fontData.fFamily); - var fWeight = documentData.fWeight; - var fStyle = documentData.fStyle; - this.layerElement.setAttribute('font-style', fStyle); - this.layerElement.setAttribute('font-weight', fWeight); - } - this.layerElement.setAttribute('aria-label', documentData.t); - - var letters = documentData.l || []; - var usesGlyphs = !!this.globalData.fontManager.chars; - len = letters.length; - - var tSpan; - var matrixHelper = this.mHelper; - var shapeStr = ''; - var singleShape = this.data.singleShape; - var xPos = 0; - var yPos = 0; - var firstLine = true; - var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; - if (singleShape && !usesGlyphs && !documentData.sz) { - var tElement = this.textContainer; - var justify = 'start'; - switch (documentData.j) { - case 1: - justify = 'end'; - break; - case 2: - justify = 'middle'; - break; - default: - justify = 'start'; - break; - } - tElement.setAttribute('text-anchor', justify); - tElement.setAttribute('letter-spacing', trackingOffset); - var textContent = this.buildTextContents(documentData.finalText); - len = textContent.length; - yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0; - for (i = 0; i < len; i += 1) { - tSpan = this.textSpans[i].span || createNS('tspan'); - tSpan.textContent = textContent[i]; - tSpan.setAttribute('x', 0); - tSpan.setAttribute('y', yPos); - tSpan.style.display = 'inherit'; - tElement.appendChild(tSpan); - if (!this.textSpans[i]) { - this.textSpans[i] = { - span: null, - glyph: null, - }; - } - this.textSpans[i].span = tSpan; - yPos += documentData.finalLineHeight; - } - - this.layerElement.appendChild(tElement); - } else { - var cachedSpansLength = this.textSpans.length; - var charData; - for (i = 0; i < len; i += 1) { - if (!this.textSpans[i]) { - this.textSpans[i] = { - span: null, - childSpan: null, - glyph: null, - }; - } - if (!usesGlyphs || !singleShape || i === 0) { - tSpan = cachedSpansLength > i ? this.textSpans[i].span : createNS(usesGlyphs ? 'g' : 'text'); - if (cachedSpansLength <= i) { - tSpan.setAttribute('stroke-linecap', 'butt'); - tSpan.setAttribute('stroke-linejoin', 'round'); - tSpan.setAttribute('stroke-miterlimit', '4'); - this.textSpans[i].span = tSpan; - if (usesGlyphs) { - var childSpan = createNS('g'); - tSpan.appendChild(childSpan); - this.textSpans[i].childSpan = childSpan; - } - this.textSpans[i].span = tSpan; - this.layerElement.appendChild(tSpan); - } - tSpan.style.display = 'inherit'; - } - - matrixHelper.reset(); - if (singleShape) { - if (letters[i].n) { - xPos = -trackingOffset; - yPos += documentData.yOffset; - yPos += firstLine ? 1 : 0; - firstLine = false; - } - this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); - xPos += letters[i].l || 0; - // xPos += letters[i].val === ' ' ? 0 : trackingOffset; - xPos += trackingOffset; - } - if (usesGlyphs) { - charData = this.globalData.fontManager.getCharData( - documentData.finalText[i], - fontData.fStyle, - this.globalData.fontManager.getFontByName(documentData.f).fFamily - ); - var glyphElement; - // t === 1 means the character has been replaced with an animated shaped - if (charData.t === 1) { - glyphElement = new SVGCompElement(charData.data, this.globalData, this); - } else { - var data = emptyShapeData; - if (charData.data && charData.data.shapes) { - data = this.buildShapeData(charData.data, documentData.finalSize); - } - glyphElement = new SVGShapeElement(data, this.globalData, this); - } - if (this.textSpans[i].glyph) { - var glyph = this.textSpans[i].glyph; - this.textSpans[i].childSpan.removeChild(glyph.layerElement); - glyph.destroy(); - } - this.textSpans[i].glyph = glyphElement; - glyphElement._debug = true; - glyphElement.prepareFrame(0); - glyphElement.renderFrame(); - this.textSpans[i].childSpan.appendChild(glyphElement.layerElement); - // when using animated shapes, the layer will be scaled instead of replacing the internal scale - // this might have issues with strokes and might need a different solution - if (charData.t === 1) { - this.textSpans[i].childSpan.setAttribute('transform', 'scale(' + documentData.finalSize / 100 + ',' + documentData.finalSize / 100 + ')'); - } - } else { - if (singleShape) { - tSpan.setAttribute('transform', 'translate(' + matrixHelper.props[12] + ',' + matrixHelper.props[13] + ')'); - } - tSpan.textContent = letters[i].val; - tSpan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - } - // - } - if (singleShape && tSpan) { - tSpan.setAttribute('d', shapeStr); - } - } - while (i < this.textSpans.length) { - this.textSpans[i].span.style.display = 'none'; - i += 1; - } - - this._sizeChanged = true; - }; - - SVGTextLottieElement.prototype.sourceRectAtTime = function () { - this.prepareFrame(this.comp.renderedFrame - this.data.st); - this.renderInnerContent(); - if (this._sizeChanged) { - this._sizeChanged = false; - var textBox = this.layerElement.getBBox(); - this.bbox = { - top: textBox.y, - left: textBox.x, - width: textBox.width, - height: textBox.height, - }; - } - return this.bbox; - }; - - SVGTextLottieElement.prototype.getValue = function () { - var i; - var len = this.textSpans.length; - var glyphElement; - this.renderedFrame = this.comp.renderedFrame; - for (i = 0; i < len; i += 1) { - glyphElement = this.textSpans[i].glyph; - if (glyphElement) { - glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st); - if (glyphElement._mdf) { - this._mdf = true; - } - } - } - }; - - SVGTextLottieElement.prototype.renderInnerContent = function () { - if (!this.data.singleShape || this._mdf) { - this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); - if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) { - this._sizeChanged = true; - var i; - var len; - var renderedLetters = this.textAnimator.renderedLetters; - - var letters = this.textProperty.currentData.l; - - len = letters.length; - var renderedLetter; - var textSpan; - var glyphElement; - for (i = 0; i < len; i += 1) { - if (!letters[i].n) { - renderedLetter = renderedLetters[i]; - textSpan = this.textSpans[i].span; - glyphElement = this.textSpans[i].glyph; - if (glyphElement) { - glyphElement.renderFrame(); - } - if (renderedLetter._mdf.m) { - textSpan.setAttribute('transform', renderedLetter.m); - } - if (renderedLetter._mdf.o) { - textSpan.setAttribute('opacity', renderedLetter.o); - } - if (renderedLetter._mdf.sw) { - textSpan.setAttribute('stroke-width', renderedLetter.sw); - } - if (renderedLetter._mdf.sc) { - textSpan.setAttribute('stroke', renderedLetter.sc); - } - if (renderedLetter._mdf.fc) { - textSpan.setAttribute('fill', renderedLetter.fc); - } - } - } - } - } - }; - - function ISolidElement(data, globalData, comp) { - this.initElement(data, globalData, comp); - } - extendPrototype([IImageElement], ISolidElement); - - ISolidElement.prototype.createContent = function () { - var rect = createNS('rect'); - /// /rect.style.width = this.data.sw; - /// /rect.style.height = this.data.sh; - /// /rect.style.fill = this.data.sc; - rect.setAttribute('width', this.data.sw); - rect.setAttribute('height', this.data.sh); - rect.setAttribute('fill', this.data.sc); - this.layerElement.appendChild(rect); - }; - - function NullElement(data, globalData, comp) { - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.initFrame(); - this.initTransform(data, globalData, comp); - this.initHierarchy(); - } - - NullElement.prototype.prepareFrame = function (num) { - this.prepareProperties(num, true); - }; - - NullElement.prototype.renderFrame = function () { - }; - - NullElement.prototype.getBaseElement = function () { - return null; - }; - - NullElement.prototype.destroy = function () { - }; - - NullElement.prototype.sourceRectAtTime = function () { - }; - - NullElement.prototype.hide = function () { - }; - - extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement], NullElement); - - function SVGRendererBase() { - } - - extendPrototype([BaseRenderer], SVGRendererBase); - - SVGRendererBase.prototype.createNull = function (data) { - return new NullElement(data, this.globalData, this); - }; - - SVGRendererBase.prototype.createShape = function (data) { - return new SVGShapeElement(data, this.globalData, this); - }; - - SVGRendererBase.prototype.createText = function (data) { - return new SVGTextLottieElement(data, this.globalData, this); - }; - - SVGRendererBase.prototype.createImage = function (data) { - return new IImageElement(data, this.globalData, this); - }; - - SVGRendererBase.prototype.createSolid = function (data) { - return new ISolidElement(data, this.globalData, this); - }; - - SVGRendererBase.prototype.configAnimation = function (animData) { - this.svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); - if (this.renderConfig.viewBoxSize) { - this.svgElement.setAttribute('viewBox', this.renderConfig.viewBoxSize); - } else { - this.svgElement.setAttribute('viewBox', '0 0 ' + animData.w + ' ' + animData.h); - } - - if (!this.renderConfig.viewBoxOnly) { - this.svgElement.setAttribute('width', animData.w); - this.svgElement.setAttribute('height', animData.h); - this.svgElement.style.width = '100%'; - this.svgElement.style.height = '100%'; - this.svgElement.style.transform = 'translate3d(0,0,0)'; - this.svgElement.style.contentVisibility = this.renderConfig.contentVisibility; - } - if (this.renderConfig.width) { - this.svgElement.setAttribute('width', this.renderConfig.width); - } - if (this.renderConfig.height) { - this.svgElement.setAttribute('height', this.renderConfig.height); - } - if (this.renderConfig.className) { - this.svgElement.setAttribute('class', this.renderConfig.className); - } - if (this.renderConfig.id) { - this.svgElement.setAttribute('id', this.renderConfig.id); - } - if (this.renderConfig.focusable !== undefined) { - this.svgElement.setAttribute('focusable', this.renderConfig.focusable); - } - this.svgElement.setAttribute('preserveAspectRatio', this.renderConfig.preserveAspectRatio); - // this.layerElement.style.transform = 'translate3d(0,0,0)'; - // this.layerElement.style.transformOrigin = this.layerElement.style.mozTransformOrigin = this.layerElement.style.webkitTransformOrigin = this.layerElement.style['-webkit-transform'] = "0px 0px 0px"; - this.animationItem.wrapper.appendChild(this.svgElement); - // Mask animation - var defs = this.globalData.defs; - - this.setupGlobalData(animData, defs); - this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; - this.data = animData; - - var maskElement = createNS('clipPath'); - var rect = createNS('rect'); - rect.setAttribute('width', animData.w); - rect.setAttribute('height', animData.h); - rect.setAttribute('x', 0); - rect.setAttribute('y', 0); - var maskId = createElementID(); - maskElement.setAttribute('id', maskId); - maskElement.appendChild(rect); - this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + maskId + ')'); - - defs.appendChild(maskElement); - this.layers = animData.layers; - this.elements = createSizedArray(animData.layers.length); - }; - - SVGRendererBase.prototype.destroy = function () { - if (this.animationItem.wrapper) { - this.animationItem.wrapper.innerText = ''; - } - this.layerElement = null; - this.globalData.defs = null; - var i; - var len = this.layers ? this.layers.length : 0; - for (i = 0; i < len; i += 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - this.elements.length = 0; - this.destroyed = true; - this.animationItem = null; - }; - - SVGRendererBase.prototype.updateContainerSize = function () { - }; - - SVGRendererBase.prototype.buildItem = function (pos) { - var elements = this.elements; - if (elements[pos] || this.layers[pos].ty === 99) { - return; - } - elements[pos] = true; - var element = this.createItem(this.layers[pos]); - - elements[pos] = element; - if (getExpressionsPlugin()) { - if (this.layers[pos].ty === 0) { - this.globalData.projectInterface.registerComposition(element); - } - element.initExpressions(); - } - this.appendElementInPos(element, pos); - if (this.layers[pos].tt) { - if (!this.elements[pos - 1] || this.elements[pos - 1] === true) { - this.buildItem(pos - 1); - this.addPendingElement(element); - } else { - element.setMatte(elements[pos - 1].layerId); - } - } - }; - - SVGRendererBase.prototype.checkPendingElements = function () { - while (this.pendingElements.length) { - var element = this.pendingElements.pop(); - element.checkParenting(); - if (element.data.tt) { - var i = 0; - var len = this.elements.length; - while (i < len) { - if (this.elements[i] === element) { - element.setMatte(this.elements[i - 1].layerId); - break; - } - i += 1; - } - } - } - }; - - SVGRendererBase.prototype.renderFrame = function (num) { - if (this.renderedFrame === num || this.destroyed) { - return; - } - if (num === null) { - num = this.renderedFrame; - } else { - this.renderedFrame = num; - } - // console.log('-------'); - // console.log('FRAME ',num); - this.globalData.frameNum = num; - this.globalData.frameId += 1; - this.globalData.projectInterface.currentFrame = num; - this.globalData._mdf = false; - var i; - var len = this.layers.length; - if (!this.completeLayers) { - this.checkLayers(num); - } - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].prepareFrame(num - this.layers[i].st); - } - } - if (this.globalData._mdf) { - for (i = 0; i < len; i += 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } - } - }; - - SVGRendererBase.prototype.appendElementInPos = function (element, pos) { - var newElement = element.getBaseElement(); - if (!newElement) { - return; - } - var i = 0; - var nextElement; - while (i < pos) { - if (this.elements[i] && this.elements[i] !== true && this.elements[i].getBaseElement()) { - nextElement = this.elements[i].getBaseElement(); - } - i += 1; - } - if (nextElement) { - this.layerElement.insertBefore(newElement, nextElement); - } else { - this.layerElement.appendChild(newElement); - } - }; - - SVGRendererBase.prototype.hide = function () { - this.layerElement.style.display = 'none'; - }; - - SVGRendererBase.prototype.show = function () { - this.layerElement.style.display = 'block'; - }; - - function ICompElement() {} - - extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement, RenderableDOMElement], ICompElement); - - ICompElement.prototype.initElement = function (data, globalData, comp) { - this.initFrame(); - this.initBaseData(data, globalData, comp); - this.initTransform(data, globalData, comp); - this.initRenderable(); - this.initHierarchy(); - this.initRendererElement(); - this.createContainerElements(); - this.createRenderableComponents(); - if (this.data.xt || !globalData.progressiveLoad) { - this.buildAllItems(); - } - this.hide(); - }; - - /* ICompElement.prototype.hide = function(){ - if(!this.hidden){ - this.hideElement(); - var i,len = this.elements.length; - for( i = 0; i < len; i+=1 ){ - if(this.elements[i]){ - this.elements[i].hide(); - } - } - } - }; */ - - ICompElement.prototype.prepareFrame = function (num) { - this._mdf = false; - this.prepareRenderableFrame(num); - this.prepareProperties(num, this.isInRange); - if (!this.isInRange && !this.data.xt) { - return; - } - - if (!this.tm._placeholder) { - var timeRemapped = this.tm.v; - if (timeRemapped === this.data.op) { - timeRemapped = this.data.op - 1; - } - this.renderedFrame = timeRemapped; - } else { - this.renderedFrame = num / this.data.sr; - } - var i; - var len = this.elements.length; - if (!this.completeLayers) { - this.checkLayers(this.renderedFrame); - } - // This iteration needs to be backwards because of how expressions connect between each other - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].prepareFrame(this.renderedFrame - this.layers[i].st); - if (this.elements[i]._mdf) { - this._mdf = true; - } - } - } - }; - - ICompElement.prototype.renderInnerContent = function () { - var i; - var len = this.layers.length; - for (i = 0; i < len; i += 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } - }; - - ICompElement.prototype.setElements = function (elems) { - this.elements = elems; - }; - - ICompElement.prototype.getElements = function () { - return this.elements; - }; - - ICompElement.prototype.destroyElements = function () { - var i; - var len = this.layers.length; - for (i = 0; i < len; i += 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - }; - - ICompElement.prototype.destroy = function () { - this.destroyElements(); - this.destroyBaseElement(); - }; - - function SVGCompElement(data, globalData, comp) { - this.layers = data.layers; - this.supports3d = true; - this.completeLayers = false; - this.pendingElements = []; - this.elements = this.layers ? createSizedArray(this.layers.length) : []; - this.initElement(data, globalData, comp); - this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; - } - - extendPrototype([SVGRendererBase, ICompElement, SVGBaseElement], SVGCompElement); - - SVGCompElement.prototype.createComp = function (data) { - return new SVGCompElement(data, this.globalData, this); - }; - - function SVGRenderer(animationItem, config) { - this.animationItem = animationItem; - this.layers = null; - this.renderedFrame = -1; - this.svgElement = createNS('svg'); - var ariaLabel = ''; - if (config && config.title) { - var titleElement = createNS('title'); - var titleId = createElementID(); - titleElement.setAttribute('id', titleId); - titleElement.textContent = config.title; - this.svgElement.appendChild(titleElement); - ariaLabel += titleId; - } - if (config && config.description) { - var descElement = createNS('desc'); - var descId = createElementID(); - descElement.setAttribute('id', descId); - descElement.textContent = config.description; - this.svgElement.appendChild(descElement); - ariaLabel += ' ' + descId; - } - if (ariaLabel) { - this.svgElement.setAttribute('aria-labelledby', ariaLabel); - } - var defs = createNS('defs'); - this.svgElement.appendChild(defs); - var maskElement = createNS('g'); - this.svgElement.appendChild(maskElement); - this.layerElement = maskElement; - this.renderConfig = { - preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', - imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', - contentVisibility: (config && config.contentVisibility) || 'visible', - progressiveLoad: (config && config.progressiveLoad) || false, - hideOnTransparent: !((config && config.hideOnTransparent === false)), - viewBoxOnly: (config && config.viewBoxOnly) || false, - viewBoxSize: (config && config.viewBoxSize) || false, - className: (config && config.className) || '', - id: (config && config.id) || '', - focusable: config && config.focusable, - filterSize: { - width: (config && config.filterSize && config.filterSize.width) || '100%', - height: (config && config.filterSize && config.filterSize.height) || '100%', - x: (config && config.filterSize && config.filterSize.x) || '0%', - y: (config && config.filterSize && config.filterSize.y) || '0%', - }, - width: (config && config.width), - height: (config && config.height), - }; - - this.globalData = { - _mdf: false, - frameNum: -1, - defs: defs, - renderConfig: this.renderConfig, - }; - this.elements = []; - this.pendingElements = []; - this.destroyed = false; - this.rendererType = 'svg'; - } - - extendPrototype([SVGRendererBase], SVGRenderer); - - SVGRenderer.prototype.createComp = function (data) { - return new SVGCompElement(data, this.globalData, this); - }; - - function CVContextData() { - this.saved = []; - this.cArrPos = 0; - this.cTr = new Matrix(); - this.cO = 1; - var i; - var len = 15; - this.savedOp = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - this.saved[i] = createTypedArray('float32', 16); - } - this._length = len; - } - - CVContextData.prototype.duplicate = function () { - var newLength = this._length * 2; - var currentSavedOp = this.savedOp; - this.savedOp = createTypedArray('float32', newLength); - this.savedOp.set(currentSavedOp); - var i = 0; - for (i = this._length; i < newLength; i += 1) { - this.saved[i] = createTypedArray('float32', 16); - } - this._length = newLength; - }; - - CVContextData.prototype.reset = function () { - this.cArrPos = 0; - this.cTr.reset(); - this.cO = 1; - }; - - function ShapeTransformManager() { - this.sequences = {}; - this.sequenceList = []; - this.transform_key_count = 0; - } - - ShapeTransformManager.prototype = { - addTransformSequence: function (transforms) { - var i; - var len = transforms.length; - var key = '_'; - for (i = 0; i < len; i += 1) { - key += transforms[i].transform.key + '_'; - } - var sequence = this.sequences[key]; - if (!sequence) { - sequence = { - transforms: [].concat(transforms), - finalTransform: new Matrix(), - _mdf: false, - }; - this.sequences[key] = sequence; - this.sequenceList.push(sequence); - } - return sequence; - }, - processSequence: function (sequence, isFirstFrame) { - var i = 0; - var len = sequence.transforms.length; - var _mdf = isFirstFrame; - while (i < len && !isFirstFrame) { - if (sequence.transforms[i].transform.mProps._mdf) { - _mdf = true; - break; - } - i += 1; - } - if (_mdf) { - var props; - sequence.finalTransform.reset(); - for (i = len - 1; i >= 0; i -= 1) { - props = sequence.transforms[i].transform.mProps.v.props; - sequence.finalTransform.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); - } - } - sequence._mdf = _mdf; - }, - processSequences: function (isFirstFrame) { - var i; - var len = this.sequenceList.length; - for (i = 0; i < len; i += 1) { - this.processSequence(this.sequenceList[i], isFirstFrame); - } - }, - getNewKey: function () { - this.transform_key_count += 1; - return '_' + this.transform_key_count; - }, - }; - - function CVEffects() { - - } - CVEffects.prototype.renderFrame = function () {}; - - function CVMaskElement(data, element) { - this.data = data; - this.element = element; - this.masksProperties = this.data.masksProperties || []; - this.viewData = createSizedArray(this.masksProperties.length); - var i; - var len = this.masksProperties.length; - var hasMasks = false; - for (i = 0; i < len; i += 1) { - if (this.masksProperties[i].mode !== 'n') { - hasMasks = true; - } - this.viewData[i] = ShapePropertyFactory.getShapeProp(this.element, this.masksProperties[i], 3); - } - this.hasMasks = hasMasks; - if (hasMasks) { - this.element.addRenderableComponent(this); - } - } - - CVMaskElement.prototype.renderFrame = function () { - if (!this.hasMasks) { - return; - } - var transform = this.element.finalTransform.mat; - var ctx = this.element.canvasContext; - var i; - var len = this.masksProperties.length; - var pt; - var pts; - var data; - ctx.beginPath(); - for (i = 0; i < len; i += 1) { - if (this.masksProperties[i].mode !== 'n') { - if (this.masksProperties[i].inv) { - ctx.moveTo(0, 0); - ctx.lineTo(this.element.globalData.compSize.w, 0); - ctx.lineTo(this.element.globalData.compSize.w, this.element.globalData.compSize.h); - ctx.lineTo(0, this.element.globalData.compSize.h); - ctx.lineTo(0, 0); - } - data = this.viewData[i].v; - pt = transform.applyToPointArray(data.v[0][0], data.v[0][1], 0); - ctx.moveTo(pt[0], pt[1]); - var j; - var jLen = data._length; - for (j = 1; j < jLen; j += 1) { - pts = transform.applyToTriplePoints(data.o[j - 1], data.i[j], data.v[j]); - ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); - } - pts = transform.applyToTriplePoints(data.o[j - 1], data.i[0], data.v[0]); - ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); - } - } - this.element.globalData.renderer.save(true); - ctx.clip(); - }; - - CVMaskElement.prototype.getMaskProperty = MaskElement.prototype.getMaskProperty; - - CVMaskElement.prototype.destroy = function () { - this.element = null; - }; - - function CVBaseElement() { - } - - CVBaseElement.prototype = { - createElements: function () {}, - initRendererElement: function () {}, - createContainerElements: function () { - this.canvasContext = this.globalData.canvasContext; - this.renderableEffectsManager = new CVEffects(this); - }, - createContent: function () {}, - setBlendMode: function () { - var globalData = this.globalData; - if (globalData.blendMode !== this.data.bm) { - globalData.blendMode = this.data.bm; - var blendModeValue = getBlendMode(this.data.bm); - globalData.canvasContext.globalCompositeOperation = blendModeValue; - } - }, - createRenderableComponents: function () { - this.maskManager = new CVMaskElement(this.data, this); - }, - hideElement: function () { - if (!this.hidden && (!this.isInRange || this.isTransparent)) { - this.hidden = true; - } - }, - showElement: function () { - if (this.isInRange && !this.isTransparent) { - this.hidden = false; - this._isFirstFrame = true; - this.maskManager._isFirstFrame = true; - } - }, - renderFrame: function () { - if (this.hidden || this.data.hd) { - return; - } - this.renderTransform(); - this.renderRenderable(); - this.setBlendMode(); - var forceRealStack = this.data.ty === 0; - this.globalData.renderer.save(forceRealStack); - this.globalData.renderer.ctxTransform(this.finalTransform.mat.props); - this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v); - this.renderInnerContent(); - this.globalData.renderer.restore(forceRealStack); - if (this.maskManager.hasMasks) { - this.globalData.renderer.restore(true); - } - if (this._isFirstFrame) { - this._isFirstFrame = false; - } - }, - destroy: function () { - this.canvasContext = null; - this.data = null; - this.globalData = null; - this.maskManager.destroy(); - }, - mHelper: new Matrix(), - }; - CVBaseElement.prototype.hide = CVBaseElement.prototype.hideElement; - CVBaseElement.prototype.show = CVBaseElement.prototype.showElement; - - function CVShapeData(element, data, styles, transformsManager) { - this.styledShapes = []; - this.tr = [0, 0, 0, 0, 0, 0]; - var ty = 4; - if (data.ty === 'rc') { - ty = 5; - } else if (data.ty === 'el') { - ty = 6; - } else if (data.ty === 'sr') { - ty = 7; - } - this.sh = ShapePropertyFactory.getShapeProp(element, data, ty, element); - var i; - var len = styles.length; - var styledShape; - for (i = 0; i < len; i += 1) { - if (!styles[i].closed) { - styledShape = { - transforms: transformsManager.addTransformSequence(styles[i].transforms), - trNodes: [], - }; - this.styledShapes.push(styledShape); - styles[i].elements.push(styledShape); - } - } - } - - CVShapeData.prototype.setAsAnimated = SVGShapeData.prototype.setAsAnimated; - - function CVShapeElement(data, globalData, comp) { - this.shapes = []; - this.shapesData = data.shapes; - this.stylesList = []; - this.itemsData = []; - this.prevViewData = []; - this.shapeModifiers = []; - this.processedElements = []; - this.transformsManager = new ShapeTransformManager(); - this.initElement(data, globalData, comp); - } - - extendPrototype([BaseElement, TransformElement, CVBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableElement], CVShapeElement); - - CVShapeElement.prototype.initElement = RenderableDOMElement.prototype.initElement; - - CVShapeElement.prototype.transformHelper = { opacity: 1, _opMdf: false }; - - CVShapeElement.prototype.dashResetter = []; - - CVShapeElement.prototype.createContent = function () { - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); - }; - - CVShapeElement.prototype.createStyleElement = function (data, transforms) { - var styleElem = { - data: data, - type: data.ty, - preTransforms: this.transformsManager.addTransformSequence(transforms), - transforms: [], - elements: [], - closed: data.hd === true, - }; - var elementData = {}; - if (data.ty === 'fl' || data.ty === 'st') { - elementData.c = PropertyFactory.getProp(this, data.c, 1, 255, this); - if (!elementData.c.k) { - styleElem.co = 'rgb(' + bmFloor(elementData.c.v[0]) + ',' + bmFloor(elementData.c.v[1]) + ',' + bmFloor(elementData.c.v[2]) + ')'; - } - } else if (data.ty === 'gf' || data.ty === 'gs') { - elementData.s = PropertyFactory.getProp(this, data.s, 1, null, this); - elementData.e = PropertyFactory.getProp(this, data.e, 1, null, this); - elementData.h = PropertyFactory.getProp(this, data.h || { k: 0 }, 0, 0.01, this); - elementData.a = PropertyFactory.getProp(this, data.a || { k: 0 }, 0, degToRads, this); - elementData.g = new GradientProperty(this, data.g, this); - } - elementData.o = PropertyFactory.getProp(this, data.o, 0, 0.01, this); - if (data.ty === 'st' || data.ty === 'gs') { - styleElem.lc = lineCapEnum[data.lc || 2]; - styleElem.lj = lineJoinEnum[data.lj || 2]; - if (data.lj == 1) { // eslint-disable-line eqeqeq - styleElem.ml = data.ml; - } - elementData.w = PropertyFactory.getProp(this, data.w, 0, null, this); - if (!elementData.w.k) { - styleElem.wi = elementData.w.v; - } - if (data.d) { - var d = new DashProperty(this, data.d, 'canvas', this); - elementData.d = d; - if (!elementData.d.k) { - styleElem.da = elementData.d.dashArray; - styleElem.do = elementData.d.dashoffset[0]; - } - } - } else { - styleElem.r = data.r === 2 ? 'evenodd' : 'nonzero'; - } - this.stylesList.push(styleElem); - elementData.style = styleElem; - return elementData; - }; - - CVShapeElement.prototype.createGroupElement = function () { - var elementData = { - it: [], - prevViewData: [], - }; - return elementData; - }; - - CVShapeElement.prototype.createTransformElement = function (data) { - var elementData = { - transform: { - opacity: 1, - _opMdf: false, - key: this.transformsManager.getNewKey(), - op: PropertyFactory.getProp(this, data.o, 0, 0.01, this), - mProps: TransformPropertyFactory.getTransformProperty(this, data, this), - }, - }; - return elementData; - }; - - CVShapeElement.prototype.createShapeElement = function (data) { - var elementData = new CVShapeData(this, data, this.stylesList, this.transformsManager); - - this.shapes.push(elementData); - this.addShapeToModifiers(elementData); - return elementData; - }; - - CVShapeElement.prototype.reloadShapes = function () { - this._isFirstFrame = true; - var i; - var len = this.itemsData.length; - for (i = 0; i < len; i += 1) { - this.prevViewData[i] = this.itemsData[i]; - } - this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); - len = this.dynamicProperties.length; - for (i = 0; i < len; i += 1) { - this.dynamicProperties[i].getValue(); - } - this.renderModifiers(); - this.transformsManager.processSequences(this._isFirstFrame); - }; - - CVShapeElement.prototype.addTransformToStyleList = function (transform) { - var i; - var len = this.stylesList.length; - for (i = 0; i < len; i += 1) { - if (!this.stylesList[i].closed) { - this.stylesList[i].transforms.push(transform); - } - } - }; - - CVShapeElement.prototype.removeTransformFromStyleList = function () { - var i; - var len = this.stylesList.length; - for (i = 0; i < len; i += 1) { - if (!this.stylesList[i].closed) { - this.stylesList[i].transforms.pop(); - } - } - }; - - CVShapeElement.prototype.closeStyles = function (styles) { - var i; - var len = styles.length; - for (i = 0; i < len; i += 1) { - styles[i].closed = true; - } - }; - - CVShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, shouldRender, transforms) { - var i; - var len = arr.length - 1; - var j; - var jLen; - var ownStyles = []; - var ownModifiers = []; - var processedPos; - var modifier; - var currentTransform; - var ownTransforms = [].concat(transforms); - for (i = len; i >= 0; i -= 1) { - processedPos = this.searchProcessedElement(arr[i]); - if (!processedPos) { - arr[i]._shouldRender = shouldRender; - } else { - itemsData[i] = prevViewData[processedPos - 1]; - } - if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs') { - if (!processedPos) { - itemsData[i] = this.createStyleElement(arr[i], ownTransforms); - } else { - itemsData[i].style.closed = false; - } - - ownStyles.push(itemsData[i].style); - } else if (arr[i].ty === 'gr') { - if (!processedPos) { - itemsData[i] = this.createGroupElement(arr[i]); - } else { - jLen = itemsData[i].it.length; - for (j = 0; j < jLen; j += 1) { - itemsData[i].prevViewData[j] = itemsData[i].it[j]; - } - } - this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, shouldRender, ownTransforms); - } else if (arr[i].ty === 'tr') { - if (!processedPos) { - currentTransform = this.createTransformElement(arr[i]); - itemsData[i] = currentTransform; - } - ownTransforms.push(itemsData[i]); - this.addTransformToStyleList(itemsData[i]); - } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { - if (!processedPos) { - itemsData[i] = this.createShapeElement(arr[i]); - } - } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'pb') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - modifier.init(this, arr[i]); - itemsData[i] = modifier; - this.shapeModifiers.push(modifier); - } else { - modifier = itemsData[i]; - modifier.closed = false; - } - ownModifiers.push(modifier); - } else if (arr[i].ty === 'rp') { - if (!processedPos) { - modifier = ShapeModifiers.getModifier(arr[i].ty); - itemsData[i] = modifier; - modifier.init(this, arr, i, itemsData); - this.shapeModifiers.push(modifier); - shouldRender = false; - } else { - modifier = itemsData[i]; - modifier.closed = true; - } - ownModifiers.push(modifier); - } - this.addProcessedElement(arr[i], i + 1); - } - this.removeTransformFromStyleList(); - this.closeStyles(ownStyles); - len = ownModifiers.length; - for (i = 0; i < len; i += 1) { - ownModifiers[i].closed = true; - } - }; - - CVShapeElement.prototype.renderInnerContent = function () { - this.transformHelper.opacity = 1; - this.transformHelper._opMdf = false; - this.renderModifiers(); - this.transformsManager.processSequences(this._isFirstFrame); - this.renderShape(this.transformHelper, this.shapesData, this.itemsData, true); - }; - - CVShapeElement.prototype.renderShapeTransform = function (parentTransform, groupTransform) { - if (parentTransform._opMdf || groupTransform.op._mdf || this._isFirstFrame) { - groupTransform.opacity = parentTransform.opacity; - groupTransform.opacity *= groupTransform.op.v; - groupTransform._opMdf = true; - } - }; - - CVShapeElement.prototype.drawLayer = function () { - var i; - var len = this.stylesList.length; - var j; - var jLen; - var k; - var kLen; - var elems; - var nodes; - var renderer = this.globalData.renderer; - var ctx = this.globalData.canvasContext; - var type; - var currentStyle; - for (i = 0; i < len; i += 1) { - currentStyle = this.stylesList[i]; - type = currentStyle.type; - - // Skipping style when - // Stroke width equals 0 - // style should not be rendered (extra unused repeaters) - // current opacity equals 0 - // global opacity equals 0 - if (!(((type === 'st' || type === 'gs') && currentStyle.wi === 0) || !currentStyle.data._shouldRender || currentStyle.coOp === 0 || this.globalData.currentGlobalAlpha === 0)) { - renderer.save(); - elems = currentStyle.elements; - if (type === 'st' || type === 'gs') { - ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd; - ctx.lineWidth = currentStyle.wi; - ctx.lineCap = currentStyle.lc; - ctx.lineJoin = currentStyle.lj; - ctx.miterLimit = currentStyle.ml || 0; - } else { - ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd; - } - renderer.ctxOpacity(currentStyle.coOp); - if (type !== 'st' && type !== 'gs') { - ctx.beginPath(); - } - renderer.ctxTransform(currentStyle.preTransforms.finalTransform.props); - jLen = elems.length; - for (j = 0; j < jLen; j += 1) { - if (type === 'st' || type === 'gs') { - ctx.beginPath(); - if (currentStyle.da) { - ctx.setLineDash(currentStyle.da); - ctx.lineDashOffset = currentStyle.do; - } - } - nodes = elems[j].trNodes; - kLen = nodes.length; - - for (k = 0; k < kLen; k += 1) { - if (nodes[k].t === 'm') { - ctx.moveTo(nodes[k].p[0], nodes[k].p[1]); - } else if (nodes[k].t === 'c') { - ctx.bezierCurveTo(nodes[k].pts[0], nodes[k].pts[1], nodes[k].pts[2], nodes[k].pts[3], nodes[k].pts[4], nodes[k].pts[5]); - } else { - ctx.closePath(); - } - } - if (type === 'st' || type === 'gs') { - ctx.stroke(); - if (currentStyle.da) { - ctx.setLineDash(this.dashResetter); - } - } - } - if (type !== 'st' && type !== 'gs') { - ctx.fill(currentStyle.r); - } - renderer.restore(); - } - } - }; - - CVShapeElement.prototype.renderShape = function (parentTransform, items, data, isMain) { - var i; - var len = items.length - 1; - var groupTransform; - groupTransform = parentTransform; - for (i = len; i >= 0; i -= 1) { - if (items[i].ty === 'tr') { - groupTransform = data[i].transform; - this.renderShapeTransform(parentTransform, groupTransform); - } else if (items[i].ty === 'sh' || items[i].ty === 'el' || items[i].ty === 'rc' || items[i].ty === 'sr') { - this.renderPath(items[i], data[i]); - } else if (items[i].ty === 'fl') { - this.renderFill(items[i], data[i], groupTransform); - } else if (items[i].ty === 'st') { - this.renderStroke(items[i], data[i], groupTransform); - } else if (items[i].ty === 'gf' || items[i].ty === 'gs') { - this.renderGradientFill(items[i], data[i], groupTransform); - } else if (items[i].ty === 'gr') { - this.renderShape(groupTransform, items[i].it, data[i].it); - } else if (items[i].ty === 'tm') { - // - } - } - if (isMain) { - this.drawLayer(); - } - }; - - CVShapeElement.prototype.renderStyledShape = function (styledShape, shape) { - if (this._isFirstFrame || shape._mdf || styledShape.transforms._mdf) { - var shapeNodes = styledShape.trNodes; - var paths = shape.paths; - var i; - var len; - var j; - var jLen = paths._length; - shapeNodes.length = 0; - var groupTransformMat = styledShape.transforms.finalTransform; - for (j = 0; j < jLen; j += 1) { - var pathNodes = paths.shapes[j]; - if (pathNodes && pathNodes.v) { - len = pathNodes._length; - for (i = 1; i < len; i += 1) { - if (i === 1) { - shapeNodes.push({ - t: 'm', - p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), - }); - } - shapeNodes.push({ - t: 'c', - pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[i], pathNodes.v[i]), - }); - } - if (len === 1) { - shapeNodes.push({ - t: 'm', - p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), - }); - } - if (pathNodes.c && len) { - shapeNodes.push({ - t: 'c', - pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[0], pathNodes.v[0]), - }); - shapeNodes.push({ - t: 'z', - }); - } - } - } - styledShape.trNodes = shapeNodes; - } - }; - - CVShapeElement.prototype.renderPath = function (pathData, itemData) { - if (pathData.hd !== true && pathData._shouldRender) { - var i; - var len = itemData.styledShapes.length; - for (i = 0; i < len; i += 1) { - this.renderStyledShape(itemData.styledShapes[i], itemData.sh); - } - } - }; - - CVShapeElement.prototype.renderFill = function (styleData, itemData, groupTransform) { - var styleElem = itemData.style; - - if (itemData.c._mdf || this._isFirstFrame) { - styleElem.co = 'rgb(' - + bmFloor(itemData.c.v[0]) + ',' - + bmFloor(itemData.c.v[1]) + ',' - + bmFloor(itemData.c.v[2]) + ')'; - } - if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { - styleElem.coOp = itemData.o.v * groupTransform.opacity; - } - }; - - CVShapeElement.prototype.renderGradientFill = function (styleData, itemData, groupTransform) { - var styleElem = itemData.style; - var grd; - if (!styleElem.grd || itemData.g._mdf || itemData.s._mdf || itemData.e._mdf || (styleData.t !== 1 && (itemData.h._mdf || itemData.a._mdf))) { - var ctx = this.globalData.canvasContext; - var pt1 = itemData.s.v; - var pt2 = itemData.e.v; - if (styleData.t === 1) { - grd = ctx.createLinearGradient(pt1[0], pt1[1], pt2[0], pt2[1]); - } else { - var rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); - var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); - - var percent = itemData.h.v; - if (percent >= 1) { - percent = 0.99; - } else if (percent <= -1) { - percent = -0.99; - } - var dist = rad * percent; - var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; - var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; - grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad); - } - - var i; - var len = styleData.g.p; - var cValues = itemData.g.c; - var opacity = 1; - - for (i = 0; i < len; i += 1) { - if (itemData.g._hasOpacity && itemData.g._collapsable) { - opacity = itemData.g.o[i * 2 + 1]; - } - grd.addColorStop(cValues[i * 4] / 100, 'rgba(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ',' + opacity + ')'); - } - styleElem.grd = grd; - } - styleElem.coOp = itemData.o.v * groupTransform.opacity; - }; - - CVShapeElement.prototype.renderStroke = function (styleData, itemData, groupTransform) { - var styleElem = itemData.style; - var d = itemData.d; - if (d && (d._mdf || this._isFirstFrame)) { - styleElem.da = d.dashArray; - styleElem.do = d.dashoffset[0]; - } - if (itemData.c._mdf || this._isFirstFrame) { - styleElem.co = 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'; - } - if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { - styleElem.coOp = itemData.o.v * groupTransform.opacity; - } - if (itemData.w._mdf || this._isFirstFrame) { - styleElem.wi = itemData.w.v; - } - }; - - CVShapeElement.prototype.destroy = function () { - this.shapesData = null; - this.globalData = null; - this.canvasContext = null; - this.stylesList.length = 0; - this.itemsData.length = 0; - }; - - function CVTextElement(data, globalData, comp) { - this.textSpans = []; - this.yOffset = 0; - this.fillColorAnim = false; - this.strokeColorAnim = false; - this.strokeWidthAnim = false; - this.stroke = false; - this.fill = false; - this.justifyOffset = 0; - this.currentRender = null; - this.renderType = 'canvas'; - this.values = { - fill: 'rgba(0,0,0,0)', - stroke: 'rgba(0,0,0,0)', - sWidth: 0, - fValue: '', - }; - this.initElement(data, globalData, comp); + return sequence; + }, + processSequence: function (sequence, isFirstFrame) { + var i = 0; + var len = sequence.transforms.length; + var _mdf = isFirstFrame; + while (i < len && !isFirstFrame) { + if (sequence.transforms[i].transform.mProps._mdf) { + _mdf = true; + break; + } + i += 1; } - extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement, ITextElement], CVTextElement); - - CVTextElement.prototype.tHelper = createTag('canvas').getContext('2d'); - - CVTextElement.prototype.buildNewText = function () { - var documentData = this.textProperty.currentData; - this.renderedLetters = createSizedArray(documentData.l ? documentData.l.length : 0); - - var hasFill = false; - if (documentData.fc) { - hasFill = true; - this.values.fill = this.buildColor(documentData.fc); - } else { - this.values.fill = 'rgba(0,0,0,0)'; - } - this.fill = hasFill; - var hasStroke = false; - if (documentData.sc) { - hasStroke = true; - this.values.stroke = this.buildColor(documentData.sc); - this.values.sWidth = documentData.sw; - } - var fontData = this.globalData.fontManager.getFontByName(documentData.f); - var i; - var len; - var letters = documentData.l; - var matrixHelper = this.mHelper; - this.stroke = hasStroke; - this.values.fValue = documentData.finalSize + 'px ' + this.globalData.fontManager.getFontByName(documentData.f).fFamily; - len = documentData.finalText.length; - // this.tHelper.font = this.values.fValue; - var charData; - var shapeData; - var k; - var kLen; - var shapes; - var j; - var jLen; - var pathNodes; - var commands; - var pathArr; - var singleShape = this.data.singleShape; - var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; - var xPos = 0; - var yPos = 0; - var firstLine = true; - var cnt = 0; - for (i = 0; i < len; i += 1) { - charData = this.globalData.fontManager.getCharData(documentData.finalText[i], fontData.fStyle, this.globalData.fontManager.getFontByName(documentData.f).fFamily); - shapeData = (charData && charData.data) || {}; - matrixHelper.reset(); - if (singleShape && letters[i].n) { - xPos = -trackingOffset; - yPos += documentData.yOffset; - yPos += firstLine ? 1 : 0; - firstLine = false; - } - shapes = shapeData.shapes ? shapeData.shapes[0].it : []; - jLen = shapes.length; - matrixHelper.scale(documentData.finalSize / 100, documentData.finalSize / 100); - if (singleShape) { - this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); - } - commands = createSizedArray(jLen - 1); - var commandsCounter = 0; - for (j = 0; j < jLen; j += 1) { - if (shapes[j].ty === 'sh') { - kLen = shapes[j].ks.k.i.length; - pathNodes = shapes[j].ks.k; - pathArr = []; - for (k = 1; k < kLen; k += 1) { - if (k === 1) { - pathArr.push(matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); - } - pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToY(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToX(pathNodes.v[k][0], pathNodes.v[k][1], 0), matrixHelper.applyToY(pathNodes.v[k][0], pathNodes.v[k][1], 0)); - } - pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToY(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); - commands[commandsCounter] = pathArr; - commandsCounter += 1; - } - } - if (singleShape) { - xPos += letters[i].l; - xPos += trackingOffset; - } - if (this.textSpans[cnt]) { - this.textSpans[cnt].elem = commands; - } else { - this.textSpans[cnt] = { elem: commands }; - } - cnt += 1; - } - }; - - CVTextElement.prototype.renderInnerContent = function () { - var ctx = this.canvasContext; - ctx.font = this.values.fValue; - ctx.lineCap = 'butt'; - ctx.lineJoin = 'miter'; - ctx.miterLimit = 4; - - if (!this.data.singleShape) { - this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); + if (_mdf) { + var props; + sequence.finalTransform.reset(); + for (i = len - 1; i >= 0; i -= 1) { + props = sequence.transforms[i].transform.mProps.v.props; + sequence.finalTransform.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); } - - var i; - var len; + } + sequence._mdf = _mdf; + }, + processSequences: function (isFirstFrame) { + var i; + var len = this.sequenceList.length; + for (i = 0; i < len; i += 1) { + this.processSequence(this.sequenceList[i], isFirstFrame); + } + }, + getNewKey: function () { + this.transform_key_count += 1; + return '_' + this.transform_key_count; + }, +}; + +function CVEffects() { + +} +CVEffects.prototype.renderFrame = function () {}; + +function CVMaskElement(data, element) { + this.data = data; + this.element = element; + this.masksProperties = this.data.masksProperties || []; + this.viewData = createSizedArray(this.masksProperties.length); + var i; + var len = this.masksProperties.length; + var hasMasks = false; + for (i = 0; i < len; i += 1) { + if (this.masksProperties[i].mode !== 'n') { + hasMasks = true; + } + this.viewData[i] = ShapePropertyFactory.getShapeProp(this.element, this.masksProperties[i], 3); + } + this.hasMasks = hasMasks; + if (hasMasks) { + this.element.addRenderableComponent(this); + } +} + +CVMaskElement.prototype.renderFrame = function () { + if (!this.hasMasks) { + return; + } + var transform = this.element.finalTransform.mat; + var ctx = this.element.canvasContext; + var i; + var len = this.masksProperties.length; + var pt; + var pts; + var data; + ctx.beginPath(); + for (i = 0; i < len; i += 1) { + if (this.masksProperties[i].mode !== 'n') { + if (this.masksProperties[i].inv) { + ctx.moveTo(0, 0); + ctx.lineTo(this.element.globalData.compSize.w, 0); + ctx.lineTo(this.element.globalData.compSize.w, this.element.globalData.compSize.h); + ctx.lineTo(0, this.element.globalData.compSize.h); + ctx.lineTo(0, 0); + } + data = this.viewData[i].v; + pt = transform.applyToPointArray(data.v[0][0], data.v[0][1], 0); + ctx.moveTo(pt[0], pt[1]); var j; - var jLen; - var k; - var kLen; - var renderedLetters = this.textAnimator.renderedLetters; - - var letters = this.textProperty.currentData.l; - - len = letters.length; - var renderedLetter; - var lastFill = null; - var lastStroke = null; - var lastStrokeW = null; - var commands; - var pathArr; - for (i = 0; i < len; i += 1) { - if (!letters[i].n) { - renderedLetter = renderedLetters[i]; - if (renderedLetter) { - this.globalData.renderer.save(); - this.globalData.renderer.ctxTransform(renderedLetter.p); - this.globalData.renderer.ctxOpacity(renderedLetter.o); - } - if (this.fill) { - if (renderedLetter && renderedLetter.fc) { - if (lastFill !== renderedLetter.fc) { - lastFill = renderedLetter.fc; - ctx.fillStyle = renderedLetter.fc; - } - } else if (lastFill !== this.values.fill) { - lastFill = this.values.fill; - ctx.fillStyle = this.values.fill; - } - commands = this.textSpans[i].elem; - jLen = commands.length; - this.globalData.canvasContext.beginPath(); - for (j = 0; j < jLen; j += 1) { - pathArr = commands[j]; - kLen = pathArr.length; - this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); - for (k = 2; k < kLen; k += 6) { - this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); - } - } - this.globalData.canvasContext.closePath(); - this.globalData.canvasContext.fill(); - /// ctx.fillText(this.textSpans[i].val,0,0); - } - if (this.stroke) { - if (renderedLetter && renderedLetter.sw) { - if (lastStrokeW !== renderedLetter.sw) { - lastStrokeW = renderedLetter.sw; - ctx.lineWidth = renderedLetter.sw; - } - } else if (lastStrokeW !== this.values.sWidth) { - lastStrokeW = this.values.sWidth; - ctx.lineWidth = this.values.sWidth; - } - if (renderedLetter && renderedLetter.sc) { - if (lastStroke !== renderedLetter.sc) { - lastStroke = renderedLetter.sc; - ctx.strokeStyle = renderedLetter.sc; - } - } else if (lastStroke !== this.values.stroke) { - lastStroke = this.values.stroke; - ctx.strokeStyle = this.values.stroke; - } - commands = this.textSpans[i].elem; - jLen = commands.length; - this.globalData.canvasContext.beginPath(); - for (j = 0; j < jLen; j += 1) { - pathArr = commands[j]; - kLen = pathArr.length; - this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); - for (k = 2; k < kLen; k += 6) { - this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); - } - } - this.globalData.canvasContext.closePath(); - this.globalData.canvasContext.stroke(); - /// ctx.strokeText(letters[i].val,0,0); - } - if (renderedLetter) { - this.globalData.renderer.restore(); - } - } - } - }; - - function CVImageElement(data, globalData, comp) { - this.assetData = globalData.getAssetData(data.refId); - this.img = globalData.imageLoader.getAsset(this.assetData); - this.initElement(data, globalData, comp); - } - extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVImageElement); - - CVImageElement.prototype.initElement = SVGShapeElement.prototype.initElement; - CVImageElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; - - CVImageElement.prototype.createContent = function () { - if (this.img.width && (this.assetData.w !== this.img.width || this.assetData.h !== this.img.height)) { - var canvas = createTag('canvas'); - canvas.width = this.assetData.w; - canvas.height = this.assetData.h; - var ctx = canvas.getContext('2d'); - - var imgW = this.img.width; - var imgH = this.img.height; - var imgRel = imgW / imgH; - var canvasRel = this.assetData.w / this.assetData.h; - var widthCrop; - var heightCrop; - var par = this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio; - if ((imgRel > canvasRel && par === 'xMidYMid slice') || (imgRel < canvasRel && par !== 'xMidYMid slice')) { - heightCrop = imgH; - widthCrop = heightCrop * canvasRel; - } else { - widthCrop = imgW; - heightCrop = widthCrop / canvasRel; - } - ctx.drawImage(this.img, (imgW - widthCrop) / 2, (imgH - heightCrop) / 2, widthCrop, heightCrop, 0, 0, this.assetData.w, this.assetData.h); - this.img = canvas; + var jLen = data._length; + for (j = 1; j < jLen; j += 1) { + pts = transform.applyToTriplePoints(data.o[j - 1], data.i[j], data.v[j]); + ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); } - }; - - CVImageElement.prototype.renderInnerContent = function () { - this.canvasContext.drawImage(this.img, 0, 0); - }; - - CVImageElement.prototype.destroy = function () { - this.img = null; - }; - - function CVSolidElement(data, globalData, comp) { - this.initElement(data, globalData, comp); + pts = transform.applyToTriplePoints(data.o[j - 1], data.i[0], data.v[0]); + ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); } - extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVSolidElement); - - CVSolidElement.prototype.initElement = SVGShapeElement.prototype.initElement; - CVSolidElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; - - CVSolidElement.prototype.renderInnerContent = function () { - var ctx = this.canvasContext; - ctx.fillStyle = this.data.sc; - ctx.fillRect(0, 0, this.data.sw, this.data.sh); - // - }; - - function CanvasRendererBase(animationItem, config) { - this.animationItem = animationItem; - this.renderConfig = { - clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, - context: (config && config.context) || null, - progressiveLoad: (config && config.progressiveLoad) || false, - preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', - imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', - contentVisibility: (config && config.contentVisibility) || 'visible', - className: (config && config.className) || '', - id: (config && config.id) || '', - }; - this.renderConfig.dpr = (config && config.dpr) || 1; - if (this.animationItem.wrapper) { - this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; - } - this.renderedFrame = -1; - this.globalData = { - frameNum: -1, - _mdf: false, - renderConfig: this.renderConfig, - currentGlobalAlpha: -1, - }; - this.contextData = new CVContextData(); - this.elements = []; - this.pendingElements = []; - this.transformMat = new Matrix(); - this.completeLayers = false; - this.rendererType = 'canvas'; + } + this.element.globalData.renderer.save(true); + ctx.clip(); +}; + +CVMaskElement.prototype.getMaskProperty = MaskElement.prototype.getMaskProperty; + +CVMaskElement.prototype.destroy = function () { + this.element = null; +}; + +function CVBaseElement() { +} + +CVBaseElement.prototype = { + createElements: function () {}, + initRendererElement: function () {}, + createContainerElements: function () { + this.canvasContext = this.globalData.canvasContext; + this.renderableEffectsManager = new CVEffects(this); + }, + createContent: function () {}, + setBlendMode: function () { + var globalData = this.globalData; + if (globalData.blendMode !== this.data.bm) { + globalData.blendMode = this.data.bm; + var blendModeValue = getBlendMode(this.data.bm); + globalData.canvasContext.globalCompositeOperation = blendModeValue; } - extendPrototype([BaseRenderer], CanvasRendererBase); - - CanvasRendererBase.prototype.createShape = function (data) { - return new CVShapeElement(data, this.globalData, this); - }; - - CanvasRendererBase.prototype.createText = function (data) { - return new CVTextElement(data, this.globalData, this); - }; - - CanvasRendererBase.prototype.createImage = function (data) { - return new CVImageElement(data, this.globalData, this); - }; - - CanvasRendererBase.prototype.createSolid = function (data) { - return new CVSolidElement(data, this.globalData, this); - }; - - CanvasRendererBase.prototype.createNull = SVGRenderer.prototype.createNull; - - CanvasRendererBase.prototype.ctxTransform = function (props) { - if (props[0] === 1 && props[1] === 0 && props[4] === 0 && props[5] === 1 && props[12] === 0 && props[13] === 0) { - return; - } - if (!this.renderConfig.clearCanvas) { - this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13]); - return; - } - this.transformMat.cloneFromProps(props); - var cProps = this.contextData.cTr.props; - this.transformMat.transform(cProps[0], cProps[1], cProps[2], cProps[3], cProps[4], cProps[5], cProps[6], cProps[7], cProps[8], cProps[9], cProps[10], cProps[11], cProps[12], cProps[13], cProps[14], cProps[15]); - // this.contextData.cTr.transform(props[0],props[1],props[2],props[3],props[4],props[5],props[6],props[7],props[8],props[9],props[10],props[11],props[12],props[13],props[14],props[15]); - this.contextData.cTr.cloneFromProps(this.transformMat.props); - var trProps = this.contextData.cTr.props; - this.canvasContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13]); - }; - - CanvasRendererBase.prototype.ctxOpacity = function (op) { - /* if(op === 1){ - return; - } */ - if (!this.renderConfig.clearCanvas) { - this.canvasContext.globalAlpha *= op < 0 ? 0 : op; - this.globalData.currentGlobalAlpha = this.contextData.cO; - return; - } - this.contextData.cO *= op < 0 ? 0 : op; - if (this.globalData.currentGlobalAlpha !== this.contextData.cO) { - this.canvasContext.globalAlpha = this.contextData.cO; - this.globalData.currentGlobalAlpha = this.contextData.cO; - } - }; - - CanvasRendererBase.prototype.reset = function () { - if (!this.renderConfig.clearCanvas) { - this.canvasContext.restore(); - return; - } - this.contextData.reset(); - }; - - CanvasRendererBase.prototype.save = function (actionFlag) { - if (!this.renderConfig.clearCanvas) { - this.canvasContext.save(); - return; - } - if (actionFlag) { - this.canvasContext.save(); - } - var props = this.contextData.cTr.props; - if (this.contextData._length <= this.contextData.cArrPos) { - this.contextData.duplicate(); - } - var i; - var arr = this.contextData.saved[this.contextData.cArrPos]; - for (i = 0; i < 16; i += 1) { - arr[i] = props[i]; - } - this.contextData.savedOp[this.contextData.cArrPos] = this.contextData.cO; - this.contextData.cArrPos += 1; - }; - - CanvasRendererBase.prototype.restore = function (actionFlag) { - if (!this.renderConfig.clearCanvas) { - this.canvasContext.restore(); - return; - } - if (actionFlag) { - this.canvasContext.restore(); - this.globalData.blendMode = 'source-over'; - } - this.contextData.cArrPos -= 1; - var popped = this.contextData.saved[this.contextData.cArrPos]; - var i; - var arr = this.contextData.cTr.props; - for (i = 0; i < 16; i += 1) { - arr[i] = popped[i]; - } - this.canvasContext.setTransform(popped[0], popped[1], popped[4], popped[5], popped[12], popped[13]); - popped = this.contextData.savedOp[this.contextData.cArrPos]; - this.contextData.cO = popped; - if (this.globalData.currentGlobalAlpha !== popped) { - this.canvasContext.globalAlpha = popped; - this.globalData.currentGlobalAlpha = popped; - } - }; - - CanvasRendererBase.prototype.configAnimation = function (animData) { - if (this.animationItem.wrapper) { - this.animationItem.container = createTag('canvas'); - var containerStyle = this.animationItem.container.style; - containerStyle.width = '100%'; - containerStyle.height = '100%'; - var origin = '0px 0px 0px'; - containerStyle.transformOrigin = origin; - containerStyle.mozTransformOrigin = origin; - containerStyle.webkitTransformOrigin = origin; - containerStyle['-webkit-transform'] = origin; - containerStyle.contentVisibility = this.renderConfig.contentVisibility; - this.animationItem.wrapper.appendChild(this.animationItem.container); - this.canvasContext = this.animationItem.container.getContext('2d'); - if (this.renderConfig.className) { - this.animationItem.container.setAttribute('class', this.renderConfig.className); - } - if (this.renderConfig.id) { - this.animationItem.container.setAttribute('id', this.renderConfig.id); - } - } else { - this.canvasContext = this.renderConfig.context; - } - this.data = animData; - this.layers = animData.layers; - this.transformCanvas = { - w: animData.w, - h: animData.h, - sx: 0, - sy: 0, - tx: 0, - ty: 0, - }; - this.setupGlobalData(animData, document.body); - this.globalData.canvasContext = this.canvasContext; - this.globalData.renderer = this; - this.globalData.isDashed = false; - this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; - this.globalData.transformCanvas = this.transformCanvas; - this.elements = createSizedArray(animData.layers.length); - - this.updateContainerSize(); - }; - - CanvasRendererBase.prototype.updateContainerSize = function () { - this.reset(); - var elementWidth; - var elementHeight; - if (this.animationItem.wrapper && this.animationItem.container) { - elementWidth = this.animationItem.wrapper.offsetWidth; - elementHeight = this.animationItem.wrapper.offsetHeight; - this.animationItem.container.setAttribute('width', elementWidth * this.renderConfig.dpr); - this.animationItem.container.setAttribute('height', elementHeight * this.renderConfig.dpr); - } else { - elementWidth = this.canvasContext.canvas.width * this.renderConfig.dpr; - elementHeight = this.canvasContext.canvas.height * this.renderConfig.dpr; - } - var elementRel; - var animationRel; - if (this.renderConfig.preserveAspectRatio.indexOf('meet') !== -1 || this.renderConfig.preserveAspectRatio.indexOf('slice') !== -1) { - var par = this.renderConfig.preserveAspectRatio.split(' '); - var fillType = par[1] || 'meet'; - var pos = par[0] || 'xMidYMid'; - var xPos = pos.substr(0, 4); - var yPos = pos.substr(4); - elementRel = elementWidth / elementHeight; - animationRel = this.transformCanvas.w / this.transformCanvas.h; - if ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice')) { - this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); - this.transformCanvas.sy = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); - } else { - this.transformCanvas.sx = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); - this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); - } - - if (xPos === 'xMid' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { - this.transformCanvas.tx = ((elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) / 2) * this.renderConfig.dpr; - } else if (xPos === 'xMax' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { - this.transformCanvas.tx = (elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) * this.renderConfig.dpr; - } else { - this.transformCanvas.tx = 0; - } - if (yPos === 'YMid' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { - this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w)) / 2) * this.renderConfig.dpr; - } else if (yPos === 'YMax' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { - this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w))) * this.renderConfig.dpr; - } else { - this.transformCanvas.ty = 0; - } - } else if (this.renderConfig.preserveAspectRatio === 'none') { - this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); - this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); - this.transformCanvas.tx = 0; - this.transformCanvas.ty = 0; - } else { - this.transformCanvas.sx = this.renderConfig.dpr; - this.transformCanvas.sy = this.renderConfig.dpr; - this.transformCanvas.tx = 0; - this.transformCanvas.ty = 0; - } - this.transformCanvas.props = [this.transformCanvas.sx, 0, 0, 0, 0, this.transformCanvas.sy, 0, 0, 0, 0, 1, 0, this.transformCanvas.tx, this.transformCanvas.ty, 0, 1]; - /* var i, len = this.elements.length; - for(i=0;i= 0; i -= 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - this.elements.length = 0; - this.globalData.canvasContext = null; - this.animationItem.container = null; - this.destroyed = true; - }; - - CanvasRendererBase.prototype.renderFrame = function (num, forceRender) { - if ((this.renderedFrame === num && this.renderConfig.clearCanvas === true && !forceRender) || this.destroyed || num === -1) { - return; - } - this.renderedFrame = num; - this.globalData.frameNum = num - this.animationItem._isFirstFrame; - this.globalData.frameId += 1; - this.globalData._mdf = !this.renderConfig.clearCanvas || forceRender; - this.globalData.projectInterface.currentFrame = num; - - // console.log('--------'); - // console.log('NEW: ',num); - var i; - var len = this.layers.length; - if (!this.completeLayers) { - this.checkLayers(num); - } - - for (i = 0; i < len; i += 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].prepareFrame(num - this.layers[i].st); - } - } - if (this.globalData._mdf) { - if (this.renderConfig.clearCanvas === true) { - this.canvasContext.clearRect(0, 0, this.transformCanvas.w, this.transformCanvas.h); - } else { - this.save(); - } - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } - if (this.renderConfig.clearCanvas !== true) { - this.restore(); - } - } - }; - - CanvasRendererBase.prototype.buildItem = function (pos) { - var elements = this.elements; - if (elements[pos] || this.layers[pos].ty === 99) { - return; - } - var element = this.createItem(this.layers[pos], this, this.globalData); - elements[pos] = element; - element.initExpressions(); - /* if(this.layers[pos].ty === 0){ - element.resize(this.globalData.transformCanvas); - } */ - }; - - CanvasRendererBase.prototype.checkPendingElements = function () { - while (this.pendingElements.length) { - var element = this.pendingElements.pop(); - element.checkParenting(); - } - }; - - CanvasRendererBase.prototype.hide = function () { - this.animationItem.container.style.display = 'none'; - }; - - CanvasRendererBase.prototype.show = function () { - this.animationItem.container.style.display = 'block'; - }; - - function CVCompElement(data, globalData, comp) { - this.completeLayers = false; - this.layers = data.layers; - this.pendingElements = []; - this.elements = createSizedArray(this.layers.length); - this.initElement(data, globalData, comp); - this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; - } - - extendPrototype([CanvasRendererBase, ICompElement, CVBaseElement], CVCompElement); - - CVCompElement.prototype.renderInnerContent = function () { - var ctx = this.canvasContext; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo(this.data.w, 0); - ctx.lineTo(this.data.w, this.data.h); - ctx.lineTo(0, this.data.h); - ctx.lineTo(0, 0); - ctx.clip(); - var i; - var len = this.layers.length; - for (i = len - 1; i >= 0; i -= 1) { - if (this.completeLayers || this.elements[i]) { - this.elements[i].renderFrame(); - } - } - }; - - CVCompElement.prototype.destroy = function () { - var i; - var len = this.layers.length; - for (i = len - 1; i >= 0; i -= 1) { - if (this.elements[i]) { - this.elements[i].destroy(); - } - } - this.layers = null; - this.elements = null; - }; - - CVCompElement.prototype.createComp = function (data) { - return new CVCompElement(data, this.globalData, this); - }; - - function CanvasRenderer(animationItem, config) { - this.animationItem = animationItem; - this.renderConfig = { - clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, - context: (config && config.context) || null, - progressiveLoad: (config && config.progressiveLoad) || false, - preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', - imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', - contentVisibility: (config && config.contentVisibility) || 'visible', - className: (config && config.className) || '', - id: (config && config.id) || '', - }; - this.renderConfig.dpr = (config && config.dpr) || 1; - if (this.animationItem.wrapper) { - this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; - } - this.renderedFrame = -1; - this.globalData = { - frameNum: -1, - _mdf: false, - renderConfig: this.renderConfig, - currentGlobalAlpha: -1, + }, + createRenderableComponents: function () { + this.maskManager = new CVMaskElement(this.data, this); + }, + hideElement: function () { + if (!this.hidden && (!this.isInRange || this.isTransparent)) { + this.hidden = true; + } + }, + showElement: function () { + if (this.isInRange && !this.isTransparent) { + this.hidden = false; + this._isFirstFrame = true; + this.maskManager._isFirstFrame = true; + } + }, + renderFrame: function () { + if (this.hidden || this.data.hd) { + return; + } + this.renderTransform(); + this.renderRenderable(); + this.setBlendMode(); + var forceRealStack = this.data.ty === 0; + this.globalData.renderer.save(forceRealStack); + this.globalData.renderer.ctxTransform(this.finalTransform.mat.props); + this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v); + this.renderInnerContent(); + this.globalData.renderer.restore(forceRealStack); + if (this.maskManager.hasMasks) { + this.globalData.renderer.restore(true); + } + if (this._isFirstFrame) { + this._isFirstFrame = false; + } + }, + destroy: function () { + this.canvasContext = null; + this.data = null; + this.globalData = null; + this.maskManager.destroy(); + }, + mHelper: new Matrix(), +}; +CVBaseElement.prototype.hide = CVBaseElement.prototype.hideElement; +CVBaseElement.prototype.show = CVBaseElement.prototype.showElement; + +function CVShapeData(element, data, styles, transformsManager) { + this.styledShapes = []; + this.tr = [0, 0, 0, 0, 0, 0]; + var ty = 4; + if (data.ty === 'rc') { + ty = 5; + } else if (data.ty === 'el') { + ty = 6; + } else if (data.ty === 'sr') { + ty = 7; + } + this.sh = ShapePropertyFactory.getShapeProp(element, data, ty, element); + var i; + var len = styles.length; + var styledShape; + for (i = 0; i < len; i += 1) { + if (!styles[i].closed) { + styledShape = { + transforms: transformsManager.addTransformSequence(styles[i].transforms), + trNodes: [], }; - this.contextData = new CVContextData(); - this.elements = []; - this.pendingElements = []; - this.transformMat = new Matrix(); - this.completeLayers = false; - this.rendererType = 'canvas'; + this.styledShapes.push(styledShape); + styles[i].elements.push(styledShape); } - extendPrototype([CanvasRendererBase], CanvasRenderer); - - CanvasRenderer.prototype.createComp = function (data) { - return new CVCompElement(data, this.globalData, this); - }; - - // Registering renderers - registerRenderer('canvas', CanvasRenderer); - - // Registering shape modifiers - ShapeModifiers.registerModifier('tm', TrimModifier); - ShapeModifiers.registerModifier('pb', PuckerAndBloatModifier); - ShapeModifiers.registerModifier('rp', RepeaterModifier); - ShapeModifiers.registerModifier('rd', RoundCornersModifier); - - const Expressions = (function () { - var ob = {}; - ob.initExpressions = initExpressions; - - function initExpressions(animation) { - var stackCount = 0; - var registers = []; - - function pushExpression() { - stackCount += 1; - } - - function popExpression() { - stackCount -= 1; - if (stackCount === 0) { - releaseInstances(); - } - } - - function registerExpressionProperty(expression) { - if (registers.indexOf(expression) === -1) { - registers.push(expression); - } - } - - function releaseInstances() { - var i; - var len = registers.length; - for (i = 0; i < len; i += 1) { - registers[i].release(); - } - registers.length = 0; - } - - animation.renderer.compInterface = CompExpressionInterface(animation.renderer); - animation.renderer.globalData.projectInterface.registerComposition(animation.renderer); - animation.renderer.globalData.pushExpression = pushExpression; - animation.renderer.globalData.popExpression = popExpression; - animation.renderer.globalData.registerExpressionProperty = registerExpressionProperty; + } +} + +CVShapeData.prototype.setAsAnimated = SVGShapeData.prototype.setAsAnimated; + +function CVShapeElement(data, globalData, comp) { + this.shapes = []; + this.shapesData = data.shapes; + this.stylesList = []; + this.itemsData = []; + this.prevViewData = []; + this.shapeModifiers = []; + this.processedElements = []; + this.transformsManager = new ShapeTransformManager(); + this.initElement(data, globalData, comp); +} + +extendPrototype([BaseElement, TransformElement, CVBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableElement], CVShapeElement); + +CVShapeElement.prototype.initElement = RenderableDOMElement.prototype.initElement; + +CVShapeElement.prototype.transformHelper = { opacity: 1, _opMdf: false }; + +CVShapeElement.prototype.dashResetter = []; + +CVShapeElement.prototype.createContent = function () { + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); +}; + +CVShapeElement.prototype.createStyleElement = function (data, transforms) { + var styleElem = { + data: data, + type: data.ty, + preTransforms: this.transformsManager.addTransformSequence(transforms), + transforms: [], + elements: [], + closed: data.hd === true, + }; + var elementData = {}; + if (data.ty === 'fl' || data.ty === 'st') { + elementData.c = PropertyFactory.getProp(this, data.c, 1, 255, this); + if (!elementData.c.k) { + styleElem.co = 'rgb(' + bmFloor(elementData.c.v[0]) + ',' + bmFloor(elementData.c.v[1]) + ',' + bmFloor(elementData.c.v[2]) + ')'; + } + } else if (data.ty === 'gf' || data.ty === 'gs') { + elementData.s = PropertyFactory.getProp(this, data.s, 1, null, this); + elementData.e = PropertyFactory.getProp(this, data.e, 1, null, this); + elementData.h = PropertyFactory.getProp(this, data.h || { k: 0 }, 0, 0.01, this); + elementData.a = PropertyFactory.getProp(this, data.a || { k: 0 }, 0, degToRads, this); + elementData.g = new GradientProperty(this, data.g, this); + } + elementData.o = PropertyFactory.getProp(this, data.o, 0, 0.01, this); + if (data.ty === 'st' || data.ty === 'gs') { + styleElem.lc = lineCapEnum[data.lc || 2]; + styleElem.lj = lineJoinEnum[data.lj || 2]; + if (data.lj == 1) { // eslint-disable-line eqeqeq + styleElem.ml = data.ml; + } + elementData.w = PropertyFactory.getProp(this, data.w, 0, null, this); + if (!elementData.w.k) { + styleElem.wi = elementData.w.v; + } + if (data.d) { + var d = new DashProperty(this, data.d, 'canvas', this); + elementData.d = d; + if (!elementData.d.k) { + styleElem.da = elementData.d.dashArray; + styleElem.do = elementData.d.dashoffset[0]; + } + } + } else { + styleElem.r = data.r === 2 ? 'evenodd' : 'nonzero'; + } + this.stylesList.push(styleElem); + elementData.style = styleElem; + return elementData; +}; + +CVShapeElement.prototype.createGroupElement = function () { + var elementData = { + it: [], + prevViewData: [], + }; + return elementData; +}; + +CVShapeElement.prototype.createTransformElement = function (data) { + var elementData = { + transform: { + opacity: 1, + _opMdf: false, + key: this.transformsManager.getNewKey(), + op: PropertyFactory.getProp(this, data.o, 0, 0.01, this), + mProps: TransformPropertyFactory.getTransformProperty(this, data, this), + }, + }; + return elementData; +}; + +CVShapeElement.prototype.createShapeElement = function (data) { + var elementData = new CVShapeData(this, data, this.stylesList, this.transformsManager); + + this.shapes.push(elementData); + this.addShapeToModifiers(elementData); + return elementData; +}; + +CVShapeElement.prototype.reloadShapes = function () { + this._isFirstFrame = true; + var i; + var len = this.itemsData.length; + for (i = 0; i < len; i += 1) { + this.prevViewData[i] = this.itemsData[i]; + } + this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, []); + len = this.dynamicProperties.length; + for (i = 0; i < len; i += 1) { + this.dynamicProperties[i].getValue(); + } + this.renderModifiers(); + this.transformsManager.processSequences(this._isFirstFrame); +}; + +CVShapeElement.prototype.addTransformToStyleList = function (transform) { + var i; + var len = this.stylesList.length; + for (i = 0; i < len; i += 1) { + if (!this.stylesList[i].closed) { + this.stylesList[i].transforms.push(transform); + } + } +}; + +CVShapeElement.prototype.removeTransformFromStyleList = function () { + var i; + var len = this.stylesList.length; + for (i = 0; i < len; i += 1) { + if (!this.stylesList[i].closed) { + this.stylesList[i].transforms.pop(); + } + } +}; + +CVShapeElement.prototype.closeStyles = function (styles) { + var i; + var len = styles.length; + for (i = 0; i < len; i += 1) { + styles[i].closed = true; + } +}; + +CVShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, shouldRender, transforms) { + var i; + var len = arr.length - 1; + var j; + var jLen; + var ownStyles = []; + var ownModifiers = []; + var processedPos; + var modifier; + var currentTransform; + var ownTransforms = [].concat(transforms); + for (i = len; i >= 0; i -= 1) { + processedPos = this.searchProcessedElement(arr[i]); + if (!processedPos) { + arr[i]._shouldRender = shouldRender; + } else { + itemsData[i] = prevViewData[processedPos - 1]; + } + if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs') { + if (!processedPos) { + itemsData[i] = this.createStyleElement(arr[i], ownTransforms); + } else { + itemsData[i].style.closed = false; } - return ob; - }()); - - /* eslint-disable */ - /* - Copyright 2014 David Bau. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - */ - - function seedRandom(pool, math) { - // - // The following constants are related to IEEE 754 limits. - // - var global = this, - width = 256, // each RC4 output is 0 <= x < 256 - chunks = 6, // at least six RC4 outputs for each double - digits = 52, // there are 52 significant digits in a double - rngname = 'random', // rngname: name for Math.random and Math.seedrandom - startdenom = math.pow(width, chunks), - significance = math.pow(2, digits), - overflow = significance * 2, - mask = width - 1, - nodecrypto; // node.js crypto module, initialized at the bottom. - - // - // seedrandom() - // This is the seedrandom function described above. - // - function seedrandom(seed, options, callback) { - var key = []; - options = (options === true) ? { entropy: true } : (options || {}); - - // Flatten the seed string or build one from local entropy if needed. - var shortseed = mixkey(flatten( - options.entropy ? [seed, tostring(pool)] : - (seed === null) ? autoseed() : seed, 3), key); - - // Use the seed to initialize an ARC4 generator. - var arc4 = new ARC4(key); - - // This function returns a random double in [0, 1) that contains - // randomness in every bit of the mantissa of the IEEE 754 value. - var prng = function() { - var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48 - d = startdenom, // and denominator d = 2 ^ 48. - x = 0; // and no 'extra last byte'. - while (n < significance) { // Fill up all significant digits by - n = (n + x) * width; // shifting numerator and - d *= width; // denominator and generating a - x = arc4.g(1); // new least-significant-byte. - } - while (n >= overflow) { // To avoid rounding up, before adding - n /= 2; // last byte, shift everything - d /= 2; // right using integer math until - x >>>= 1; // we have exactly the desired bits. - } - return (n + x) / d; // Form the number within [0, 1). - }; - - prng.int32 = function() { return arc4.g(4) | 0; }; - prng.quick = function() { return arc4.g(4) / 0x100000000; }; - prng.double = prng; - - // Mix the randomness into accumulated entropy. - mixkey(tostring(arc4.S), pool); - - // Calling convention: what to return as a function of prng, seed, is_math. - return (options.pass || callback || - function(prng, seed, is_math_call, state) { - if (state) { - // Load the arc4 state from the given state if it has an S array. - if (state.S) { copy(state, arc4); } - // Only provide the .state method if requested via options.state. - prng.state = function() { return copy(arc4, {}); }; - } - - // If called as a method of Math (Math.seedrandom()), mutate - // Math.random because that is how seedrandom.js has worked since v1.0. - if (is_math_call) { math[rngname] = prng; return seed; } - - // Otherwise, it is a newer calling convention, so return the - // prng directly. - else return prng; - })( - prng, - shortseed, - 'global' in options ? options.global : (this == math), - options.state); - } - math['seed' + rngname] = seedrandom; - - // - // ARC4 - // - // An ARC4 implementation. The constructor takes a key in the form of - // an array of at most (width) integers that should be 0 <= x < (width). - // - // The g(count) method returns a pseudorandom integer that concatenates - // the next (count) outputs from ARC4. Its return value is a number x - // that is in the range 0 <= x < (width ^ count). - // - function ARC4(key) { - var t, keylen = key.length, - me = this, i = 0, j = me.i = me.j = 0, s = me.S = []; - - // The empty key [] is treated as [0]. - if (!keylen) { key = [keylen++]; } - - // Set up S using the standard key scheduling algorithm. - while (i < width) { - s[i] = i++; - } - for (i = 0; i < width; i++) { - s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))]; - s[j] = t; - } - // The "g" method returns the next (count) outputs as one number. - me.g = function(count) { - // Using instance members instead of closure state nearly doubles speed. - var t, r = 0, - i = me.i, j = me.j, s = me.S; - while (count--) { - t = s[i = mask & (i + 1)]; - r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))]; - } - me.i = i; me.j = j; - return r; - // For robust unpredictability, the function call below automatically - // discards an initial batch of values. This is called RC4-drop[256]. - // See http://google.com/search?q=rsa+fluhrer+response&btnI - }; + ownStyles.push(itemsData[i].style); + } else if (arr[i].ty === 'gr') { + if (!processedPos) { + itemsData[i] = this.createGroupElement(arr[i]); + } else { + jLen = itemsData[i].it.length; + for (j = 0; j < jLen; j += 1) { + itemsData[i].prevViewData[j] = itemsData[i].it[j]; + } + } + this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, shouldRender, ownTransforms); + } else if (arr[i].ty === 'tr') { + if (!processedPos) { + currentTransform = this.createTransformElement(arr[i]); + itemsData[i] = currentTransform; + } + ownTransforms.push(itemsData[i]); + this.addTransformToStyleList(itemsData[i]); + } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') { + if (!processedPos) { + itemsData[i] = this.createShapeElement(arr[i]); + } + } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'pb') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + modifier.init(this, arr[i]); + itemsData[i] = modifier; + this.shapeModifiers.push(modifier); + } else { + modifier = itemsData[i]; + modifier.closed = false; + } + ownModifiers.push(modifier); + } else if (arr[i].ty === 'rp') { + if (!processedPos) { + modifier = ShapeModifiers.getModifier(arr[i].ty); + itemsData[i] = modifier; + modifier.init(this, arr, i, itemsData); + this.shapeModifiers.push(modifier); + shouldRender = false; + } else { + modifier = itemsData[i]; + modifier.closed = true; + } + ownModifiers.push(modifier); + } + this.addProcessedElement(arr[i], i + 1); + } + this.removeTransformFromStyleList(); + this.closeStyles(ownStyles); + len = ownModifiers.length; + for (i = 0; i < len; i += 1) { + ownModifiers[i].closed = true; + } +}; + +CVShapeElement.prototype.renderInnerContent = function () { + this.transformHelper.opacity = 1; + this.transformHelper._opMdf = false; + this.renderModifiers(); + this.transformsManager.processSequences(this._isFirstFrame); + this.renderShape(this.transformHelper, this.shapesData, this.itemsData, true); +}; + +CVShapeElement.prototype.renderShapeTransform = function (parentTransform, groupTransform) { + if (parentTransform._opMdf || groupTransform.op._mdf || this._isFirstFrame) { + groupTransform.opacity = parentTransform.opacity; + groupTransform.opacity *= groupTransform.op.v; + groupTransform._opMdf = true; + } +}; + +CVShapeElement.prototype.drawLayer = function () { + var i; + var len = this.stylesList.length; + var j; + var jLen; + var k; + var kLen; + var elems; + var nodes; + var renderer = this.globalData.renderer; + var ctx = this.globalData.canvasContext; + var type; + var currentStyle; + for (i = 0; i < len; i += 1) { + currentStyle = this.stylesList[i]; + type = currentStyle.type; + + // Skipping style when + // Stroke width equals 0 + // style should not be rendered (extra unused repeaters) + // current opacity equals 0 + // global opacity equals 0 + if (!(((type === 'st' || type === 'gs') && currentStyle.wi === 0) || !currentStyle.data._shouldRender || currentStyle.coOp === 0 || this.globalData.currentGlobalAlpha === 0)) { + renderer.save(); + elems = currentStyle.elements; + if (type === 'st' || type === 'gs') { + ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd; + ctx.lineWidth = currentStyle.wi; + ctx.lineCap = currentStyle.lc; + ctx.lineJoin = currentStyle.lj; + ctx.miterLimit = currentStyle.ml || 0; + } else { + ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd; + } + renderer.ctxOpacity(currentStyle.coOp); + if (type !== 'st' && type !== 'gs') { + ctx.beginPath(); + } + renderer.ctxTransform(currentStyle.preTransforms.finalTransform.props); + jLen = elems.length; + for (j = 0; j < jLen; j += 1) { + if (type === 'st' || type === 'gs') { + ctx.beginPath(); + if (currentStyle.da) { + ctx.setLineDash(currentStyle.da); + ctx.lineDashOffset = currentStyle.do; + } } + nodes = elems[j].trNodes; + kLen = nodes.length; - // - // copy() - // Copies internal state of ARC4 to or from a plain object. - // - function copy(f, t) { - t.i = f.i; - t.j = f.j; - t.S = f.S.slice(); - return t; + for (k = 0; k < kLen; k += 1) { + if (nodes[k].t === 'm') { + ctx.moveTo(nodes[k].p[0], nodes[k].p[1]); + } else if (nodes[k].t === 'c') { + ctx.bezierCurveTo(nodes[k].pts[0], nodes[k].pts[1], nodes[k].pts[2], nodes[k].pts[3], nodes[k].pts[4], nodes[k].pts[5]); + } else { + ctx.closePath(); + } } - - // - // flatten() - // Converts an object tree to nested arrays of strings. - // - function flatten(obj, depth) { - var result = [], typ = (typeof obj), prop; - if (depth && typ == 'object') { - for (prop in obj) { - try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} - } - } - return (result.length ? result : typ == 'string' ? obj : obj + '\0'); + if (type === 'st' || type === 'gs') { + ctx.stroke(); + if (currentStyle.da) { + ctx.setLineDash(this.dashResetter); + } } - - // - // mixkey() - // Mixes a string seed into a key that is an array of integers, and - // returns a shortened string seed that is equivalent to the result key. - // - function mixkey(seed, key) { - var stringseed = seed + '', smear, j = 0; - while (j < stringseed.length) { - key[mask & j] = - mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++)); - } - return tostring(key); + } + if (type !== 'st' && type !== 'gs') { + ctx.fill(currentStyle.r); + } + renderer.restore(); + } + } +}; + +CVShapeElement.prototype.renderShape = function (parentTransform, items, data, isMain) { + var i; + var len = items.length - 1; + var groupTransform; + groupTransform = parentTransform; + for (i = len; i >= 0; i -= 1) { + if (items[i].ty === 'tr') { + groupTransform = data[i].transform; + this.renderShapeTransform(parentTransform, groupTransform); + } else if (items[i].ty === 'sh' || items[i].ty === 'el' || items[i].ty === 'rc' || items[i].ty === 'sr') { + this.renderPath(items[i], data[i]); + } else if (items[i].ty === 'fl') { + this.renderFill(items[i], data[i], groupTransform); + } else if (items[i].ty === 'st') { + this.renderStroke(items[i], data[i], groupTransform); + } else if (items[i].ty === 'gf' || items[i].ty === 'gs') { + this.renderGradientFill(items[i], data[i], groupTransform); + } else if (items[i].ty === 'gr') { + this.renderShape(groupTransform, items[i].it, data[i].it); + } else if (items[i].ty === 'tm') { + // + } + } + if (isMain) { + this.drawLayer(); + } +}; + +CVShapeElement.prototype.renderStyledShape = function (styledShape, shape) { + if (this._isFirstFrame || shape._mdf || styledShape.transforms._mdf) { + var shapeNodes = styledShape.trNodes; + var paths = shape.paths; + var i; + var len; + var j; + var jLen = paths._length; + shapeNodes.length = 0; + var groupTransformMat = styledShape.transforms.finalTransform; + for (j = 0; j < jLen; j += 1) { + var pathNodes = paths.shapes[j]; + if (pathNodes && pathNodes.v) { + len = pathNodes._length; + for (i = 1; i < len; i += 1) { + if (i === 1) { + shapeNodes.push({ + t: 'm', + p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), + }); + } + shapeNodes.push({ + t: 'c', + pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[i], pathNodes.v[i]), + }); } - - // - // autoseed() - // Returns an object for autoseeding, using window.crypto and Node crypto - // module if available. - // - function autoseed() { - try { - if (nodecrypto) { return tostring(nodecrypto.randomBytes(width)); } - var out = new Uint8Array(width); - (global.crypto || global.msCrypto).getRandomValues(out); - return tostring(out); - } catch (e) { - var browser = global.navigator, - plugins = browser && browser.plugins; - return [+new Date(), global, plugins, global.screen, tostring(pool)]; - } + if (len === 1) { + shapeNodes.push({ + t: 'm', + p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0), + }); } - - // - // tostring() - // Converts an array of charcodes to a string - // - function tostring(a) { - return String.fromCharCode.apply(0, a); + if (pathNodes.c && len) { + shapeNodes.push({ + t: 'c', + pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[0], pathNodes.v[0]), + }); + shapeNodes.push({ + t: 'z', + }); } - - // - // When seedrandom.js is loaded, we immediately mix a few bits - // from the built-in RNG into the entropy pool. Because we do - // not want to interfere with deterministic PRNG state later, - // seedrandom will not call math.random on its own again after - // initialization. - // - mixkey(math.random(), pool); - - // - // Nodejs and AMD support: export the implementation as a module using - // either convention. - // - - // End anonymous scope, and pass initial values. - }; - - function initialize$2(BMMath) { - seedRandom([], BMMath); + } + } + styledShape.trNodes = shapeNodes; + } +}; + +CVShapeElement.prototype.renderPath = function (pathData, itemData) { + if (pathData.hd !== true && pathData._shouldRender) { + var i; + var len = itemData.styledShapes.length; + for (i = 0; i < len; i += 1) { + this.renderStyledShape(itemData.styledShapes[i], itemData.sh); + } + } +}; + +CVShapeElement.prototype.renderFill = function (styleData, itemData, groupTransform) { + var styleElem = itemData.style; + + if (itemData.c._mdf || this._isFirstFrame) { + styleElem.co = 'rgb(' + + bmFloor(itemData.c.v[0]) + ',' + + bmFloor(itemData.c.v[1]) + ',' + + bmFloor(itemData.c.v[2]) + ')'; + } + if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { + styleElem.coOp = itemData.o.v * groupTransform.opacity; + } +}; + +CVShapeElement.prototype.renderGradientFill = function (styleData, itemData, groupTransform) { + var styleElem = itemData.style; + var grd; + if (!styleElem.grd || itemData.g._mdf || itemData.s._mdf || itemData.e._mdf || (styleData.t !== 1 && (itemData.h._mdf || itemData.a._mdf))) { + var ctx = this.globalData.canvasContext; + var pt1 = itemData.s.v; + var pt2 = itemData.e.v; + if (styleData.t === 1) { + grd = ctx.createLinearGradient(pt1[0], pt1[1], pt2[0], pt2[1]); + } else { + var rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2)); + var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]); + + var percent = itemData.h.v; + if (percent >= 1) { + percent = 0.99; + } else if (percent <= -1) { + percent = -0.99; + } + var dist = rad * percent; + var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]; + var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]; + grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad); } - var propTypes = { - SHAPE: 'shape', - }; - - /* eslint-disable camelcase */ + var i; + var len = styleData.g.p; + var cValues = itemData.g.c; + var opacity = 1; - const ExpressionManager = (function () { - 'use strict'; + for (i = 0; i < len; i += 1) { + if (itemData.g._hasOpacity && itemData.g._collapsable) { + opacity = itemData.g.o[i * 2 + 1]; + } + grd.addColorStop(cValues[i * 4] / 100, 'rgba(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ',' + opacity + ')'); + } + styleElem.grd = grd; + } + styleElem.coOp = itemData.o.v * groupTransform.opacity; +}; + +CVShapeElement.prototype.renderStroke = function (styleData, itemData, groupTransform) { + var styleElem = itemData.style; + var d = itemData.d; + if (d && (d._mdf || this._isFirstFrame)) { + styleElem.da = d.dashArray; + styleElem.do = d.dashoffset[0]; + } + if (itemData.c._mdf || this._isFirstFrame) { + styleElem.co = 'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'; + } + if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) { + styleElem.coOp = itemData.o.v * groupTransform.opacity; + } + if (itemData.w._mdf || this._isFirstFrame) { + styleElem.wi = itemData.w.v; + } +}; + +CVShapeElement.prototype.destroy = function () { + this.shapesData = null; + this.globalData = null; + this.canvasContext = null; + this.stylesList.length = 0; + this.itemsData.length = 0; +}; + +function CVTextElement(data, globalData, comp) { + this.textSpans = []; + this.yOffset = 0; + this.fillColorAnim = false; + this.strokeColorAnim = false; + this.strokeWidthAnim = false; + this.stroke = false; + this.fill = false; + this.justifyOffset = 0; + this.currentRender = null; + this.renderType = 'canvas'; + this.values = { + fill: 'rgba(0,0,0,0)', + stroke: 'rgba(0,0,0,0)', + sWidth: 0, + fValue: '', + }; + this.initElement(data, globalData, comp); +} +extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement, ITextElement], CVTextElement); + +CVTextElement.prototype.tHelper = createTag('canvas').getContext('2d'); + +CVTextElement.prototype.buildNewText = function () { + var documentData = this.textProperty.currentData; + this.renderedLetters = createSizedArray(documentData.l ? documentData.l.length : 0); + + var hasFill = false; + if (documentData.fc) { + hasFill = true; + this.values.fill = this.buildColor(documentData.fc); + } else { + this.values.fill = 'rgba(0,0,0,0)'; + } + this.fill = hasFill; + var hasStroke = false; + if (documentData.sc) { + hasStroke = true; + this.values.stroke = this.buildColor(documentData.sc); + this.values.sWidth = documentData.sw; + } + var fontData = this.globalData.fontManager.getFontByName(documentData.f); + var i; + var len; + var letters = documentData.l; + var matrixHelper = this.mHelper; + this.stroke = hasStroke; + this.values.fValue = documentData.finalSize + 'px ' + this.globalData.fontManager.getFontByName(documentData.f).fFamily; + len = documentData.finalText.length; + // this.tHelper.font = this.values.fValue; + var charData; + var shapeData; + var k; + var kLen; + var shapes; + var j; + var jLen; + var pathNodes; + var commands; + var pathArr; + var singleShape = this.data.singleShape; + var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; + var xPos = 0; + var yPos = 0; + var firstLine = true; + var cnt = 0; + for (i = 0; i < len; i += 1) { + charData = this.globalData.fontManager.getCharData(documentData.finalText[i], fontData.fStyle, this.globalData.fontManager.getFontByName(documentData.f).fFamily); + shapeData = (charData && charData.data) || {}; + matrixHelper.reset(); + if (singleShape && letters[i].n) { + xPos = -trackingOffset; + yPos += documentData.yOffset; + yPos += firstLine ? 1 : 0; + firstLine = false; + } + shapes = shapeData.shapes ? shapeData.shapes[0].it : []; + jLen = shapes.length; + matrixHelper.scale(documentData.finalSize / 100, documentData.finalSize / 100); + if (singleShape) { + this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); + } + commands = createSizedArray(jLen - 1); + var commandsCounter = 0; + for (j = 0; j < jLen; j += 1) { + if (shapes[j].ty === 'sh') { + kLen = shapes[j].ks.k.i.length; + pathNodes = shapes[j].ks.k; + pathArr = []; + for (k = 1; k < kLen; k += 1) { + if (k === 1) { + pathArr.push(matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); + } + pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToY(pathNodes.i[k][0], pathNodes.i[k][1], 0), matrixHelper.applyToX(pathNodes.v[k][0], pathNodes.v[k][1], 0), matrixHelper.applyToY(pathNodes.v[k][0], pathNodes.v[k][1], 0)); + } + pathArr.push(matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0), matrixHelper.applyToX(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToY(pathNodes.i[0][0], pathNodes.i[0][1], 0), matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0), matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0)); + commands[commandsCounter] = pathArr; + commandsCounter += 1; + } + } + if (singleShape) { + xPos += letters[i].l; + xPos += trackingOffset; + } + if (this.textSpans[cnt]) { + this.textSpans[cnt].elem = commands; + } else { + this.textSpans[cnt] = { elem: commands }; + } + cnt += 1; + } +}; + +CVTextElement.prototype.renderInnerContent = function () { + var ctx = this.canvasContext; + ctx.font = this.values.fValue; + ctx.lineCap = 'butt'; + ctx.lineJoin = 'miter'; + ctx.miterLimit = 4; + + if (!this.data.singleShape) { + this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); + } + + var i; + var len; + var j; + var jLen; + var k; + var kLen; + var renderedLetters = this.textAnimator.renderedLetters; + + var letters = this.textProperty.currentData.l; + + len = letters.length; + var renderedLetter; + var lastFill = null; + var lastStroke = null; + var lastStrokeW = null; + var commands; + var pathArr; + for (i = 0; i < len; i += 1) { + if (!letters[i].n) { + renderedLetter = renderedLetters[i]; + if (renderedLetter) { + this.globalData.renderer.save(); + this.globalData.renderer.ctxTransform(renderedLetter.p); + this.globalData.renderer.ctxOpacity(renderedLetter.o); + } + if (this.fill) { + if (renderedLetter && renderedLetter.fc) { + if (lastFill !== renderedLetter.fc) { + lastFill = renderedLetter.fc; + ctx.fillStyle = renderedLetter.fc; + } + } else if (lastFill !== this.values.fill) { + lastFill = this.values.fill; + ctx.fillStyle = this.values.fill; + } + commands = this.textSpans[i].elem; + jLen = commands.length; + this.globalData.canvasContext.beginPath(); + for (j = 0; j < jLen; j += 1) { + pathArr = commands[j]; + kLen = pathArr.length; + this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); + for (k = 2; k < kLen; k += 6) { + this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); + } + } + this.globalData.canvasContext.closePath(); + this.globalData.canvasContext.fill(); + /// ctx.fillText(this.textSpans[i].val,0,0); + } + if (this.stroke) { + if (renderedLetter && renderedLetter.sw) { + if (lastStrokeW !== renderedLetter.sw) { + lastStrokeW = renderedLetter.sw; + ctx.lineWidth = renderedLetter.sw; + } + } else if (lastStrokeW !== this.values.sWidth) { + lastStrokeW = this.values.sWidth; + ctx.lineWidth = this.values.sWidth; + } + if (renderedLetter && renderedLetter.sc) { + if (lastStroke !== renderedLetter.sc) { + lastStroke = renderedLetter.sc; + ctx.strokeStyle = renderedLetter.sc; + } + } else if (lastStroke !== this.values.stroke) { + lastStroke = this.values.stroke; + ctx.strokeStyle = this.values.stroke; + } + commands = this.textSpans[i].elem; + jLen = commands.length; + this.globalData.canvasContext.beginPath(); + for (j = 0; j < jLen; j += 1) { + pathArr = commands[j]; + kLen = pathArr.length; + this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1]); + for (k = 2; k < kLen; k += 6) { + this.globalData.canvasContext.bezierCurveTo(pathArr[k], pathArr[k + 1], pathArr[k + 2], pathArr[k + 3], pathArr[k + 4], pathArr[k + 5]); + } + } + this.globalData.canvasContext.closePath(); + this.globalData.canvasContext.stroke(); + /// ctx.strokeText(letters[i].val,0,0); + } + if (renderedLetter) { + this.globalData.renderer.restore(); + } + } + } +}; + +function CVImageElement(data, globalData, comp) { + this.assetData = globalData.getAssetData(data.refId); + this.img = globalData.imageLoader.getAsset(this.assetData); + this.initElement(data, globalData, comp); +} +extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVImageElement); + +CVImageElement.prototype.initElement = SVGShapeElement.prototype.initElement; +CVImageElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; + +CVImageElement.prototype.createContent = function () { + if (this.img.width && (this.assetData.w !== this.img.width || this.assetData.h !== this.img.height)) { + var canvas = createTag('canvas'); + canvas.width = this.assetData.w; + canvas.height = this.assetData.h; + var ctx = canvas.getContext('2d'); + + var imgW = this.img.width; + var imgH = this.img.height; + var imgRel = imgW / imgH; + var canvasRel = this.assetData.w / this.assetData.h; + var widthCrop; + var heightCrop; + var par = this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio; + if ((imgRel > canvasRel && par === 'xMidYMid slice') || (imgRel < canvasRel && par !== 'xMidYMid slice')) { + heightCrop = imgH; + widthCrop = heightCrop * canvasRel; + } else { + widthCrop = imgW; + heightCrop = widthCrop / canvasRel; + } + ctx.drawImage(this.img, (imgW - widthCrop) / 2, (imgH - heightCrop) / 2, widthCrop, heightCrop, 0, 0, this.assetData.w, this.assetData.h); + this.img = canvas; + } +}; + +CVImageElement.prototype.renderInnerContent = function () { + this.canvasContext.drawImage(this.img, 0, 0); +}; + +CVImageElement.prototype.destroy = function () { + this.img = null; +}; + +function CVSolidElement(data, globalData, comp) { + this.initElement(data, globalData, comp); +} +extendPrototype([BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement], CVSolidElement); + +CVSolidElement.prototype.initElement = SVGShapeElement.prototype.initElement; +CVSolidElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame; + +CVSolidElement.prototype.renderInnerContent = function () { + var ctx = this.canvasContext; + ctx.fillStyle = this.data.sc; + ctx.fillRect(0, 0, this.data.sw, this.data.sh); + // +}; + +function CanvasRendererBase(animationItem, config) { + this.animationItem = animationItem; + this.renderConfig = { + clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, + context: (config && config.context) || null, + progressiveLoad: (config && config.progressiveLoad) || false, + preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', + imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', + contentVisibility: (config && config.contentVisibility) || 'visible', + className: (config && config.className) || '', + id: (config && config.id) || '', + }; + this.renderConfig.dpr = (config && config.dpr) || 1; + if (this.animationItem.wrapper) { + this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; + } + this.renderedFrame = -1; + this.globalData = { + frameNum: -1, + _mdf: false, + renderConfig: this.renderConfig, + currentGlobalAlpha: -1, + }; + this.contextData = new CVContextData(); + this.elements = []; + this.pendingElements = []; + this.transformMat = new Matrix(); + this.completeLayers = false; + this.rendererType = 'canvas'; +} +extendPrototype([BaseRenderer], CanvasRendererBase); + +CanvasRendererBase.prototype.createShape = function (data) { + return new CVShapeElement(data, this.globalData, this); +}; + +CanvasRendererBase.prototype.createText = function (data) { + return new CVTextElement(data, this.globalData, this); +}; + +CanvasRendererBase.prototype.createImage = function (data) { + return new CVImageElement(data, this.globalData, this); +}; + +CanvasRendererBase.prototype.createSolid = function (data) { + return new CVSolidElement(data, this.globalData, this); +}; + +CanvasRendererBase.prototype.createNull = SVGRenderer.prototype.createNull; + +CanvasRendererBase.prototype.ctxTransform = function (props) { + if (props[0] === 1 && props[1] === 0 && props[4] === 0 && props[5] === 1 && props[12] === 0 && props[13] === 0) { + return; + } + if (!this.renderConfig.clearCanvas) { + this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13]); + return; + } + this.transformMat.cloneFromProps(props); + var cProps = this.contextData.cTr.props; + this.transformMat.transform(cProps[0], cProps[1], cProps[2], cProps[3], cProps[4], cProps[5], cProps[6], cProps[7], cProps[8], cProps[9], cProps[10], cProps[11], cProps[12], cProps[13], cProps[14], cProps[15]); + // this.contextData.cTr.transform(props[0],props[1],props[2],props[3],props[4],props[5],props[6],props[7],props[8],props[9],props[10],props[11],props[12],props[13],props[14],props[15]); + this.contextData.cTr.cloneFromProps(this.transformMat.props); + var trProps = this.contextData.cTr.props; + this.canvasContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13]); +}; + +CanvasRendererBase.prototype.ctxOpacity = function (op) { + /* if(op === 1){ + return; + } */ + if (!this.renderConfig.clearCanvas) { + this.canvasContext.globalAlpha *= op < 0 ? 0 : op; + this.globalData.currentGlobalAlpha = this.contextData.cO; + return; + } + this.contextData.cO *= op < 0 ? 0 : op; + if (this.globalData.currentGlobalAlpha !== this.contextData.cO) { + this.canvasContext.globalAlpha = this.contextData.cO; + this.globalData.currentGlobalAlpha = this.contextData.cO; + } +}; + +CanvasRendererBase.prototype.reset = function () { + if (!this.renderConfig.clearCanvas) { + this.canvasContext.restore(); + return; + } + this.contextData.reset(); +}; + +CanvasRendererBase.prototype.save = function (actionFlag) { + if (!this.renderConfig.clearCanvas) { + this.canvasContext.save(); + return; + } + if (actionFlag) { + this.canvasContext.save(); + } + var props = this.contextData.cTr.props; + if (this.contextData._length <= this.contextData.cArrPos) { + this.contextData.duplicate(); + } + var i; + var arr = this.contextData.saved[this.contextData.cArrPos]; + for (i = 0; i < 16; i += 1) { + arr[i] = props[i]; + } + this.contextData.savedOp[this.contextData.cArrPos] = this.contextData.cO; + this.contextData.cArrPos += 1; +}; + +CanvasRendererBase.prototype.restore = function (actionFlag) { + if (!this.renderConfig.clearCanvas) { + this.canvasContext.restore(); + return; + } + if (actionFlag) { + this.canvasContext.restore(); + this.globalData.blendMode = 'source-over'; + } + this.contextData.cArrPos -= 1; + var popped = this.contextData.saved[this.contextData.cArrPos]; + var i; + var arr = this.contextData.cTr.props; + for (i = 0; i < 16; i += 1) { + arr[i] = popped[i]; + } + this.canvasContext.setTransform(popped[0], popped[1], popped[4], popped[5], popped[12], popped[13]); + popped = this.contextData.savedOp[this.contextData.cArrPos]; + this.contextData.cO = popped; + if (this.globalData.currentGlobalAlpha !== popped) { + this.canvasContext.globalAlpha = popped; + this.globalData.currentGlobalAlpha = popped; + } +}; + +CanvasRendererBase.prototype.configAnimation = function (animData) { + if (this.animationItem.wrapper) { + this.animationItem.container = createTag('canvas'); + var containerStyle = this.animationItem.container.style; + containerStyle.width = '100%'; + containerStyle.height = '100%'; + var origin = '0px 0px 0px'; + containerStyle.transformOrigin = origin; + containerStyle.mozTransformOrigin = origin; + containerStyle.webkitTransformOrigin = origin; + containerStyle['-webkit-transform'] = origin; + containerStyle.contentVisibility = this.renderConfig.contentVisibility; + this.animationItem.wrapper.appendChild(this.animationItem.container); + this.canvasContext = this.animationItem.container.getContext('2d'); + if (this.renderConfig.className) { + this.animationItem.container.setAttribute('class', this.renderConfig.className); + } + if (this.renderConfig.id) { + this.animationItem.container.setAttribute('id', this.renderConfig.id); + } + } else { + this.canvasContext = this.renderConfig.context; + } + this.data = animData; + this.layers = animData.layers; + this.transformCanvas = { + w: animData.w, + h: animData.h, + sx: 0, + sy: 0, + tx: 0, + ty: 0, + }; + this.setupGlobalData(animData, document.body); + this.globalData.canvasContext = this.canvasContext; + this.globalData.renderer = this; + this.globalData.isDashed = false; + this.globalData.progressiveLoad = this.renderConfig.progressiveLoad; + this.globalData.transformCanvas = this.transformCanvas; + this.elements = createSizedArray(animData.layers.length); + + this.updateContainerSize(); +}; + +CanvasRendererBase.prototype.updateContainerSize = function () { + this.reset(); + var elementWidth; + var elementHeight; + if (this.animationItem.wrapper && this.animationItem.container) { + elementWidth = this.animationItem.wrapper.offsetWidth; + elementHeight = this.animationItem.wrapper.offsetHeight; + this.animationItem.container.setAttribute('width', elementWidth * this.renderConfig.dpr); + this.animationItem.container.setAttribute('height', elementHeight * this.renderConfig.dpr); + } else { + elementWidth = this.canvasContext.canvas.width * this.renderConfig.dpr; + elementHeight = this.canvasContext.canvas.height * this.renderConfig.dpr; + } + var elementRel; + var animationRel; + if (this.renderConfig.preserveAspectRatio.indexOf('meet') !== -1 || this.renderConfig.preserveAspectRatio.indexOf('slice') !== -1) { + var par = this.renderConfig.preserveAspectRatio.split(' '); + var fillType = par[1] || 'meet'; + var pos = par[0] || 'xMidYMid'; + var xPos = pos.substr(0, 4); + var yPos = pos.substr(4); + elementRel = elementWidth / elementHeight; + animationRel = this.transformCanvas.w / this.transformCanvas.h; + if ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice')) { + this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); + this.transformCanvas.sy = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); + } else { + this.transformCanvas.sx = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); + this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); + } - var ob = {}; - var Math = BMMath; - var window = null; - var document = null; - var XMLHttpRequest = null; - var fetch = null; - var frames = null; - initialize$2(BMMath); + if (xPos === 'xMid' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { + this.transformCanvas.tx = ((elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) / 2) * this.renderConfig.dpr; + } else if (xPos === 'xMax' && ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))) { + this.transformCanvas.tx = (elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) * this.renderConfig.dpr; + } else { + this.transformCanvas.tx = 0; + } + if (yPos === 'YMid' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { + this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w)) / 2) * this.renderConfig.dpr; + } else if (yPos === 'YMax' && ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))) { + this.transformCanvas.ty = ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w))) * this.renderConfig.dpr; + } else { + this.transformCanvas.ty = 0; + } + } else if (this.renderConfig.preserveAspectRatio === 'none') { + this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr); + this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr); + this.transformCanvas.tx = 0; + this.transformCanvas.ty = 0; + } else { + this.transformCanvas.sx = this.renderConfig.dpr; + this.transformCanvas.sy = this.renderConfig.dpr; + this.transformCanvas.tx = 0; + this.transformCanvas.ty = 0; + } + this.transformCanvas.props = [this.transformCanvas.sx, 0, 0, 0, 0, this.transformCanvas.sy, 0, 0, 0, 0, 1, 0, this.transformCanvas.tx, this.transformCanvas.ty, 0, 1]; + /* var i, len = this.elements.length; + for(i=0;i= 0; i -= 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + this.elements.length = 0; + this.globalData.canvasContext = null; + this.animationItem.container = null; + this.destroyed = true; +}; + +CanvasRendererBase.prototype.renderFrame = function (num, forceRender) { + if ((this.renderedFrame === num && this.renderConfig.clearCanvas === true && !forceRender) || this.destroyed || num === -1) { + return; + } + this.renderedFrame = num; + this.globalData.frameNum = num - this.animationItem._isFirstFrame; + this.globalData.frameId += 1; + this.globalData._mdf = !this.renderConfig.clearCanvas || forceRender; + this.globalData.projectInterface.currentFrame = num; + + // console.log('--------'); + // console.log('NEW: ',num); + var i; + var len = this.layers.length; + if (!this.completeLayers) { + this.checkLayers(num); + } + + for (i = 0; i < len; i += 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].prepareFrame(num - this.layers[i].st); + } + } + if (this.globalData._mdf) { + if (this.renderConfig.clearCanvas === true) { + this.canvasContext.clearRect(0, 0, this.transformCanvas.w, this.transformCanvas.h); + } else { + this.save(); + } + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } + if (this.renderConfig.clearCanvas !== true) { + this.restore(); + } + } +}; + +CanvasRendererBase.prototype.buildItem = function (pos) { + var elements = this.elements; + if (elements[pos] || this.layers[pos].ty === 99) { + return; + } + var element = this.createItem(this.layers[pos], this, this.globalData); + elements[pos] = element; + element.initExpressions(); + /* if(this.layers[pos].ty === 0){ + element.resize(this.globalData.transformCanvas); + } */ +}; + +CanvasRendererBase.prototype.checkPendingElements = function () { + while (this.pendingElements.length) { + var element = this.pendingElements.pop(); + element.checkParenting(); + } +}; + +CanvasRendererBase.prototype.hide = function () { + this.animationItem.container.style.display = 'none'; +}; + +CanvasRendererBase.prototype.show = function () { + this.animationItem.container.style.display = 'block'; +}; + +function CVCompElement(data, globalData, comp) { + this.completeLayers = false; + this.layers = data.layers; + this.pendingElements = []; + this.elements = createSizedArray(this.layers.length); + this.initElement(data, globalData, comp); + this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }; +} + +extendPrototype([CanvasRendererBase, ICompElement, CVBaseElement], CVCompElement); + +CVCompElement.prototype.renderInnerContent = function () { + var ctx = this.canvasContext; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(this.data.w, 0); + ctx.lineTo(this.data.w, this.data.h); + ctx.lineTo(0, this.data.h); + ctx.lineTo(0, 0); + ctx.clip(); + var i; + var len = this.layers.length; + for (i = len - 1; i >= 0; i -= 1) { + if (this.completeLayers || this.elements[i]) { + this.elements[i].renderFrame(); + } + } +}; + +CVCompElement.prototype.destroy = function () { + var i; + var len = this.layers.length; + for (i = len - 1; i >= 0; i -= 1) { + if (this.elements[i]) { + this.elements[i].destroy(); + } + } + this.layers = null; + this.elements = null; +}; + +CVCompElement.prototype.createComp = function (data) { + return new CVCompElement(data, this.globalData, this); +}; + +function CanvasRenderer(animationItem, config) { + this.animationItem = animationItem; + this.renderConfig = { + clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true, + context: (config && config.context) || null, + progressiveLoad: (config && config.progressiveLoad) || false, + preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet', + imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice', + contentVisibility: (config && config.contentVisibility) || 'visible', + className: (config && config.className) || '', + id: (config && config.id) || '', + }; + this.renderConfig.dpr = (config && config.dpr) || 1; + if (this.animationItem.wrapper) { + this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1; + } + this.renderedFrame = -1; + this.globalData = { + frameNum: -1, + _mdf: false, + renderConfig: this.renderConfig, + currentGlobalAlpha: -1, + }; + this.contextData = new CVContextData(); + this.elements = []; + this.pendingElements = []; + this.transformMat = new Matrix(); + this.completeLayers = false; + this.rendererType = 'canvas'; +} +extendPrototype([CanvasRendererBase], CanvasRenderer); + +CanvasRenderer.prototype.createComp = function (data) { + return new CVCompElement(data, this.globalData, this); +}; + +// Registering renderers +registerRenderer('canvas', CanvasRenderer); + +// Registering shape modifiers +ShapeModifiers.registerModifier('tm', TrimModifier); +ShapeModifiers.registerModifier('pb', PuckerAndBloatModifier); +ShapeModifiers.registerModifier('rp', RepeaterModifier); +ShapeModifiers.registerModifier('rd', RoundCornersModifier); + +const Expressions = (function () { + var ob = {}; + ob.initExpressions = initExpressions; + + function initExpressions(animation) { + var stackCount = 0; + var registers = []; + + function pushExpression() { + stackCount += 1; + } - function $bm_isInstanceOfArray(arr) { - return arr.constructor === Array || arr.constructor === Float32Array; + function popExpression() { + stackCount -= 1; + if (stackCount === 0) { + releaseInstances(); } + } - function isNumerable(tOfV, v) { - return tOfV === 'number' || tOfV === 'boolean' || tOfV === 'string' || v instanceof Number; + function registerExpressionProperty(expression) { + if (registers.indexOf(expression) === -1) { + registers.push(expression); } + } - function $bm_neg(a) { - var tOfA = typeof a; - if (tOfA === 'number' || tOfA === 'boolean' || a instanceof Number) { - return -a; - } - if ($bm_isInstanceOfArray(a)) { - var i; - var lenA = a.length; - var retArr = []; - for (i = 0; i < lenA; i += 1) { - retArr[i] = -a[i]; - } - return retArr; - } - if (a.propType) { - return a.v; - } - return -a; + function releaseInstances() { + var i; + var len = registers.length; + for (i = 0; i < len; i += 1) { + registers[i].release(); } + registers.length = 0; + } - var easeInBez = BezierFactory.getBezierEasing(0.333, 0, 0.833, 0.833, 'easeIn').get; - var easeOutBez = BezierFactory.getBezierEasing(0.167, 0.167, 0.667, 1, 'easeOut').get; - var easeInOutBez = BezierFactory.getBezierEasing(0.33, 0, 0.667, 1, 'easeInOut').get; + animation.renderer.compInterface = CompExpressionInterface(animation.renderer); + animation.renderer.globalData.projectInterface.registerComposition(animation.renderer); + animation.renderer.globalData.pushExpression = pushExpression; + animation.renderer.globalData.popExpression = popExpression; + animation.renderer.globalData.registerExpressionProperty = registerExpressionProperty; + } + return ob; +}()); + +/* eslint-disable */ +/* + Copyright 2014 David Bau. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +function seedRandom(pool, math) { +// +// The following constants are related to IEEE 754 limits. +// + var global = this, + width = 256, // each RC4 output is 0 <= x < 256 + chunks = 6, // at least six RC4 outputs for each double + digits = 52, // there are 52 significant digits in a double + rngname = 'random', // rngname: name for Math.random and Math.seedrandom + startdenom = math.pow(width, chunks), + significance = math.pow(2, digits), + overflow = significance * 2, + mask = width - 1, + nodecrypto; // node.js crypto module, initialized at the bottom. + +// +// seedrandom() +// This is the seedrandom function described above. +// + function seedrandom(seed, options, callback) { + var key = []; + options = (options === true) ? { entropy: true } : (options || {}); + + // Flatten the seed string or build one from local entropy if needed. + var shortseed = mixkey(flatten( + options.entropy ? [seed, tostring(pool)] : + (seed === null) ? autoseed() : seed, 3), key); + + // Use the seed to initialize an ARC4 generator. + var arc4 = new ARC4(key); + + // This function returns a random double in [0, 1) that contains + // randomness in every bit of the mantissa of the IEEE 754 value. + var prng = function() { + var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48 + d = startdenom, // and denominator d = 2 ^ 48. + x = 0; // and no 'extra last byte'. + while (n < significance) { // Fill up all significant digits by + n = (n + x) * width; // shifting numerator and + d *= width; // denominator and generating a + x = arc4.g(1); // new least-significant-byte. + } + while (n >= overflow) { // To avoid rounding up, before adding + n /= 2; // last byte, shift everything + d /= 2; // right using integer math until + x >>>= 1; // we have exactly the desired bits. + } + return (n + x) / d; // Form the number within [0, 1). + }; - function sum(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - if (tOfA === 'string' || tOfB === 'string') { - return a + b; - } - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - return a + b; - } - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - a = a.slice(0); - a[0] += b; - return a; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - b = b.slice(0); - b[0] = a + b[0]; - return b; - } - if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { - var i = 0; - var lenA = a.length; - var lenB = b.length; - var retArr = []; - while (i < lenA || i < lenB) { - if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { - retArr[i] = a[i] + b[i]; - } else { - retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; - } - i += 1; - } - return retArr; - } - return 0; - } - var add = sum; + prng.int32 = function() { return arc4.g(4) | 0; }; + prng.quick = function() { return arc4.g(4) / 0x100000000; }; + prng.double = prng; + + // Mix the randomness into accumulated entropy. + mixkey(tostring(arc4.S), pool); + + // Calling convention: what to return as a function of prng, seed, is_math. + return (options.pass || callback || + function(prng, seed, is_math_call, state) { + if (state) { + // Load the arc4 state from the given state if it has an S array. + if (state.S) { copy(state, arc4); } + // Only provide the .state method if requested via options.state. + prng.state = function() { return copy(arc4, {}); }; + } + + // If called as a method of Math (Math.seedrandom()), mutate + // Math.random because that is how seedrandom.js has worked since v1.0. + if (is_math_call) { math[rngname] = prng; return seed; } + + // Otherwise, it is a newer calling convention, so return the + // prng directly. + else return prng; + })( + prng, + shortseed, + 'global' in options ? options.global : (this == math), + options.state); + } + math['seed' + rngname] = seedrandom; + +// +// ARC4 +// +// An ARC4 implementation. The constructor takes a key in the form of +// an array of at most (width) integers that should be 0 <= x < (width). +// +// The g(count) method returns a pseudorandom integer that concatenates +// the next (count) outputs from ARC4. Its return value is a number x +// that is in the range 0 <= x < (width ^ count). +// + function ARC4(key) { + var t, keylen = key.length, + me = this, i = 0, j = me.i = me.j = 0, s = me.S = []; + + // The empty key [] is treated as [0]. + if (!keylen) { key = [keylen++]; } + + // Set up S using the standard key scheduling algorithm. + while (i < width) { + s[i] = i++; + } + for (i = 0; i < width; i++) { + s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))]; + s[j] = t; + } + + // The "g" method returns the next (count) outputs as one number. + me.g = function(count) { + // Using instance members instead of closure state nearly doubles speed. + var t, r = 0, + i = me.i, j = me.j, s = me.S; + while (count--) { + t = s[i = mask & (i + 1)]; + r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))]; + } + me.i = i; me.j = j; + return r; + // For robust unpredictability, the function call below automatically + // discards an initial batch of values. This is called RC4-drop[256]. + // See http://google.com/search?q=rsa+fluhrer+response&btnI + }; + } - function sub(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - if (tOfA === 'string') { - a = parseInt(a, 10); - } - if (tOfB === 'string') { - b = parseInt(b, 10); - } - return a - b; - } - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - a = a.slice(0); - a[0] -= b; - return a; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - b = b.slice(0); - b[0] = a - b[0]; - return b; - } - if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { - var i = 0; - var lenA = a.length; - var lenB = b.length; - var retArr = []; - while (i < lenA || i < lenB) { - if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { - retArr[i] = a[i] - b[i]; - } else { - retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; +// +// copy() +// Copies internal state of ARC4 to or from a plain object. +// + function copy(f, t) { + t.i = f.i; + t.j = f.j; + t.S = f.S.slice(); + return t; + } + +// +// flatten() +// Converts an object tree to nested arrays of strings. +// + function flatten(obj, depth) { + var result = [], typ = (typeof obj), prop; + if (depth && typ == 'object') { + for (prop in obj) { + try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} } - i += 1; - } - return retArr; } - return 0; - } + return (result.length ? result : typ == 'string' ? obj : obj + '\0'); + } - function mul(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - var arr; - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - return a * b; - } +// +// mixkey() +// Mixes a string seed into a key that is an array of integers, and +// returns a shortened string seed that is equivalent to the result key. +// + function mixkey(seed, key) { + var stringseed = seed + '', smear, j = 0; + while (j < stringseed.length) { + key[mask & j] = + mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++)); + } + return tostring(key); + } - var i; - var len; - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - len = a.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a[i] * b; - } - return arr; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - len = b.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a * b[i]; - } - return arr; +// +// autoseed() +// Returns an object for autoseeding, using window.crypto and Node crypto +// module if available. +// + function autoseed() { + try { + if (nodecrypto) { return tostring(nodecrypto.randomBytes(width)); } + var out = new Uint8Array(width); + (global.crypto || global.msCrypto).getRandomValues(out); + return tostring(out); + } catch (e) { + var browser = global.navigator, + plugins = browser && browser.plugins; + return [+new Date(), global, plugins, global.screen, tostring(pool)]; } - return 0; - } + } - function div(a, b) { - var tOfA = typeof a; - var tOfB = typeof b; - var arr; - if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { - return a / b; - } - var i; - var len; - if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { - len = a.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a[i] / b; - } - return arr; - } - if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { - len = b.length; - arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = a / b[i]; - } - return arr; - } - return 0; +// +// tostring() +// Converts an array of charcodes to a string +// + function tostring(a) { + return String.fromCharCode.apply(0, a); + } + +// +// When seedrandom.js is loaded, we immediately mix a few bits +// from the built-in RNG into the entropy pool. Because we do +// not want to interfere with deterministic PRNG state later, +// seedrandom will not call math.random on its own again after +// initialization. +// + mixkey(math.random(), pool); + +// +// Nodejs and AMD support: export the implementation as a module using +// either convention. +// + +// End anonymous scope, and pass initial values. +}; + +function initialize$2(BMMath) { + seedRandom([], BMMath); +} + +var propTypes = { + SHAPE: 'shape', +}; + +/* eslint-disable camelcase */ + +const ExpressionManager = (function () { + 'use strict'; + + var ob = {}; + var Math = BMMath; + var window = null; + var document = null; + var XMLHttpRequest = null; + var fetch = null; + var frames = null; + initialize$2(BMMath); + + function $bm_isInstanceOfArray(arr) { + return arr.constructor === Array || arr.constructor === Float32Array; + } + + function isNumerable(tOfV, v) { + return tOfV === 'number' || tOfV === 'boolean' || tOfV === 'string' || v instanceof Number; + } + + function $bm_neg(a) { + var tOfA = typeof a; + if (tOfA === 'number' || tOfA === 'boolean' || a instanceof Number) { + return -a; + } + if ($bm_isInstanceOfArray(a)) { + var i; + var lenA = a.length; + var retArr = []; + for (i = 0; i < lenA; i += 1) { + retArr[i] = -a[i]; } - function mod(a, b) { - if (typeof a === 'string') { - a = parseInt(a, 10); - } - if (typeof b === 'string') { - b = parseInt(b, 10); + return retArr; + } + if (a.propType) { + return a.v; + } + return -a; + } + + var easeInBez = BezierFactory.getBezierEasing(0.333, 0, 0.833, 0.833, 'easeIn').get; + var easeOutBez = BezierFactory.getBezierEasing(0.167, 0.167, 0.667, 1, 'easeOut').get; + var easeInOutBez = BezierFactory.getBezierEasing(0.33, 0, 0.667, 1, 'easeInOut').get; + + function sum(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + if (tOfA === 'string' || tOfB === 'string') { + return a + b; + } + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + return a + b; + } + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + a = a.slice(0); + a[0] += b; + return a; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + b = b.slice(0); + b[0] = a + b[0]; + return b; + } + if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { + var i = 0; + var lenA = a.length; + var lenB = b.length; + var retArr = []; + while (i < lenA || i < lenB) { + if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { + retArr[i] = a[i] + b[i]; + } else { + retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; } - return a % b; + i += 1; } - var $bm_sum = sum; - var $bm_sub = sub; - var $bm_mul = mul; - var $bm_div = div; - var $bm_mod = mod; + return retArr; + } + return 0; + } + var add = sum; - function clamp(num, min, max) { - if (min > max) { - var mm = max; - max = min; - min = mm; + function sub(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + if (tOfA === 'string') { + a = parseInt(a, 10); + } + if (tOfB === 'string') { + b = parseInt(b, 10); + } + return a - b; + } + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + a = a.slice(0); + a[0] -= b; + return a; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + b = b.slice(0); + b[0] = a - b[0]; + return b; + } + if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) { + var i = 0; + var lenA = a.length; + var lenB = b.length; + var retArr = []; + while (i < lenA || i < lenB) { + if ((typeof a[i] === 'number' || a[i] instanceof Number) && (typeof b[i] === 'number' || b[i] instanceof Number)) { + retArr[i] = a[i] - b[i]; + } else { + retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]; } - return Math.min(Math.max(num, min), max); + i += 1; } + return retArr; + } + return 0; + } + + function mul(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + var arr; + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + return a * b; + } - function radiansToDegrees(val) { - return val / degToRads; + var i; + var len; + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + len = a.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a[i] * b; } - var radians_to_degrees = radiansToDegrees; - - function degreesToRadians(val) { - return val * degToRads; + return arr; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + len = b.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a * b[i]; } - var degrees_to_radians = radiansToDegrees; - - var helperLengthArray = [0, 0, 0, 0, 0, 0]; - - function length(arr1, arr2) { - if (typeof arr1 === 'number' || arr1 instanceof Number) { - arr2 = arr2 || 0; - return Math.abs(arr1 - arr2); - } - if (!arr2) { - arr2 = helperLengthArray; - } - var i; - var len = Math.min(arr1.length, arr2.length); - var addedLength = 0; - for (i = 0; i < len; i += 1) { - addedLength += Math.pow(arr2[i] - arr1[i], 2); - } - return Math.sqrt(addedLength); + return arr; + } + return 0; + } + + function div(a, b) { + var tOfA = typeof a; + var tOfB = typeof b; + var arr; + if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) { + return a / b; + } + var i; + var len; + if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) { + len = a.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a[i] / b; } - - function normalize(vec) { - return div(vec, length(vec)); + return arr; + } + if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) { + len = b.length; + arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = a / b[i]; } + return arr; + } + return 0; + } + function mod(a, b) { + if (typeof a === 'string') { + a = parseInt(a, 10); + } + if (typeof b === 'string') { + b = parseInt(b, 10); + } + return a % b; + } + var $bm_sum = sum; + var $bm_sub = sub; + var $bm_mul = mul; + var $bm_div = div; + var $bm_mod = mod; + + function clamp(num, min, max) { + if (min > max) { + var mm = max; + max = min; + min = mm; + } + return Math.min(Math.max(num, min), max); + } - function rgbToHsl(val) { - var r = val[0]; var g = val[1]; var b = val[2]; - var max = Math.max(r, g, b); - var min = Math.min(r, g, b); - var h; - var s; - var l = (max + min) / 2; + function radiansToDegrees(val) { + return val / degToRads; + } + var radians_to_degrees = radiansToDegrees; - if (max === min) { - h = 0; // achromatic - s = 0; // achromatic - } else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - default: break; - } - h /= 6; - } + function degreesToRadians(val) { + return val * degToRads; + } + var degrees_to_radians = radiansToDegrees; - return [h, s, l, val[3]]; - } + var helperLengthArray = [0, 0, 0, 0, 0, 0]; - function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; + function length(arr1, arr2) { + if (typeof arr1 === 'number' || arr1 instanceof Number) { + arr2 = arr2 || 0; + return Math.abs(arr1 - arr2); + } + if (!arr2) { + arr2 = helperLengthArray; + } + var i; + var len = Math.min(arr1.length, arr2.length); + var addedLength = 0; + for (i = 0; i < len; i += 1) { + addedLength += Math.pow(arr2[i] - arr1[i], 2); + } + return Math.sqrt(addedLength); + } + + function normalize(vec) { + return div(vec, length(vec)); + } + + function rgbToHsl(val) { + var r = val[0]; var g = val[1]; var b = val[2]; + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var h; + var s; + var l = (max + min) / 2; + + if (max === min) { + h = 0; // achromatic + s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + default: break; } + h /= 6; + } - function hslToRgb(val) { - var h = val[0]; - var s = val[1]; - var l = val[2]; - - var r; - var g; - var b; + return [h, s, l, val[3]]; + } + + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + function hslToRgb(val) { + var h = val[0]; + var s = val[1]; + var l = val[2]; + + var r; + var g; + var b; + + if (s === 0) { + r = l; // achromatic + b = l; // achromatic + g = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } - if (s === 0) { - r = l; // achromatic - b = l; // achromatic - g = l; // achromatic - } else { - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } + return [r, g, b, val[3]]; + } - return [r, g, b, val[3]]; + function linear(t, tMin, tMax, value1, value2) { + if (value1 === undefined || value2 === undefined) { + value1 = tMin; + value2 = tMax; + tMin = 0; + tMax = 1; + } + if (tMax < tMin) { + var _tMin = tMax; + tMax = tMin; + tMin = _tMin; + } + if (t <= tMin) { + return value1; + } if (t >= tMax) { + return value2; + } + var perc = tMax === tMin ? 0 : (t - tMin) / (tMax - tMin); + if (!value1.length) { + return value1 + (value2 - value1) * perc; + } + var i; + var len = value1.length; + var arr = createTypedArray('float32', len); + for (i = 0; i < len; i += 1) { + arr[i] = value1[i] + (value2[i] - value1[i]) * perc; + } + return arr; + } + function random(min, max) { + if (max === undefined) { + if (min === undefined) { + min = 0; + max = 1; + } else { + max = min; + min = undefined; } - - function linear(t, tMin, tMax, value1, value2) { - if (value1 === undefined || value2 === undefined) { - value1 = tMin; - value2 = tMax; - tMin = 0; - tMax = 1; - } - if (tMax < tMin) { - var _tMin = tMax; - tMax = tMin; - tMin = _tMin; - } - if (t <= tMin) { - return value1; - } if (t >= tMax) { - return value2; - } - var perc = tMax === tMin ? 0 : (t - tMin) / (tMax - tMin); - if (!value1.length) { - return value1 + (value2 - value1) * perc; - } - var i; - var len = value1.length; - var arr = createTypedArray('float32', len); - for (i = 0; i < len; i += 1) { - arr[i] = value1[i] + (value2[i] - value1[i]) * perc; - } - return arr; + } + if (max.length) { + var i; + var len = max.length; + if (!min) { + min = createTypedArray('float32', len); } - function random(min, max) { - if (max === undefined) { - if (min === undefined) { - min = 0; - max = 1; - } else { - max = min; - min = undefined; - } - } - if (max.length) { - var i; - var len = max.length; - if (!min) { - min = createTypedArray('float32', len); - } - var arr = createTypedArray('float32', len); - var rnd = BMMath.random(); - for (i = 0; i < len; i += 1) { - arr[i] = min[i] + rnd * (max[i] - min[i]); - } - return arr; - } - if (min === undefined) { - min = 0; + var arr = createTypedArray('float32', len); + var rnd = BMMath.random(); + for (i = 0; i < len; i += 1) { + arr[i] = min[i] + rnd * (max[i] - min[i]); + } + return arr; + } + if (min === undefined) { + min = 0; + } + var rndm = BMMath.random(); + return min + rndm * (max - min); + } + + function createPath(points, inTangents, outTangents, closed) { + var i; + var len = points.length; + var path = shapePool.newElement(); + path.setPathData(!!closed, len); + var arrPlaceholder = [0, 0]; + var inVertexPoint; + var outVertexPoint; + for (i = 0; i < len; i += 1) { + inVertexPoint = (inTangents && inTangents[i]) ? inTangents[i] : arrPlaceholder; + outVertexPoint = (outTangents && outTangents[i]) ? outTangents[i] : arrPlaceholder; + path.setTripleAt(points[i][0], points[i][1], outVertexPoint[0] + points[i][0], outVertexPoint[1] + points[i][1], inVertexPoint[0] + points[i][0], inVertexPoint[1] + points[i][1], i, true); + } + return path; + } + + function initiateExpression(elem, data, property) { + var val = data.x; + var needsVelocity = /velocity(?![\w\d])/.test(val); + var _needsRandom = val.indexOf('random') !== -1; + var elemType = elem.data.ty; + var transform; + var $bm_transform; + var content; + var effect; + var thisProperty = property; + thisProperty.valueAtTime = thisProperty.getValueAtTime; + Object.defineProperty(thisProperty, 'value', { + get: function () { + return thisProperty.v; + }, + }); + elem.comp.frameDuration = 1 / elem.comp.globalData.frameRate; + elem.comp.displayStartTime = 0; + var inPoint = elem.data.ip / elem.comp.globalData.frameRate; + var outPoint = elem.data.op / elem.comp.globalData.frameRate; + var width = elem.data.sw ? elem.data.sw : 0; + var height = elem.data.sh ? elem.data.sh : 0; + var name = elem.data.nm; + var loopIn; + var loop_in; + var loopOut; + var loop_out; + var smooth; + var toWorld; + var fromWorld; + var fromComp; + var toComp; + var fromCompToSurface; + var position; + var rotation; + var anchorPoint; + var scale; + var thisLayer; + var thisComp; + var mask; + var valueAtTime; + var velocityAtTime; + + var scoped_bm_rt; + // val = val.replace(/(\\?"|')((http)(s)?(:\/))?\/.*?(\\?"|')/g, "\"\""); // deter potential network calls + var expression_function = eval('[function _expression_function(){' + val + ';scoped_bm_rt=$bm_rt}]')[0]; // eslint-disable-line no-eval + var numKeys = property.kf ? data.k.length : 0; + + var active = !this.data || this.data.hd !== true; + + var wiggle = function wiggle(freq, amp) { + var iWiggle; + var j; + var lenWiggle = this.pv.length ? this.pv.length : 1; + var addedAmps = createTypedArray('float32', lenWiggle); + freq = 5; + var iterations = Math.floor(time * freq); + iWiggle = 0; + j = 0; + while (iWiggle < iterations) { + // var rnd = BMMath.random(); + for (j = 0; j < lenWiggle; j += 1) { + addedAmps[j] += -amp + amp * 2 * BMMath.random(); + // addedAmps[j] += -amp + amp*2*rnd; + } + iWiggle += 1; + } + // var rnd2 = BMMath.random(); + var periods = time * freq; + var perc = periods - Math.floor(periods); + var arr = createTypedArray('float32', lenWiggle); + if (lenWiggle > 1) { + for (j = 0; j < lenWiggle; j += 1) { + arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp * 2 * BMMath.random()) * perc; + // arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp*2*rnd)*perc; + // arr[i] = this.pv[i] + addedAmp + amp1*perc + amp2*(1-perc); } - var rndm = BMMath.random(); - return min + rndm * (max - min); + return arr; } + return this.pv + addedAmps[0] + (-amp + amp * 2 * BMMath.random()) * perc; + }.bind(this); - function createPath(points, inTangents, outTangents, closed) { - var i; - var len = points.length; - var path = shapePool.newElement(); - path.setPathData(!!closed, len); - var arrPlaceholder = [0, 0]; - var inVertexPoint; - var outVertexPoint; - for (i = 0; i < len; i += 1) { - inVertexPoint = (inTangents && inTangents[i]) ? inTangents[i] : arrPlaceholder; - outVertexPoint = (outTangents && outTangents[i]) ? outTangents[i] : arrPlaceholder; - path.setTripleAt(points[i][0], points[i][1], outVertexPoint[0] + points[i][0], outVertexPoint[1] + points[i][1], inVertexPoint[0] + points[i][0], inVertexPoint[1] + points[i][1], i, true); - } - return path; - } - - function initiateExpression(elem, data, property) { - var val = data.x; - var needsVelocity = /velocity(?![\w\d])/.test(val); - var _needsRandom = val.indexOf('random') !== -1; - var elemType = elem.data.ty; - var transform; - var $bm_transform; - var content; - var effect; - var thisProperty = property; - thisProperty.valueAtTime = thisProperty.getValueAtTime; - Object.defineProperty(thisProperty, 'value', { - get: function () { - return thisProperty.v; - }, - }); - elem.comp.frameDuration = 1 / elem.comp.globalData.frameRate; - elem.comp.displayStartTime = 0; - var inPoint = elem.data.ip / elem.comp.globalData.frameRate; - var outPoint = elem.data.op / elem.comp.globalData.frameRate; - var width = elem.data.sw ? elem.data.sw : 0; - var height = elem.data.sh ? elem.data.sh : 0; - var name = elem.data.nm; - var loopIn; - var loop_in; - var loopOut; - var loop_out; - var smooth; - var toWorld; - var fromWorld; - var fromComp; - var toComp; - var fromCompToSurface; - var position; - var rotation; - var anchorPoint; - var scale; - var thisLayer; - var thisComp; - var mask; - var valueAtTime; - var velocityAtTime; - - var scoped_bm_rt; - // val = val.replace(/(\\?"|')((http)(s)?(:\/))?\/.*?(\\?"|')/g, "\"\""); // deter potential network calls - var expression_function = eval('[function _expression_function(){' + val + ';scoped_bm_rt=$bm_rt}]')[0]; // eslint-disable-line no-eval - var numKeys = property.kf ? data.k.length : 0; - - var active = !this.data || this.data.hd !== true; - - var wiggle = function wiggle(freq, amp) { - var iWiggle; - var j; - var lenWiggle = this.pv.length ? this.pv.length : 1; - var addedAmps = createTypedArray('float32', lenWiggle); - freq = 5; - var iterations = Math.floor(time * freq); - iWiggle = 0; - j = 0; - while (iWiggle < iterations) { - // var rnd = BMMath.random(); - for (j = 0; j < lenWiggle; j += 1) { - addedAmps[j] += -amp + amp * 2 * BMMath.random(); - // addedAmps[j] += -amp + amp*2*rnd; - } - iWiggle += 1; - } - // var rnd2 = BMMath.random(); - var periods = time * freq; - var perc = periods - Math.floor(periods); - var arr = createTypedArray('float32', lenWiggle); - if (lenWiggle > 1) { - for (j = 0; j < lenWiggle; j += 1) { - arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp * 2 * BMMath.random()) * perc; - // arr[j] = this.pv[j] + addedAmps[j] + (-amp + amp*2*rnd)*perc; - // arr[i] = this.pv[i] + addedAmp + amp1*perc + amp2*(1-perc); - } - return arr; - } - return this.pv + addedAmps[0] + (-amp + amp * 2 * BMMath.random()) * perc; - }.bind(this); - - if (thisProperty.loopIn) { - loopIn = thisProperty.loopIn.bind(thisProperty); - loop_in = loopIn; - } + if (thisProperty.loopIn) { + loopIn = thisProperty.loopIn.bind(thisProperty); + loop_in = loopIn; + } - if (thisProperty.loopOut) { - loopOut = thisProperty.loopOut.bind(thisProperty); - loop_out = loopOut; - } + if (thisProperty.loopOut) { + loopOut = thisProperty.loopOut.bind(thisProperty); + loop_out = loopOut; + } - if (thisProperty.smooth) { - smooth = thisProperty.smooth.bind(thisProperty); - } + if (thisProperty.smooth) { + smooth = thisProperty.smooth.bind(thisProperty); + } - function loopInDuration(type, duration) { - return loopIn(type, duration, true); - } + function loopInDuration(type, duration) { + return loopIn(type, duration, true); + } - function loopOutDuration(type, duration) { - return loopOut(type, duration, true); - } + function loopOutDuration(type, duration) { + return loopOut(type, duration, true); + } - if (this.getValueAtTime) { - valueAtTime = this.getValueAtTime.bind(this); - } + if (this.getValueAtTime) { + valueAtTime = this.getValueAtTime.bind(this); + } - if (this.getVelocityAtTime) { - velocityAtTime = this.getVelocityAtTime.bind(this); - } + if (this.getVelocityAtTime) { + velocityAtTime = this.getVelocityAtTime.bind(this); + } - var comp = elem.comp.globalData.projectInterface.bind(elem.comp.globalData.projectInterface); + var comp = elem.comp.globalData.projectInterface.bind(elem.comp.globalData.projectInterface); - function lookAt(elem1, elem2) { - var fVec = [elem2[0] - elem1[0], elem2[1] - elem1[1], elem2[2] - elem1[2]]; - var pitch = Math.atan2(fVec[0], Math.sqrt(fVec[1] * fVec[1] + fVec[2] * fVec[2])) / degToRads; - var yaw = -Math.atan2(fVec[1], fVec[2]) / degToRads; - return [yaw, pitch, 0]; - } + function lookAt(elem1, elem2) { + var fVec = [elem2[0] - elem1[0], elem2[1] - elem1[1], elem2[2] - elem1[2]]; + var pitch = Math.atan2(fVec[0], Math.sqrt(fVec[1] * fVec[1] + fVec[2] * fVec[2])) / degToRads; + var yaw = -Math.atan2(fVec[1], fVec[2]) / degToRads; + return [yaw, pitch, 0]; + } - function easeOut(t, tMin, tMax, val1, val2) { - return applyEase(easeOutBez, t, tMin, tMax, val1, val2); - } + function easeOut(t, tMin, tMax, val1, val2) { + return applyEase(easeOutBez, t, tMin, tMax, val1, val2); + } - function easeIn(t, tMin, tMax, val1, val2) { - return applyEase(easeInBez, t, tMin, tMax, val1, val2); - } + function easeIn(t, tMin, tMax, val1, val2) { + return applyEase(easeInBez, t, tMin, tMax, val1, val2); + } - function ease(t, tMin, tMax, val1, val2) { - return applyEase(easeInOutBez, t, tMin, tMax, val1, val2); - } + function ease(t, tMin, tMax, val1, val2) { + return applyEase(easeInOutBez, t, tMin, tMax, val1, val2); + } - function applyEase(fn, t, tMin, tMax, val1, val2) { - if (val1 === undefined) { - val1 = tMin; - val2 = tMax; - } else { - t = (t - tMin) / (tMax - tMin); - } - if (t > 1) { - t = 1; - } else if (t < 0) { - t = 0; - } - var mult = fn(t); - if ($bm_isInstanceOfArray(val1)) { - var iKey; - var lenKey = val1.length; - var arr = createTypedArray('float32', lenKey); - for (iKey = 0; iKey < lenKey; iKey += 1) { - arr[iKey] = (val2[iKey] - val1[iKey]) * mult + val1[iKey]; - } - return arr; - } - return (val2 - val1) * mult + val1; + function applyEase(fn, t, tMin, tMax, val1, val2) { + if (val1 === undefined) { + val1 = tMin; + val2 = tMax; + } else { + t = (t - tMin) / (tMax - tMin); + } + if (t > 1) { + t = 1; + } else if (t < 0) { + t = 0; + } + var mult = fn(t); + if ($bm_isInstanceOfArray(val1)) { + var iKey; + var lenKey = val1.length; + var arr = createTypedArray('float32', lenKey); + for (iKey = 0; iKey < lenKey; iKey += 1) { + arr[iKey] = (val2[iKey] - val1[iKey]) * mult + val1[iKey]; } + return arr; + } + return (val2 - val1) * mult + val1; + } - function nearestKey(time) { - var iKey; - var lenKey = data.k.length; - var index; - var keyTime; - if (!data.k.length || typeof (data.k[0]) === 'number') { - index = 0; - keyTime = 0; - } else { - index = -1; - time *= elem.comp.globalData.frameRate; - if (time < data.k[0].t) { - index = 1; - keyTime = data.k[0].t; - } else { - for (iKey = 0; iKey < lenKey - 1; iKey += 1) { - if (time === data.k[iKey].t) { - index = iKey + 1; - keyTime = data.k[iKey].t; - break; - } else if (time > data.k[iKey].t && time < data.k[iKey + 1].t) { - if (time - data.k[iKey].t > data.k[iKey + 1].t - time) { - index = iKey + 2; - keyTime = data.k[iKey + 1].t; - } else { - index = iKey + 1; - keyTime = data.k[iKey].t; - } - break; - } - } - if (index === -1) { + function nearestKey(time) { + var iKey; + var lenKey = data.k.length; + var index; + var keyTime; + if (!data.k.length || typeof (data.k[0]) === 'number') { + index = 0; + keyTime = 0; + } else { + index = -1; + time *= elem.comp.globalData.frameRate; + if (time < data.k[0].t) { + index = 1; + keyTime = data.k[0].t; + } else { + for (iKey = 0; iKey < lenKey - 1; iKey += 1) { + if (time === data.k[iKey].t) { + index = iKey + 1; + keyTime = data.k[iKey].t; + break; + } else if (time > data.k[iKey].t && time < data.k[iKey + 1].t) { + if (time - data.k[iKey].t > data.k[iKey + 1].t - time) { + index = iKey + 2; + keyTime = data.k[iKey + 1].t; + } else { index = iKey + 1; keyTime = data.k[iKey].t; } + break; } } - var obKey = {}; - obKey.index = index; - obKey.time = keyTime / elem.comp.globalData.frameRate; - return obKey; - } - - function key(ind) { - var obKey; - var iKey; - var lenKey; - if (!data.k.length || typeof (data.k[0]) === 'number') { - throw new Error('The property has no keyframe at index ' + ind); - } - ind -= 1; - obKey = { - time: data.k[ind].t / elem.comp.globalData.frameRate, - value: [], - }; - var arr = Object.prototype.hasOwnProperty.call(data.k[ind], 's') ? data.k[ind].s : data.k[ind - 1].e; - - lenKey = arr.length; - for (iKey = 0; iKey < lenKey; iKey += 1) { - obKey[iKey] = arr[iKey]; - obKey.value[iKey] = arr[iKey]; - } - return obKey; - } - - function framesToTime(fr, fps) { - if (!fps) { - fps = elem.comp.globalData.frameRate; - } - return fr / fps; - } - - function timeToFrames(t, fps) { - if (!t && t !== 0) { - t = time; - } - if (!fps) { - fps = elem.comp.globalData.frameRate; - } - return t * fps; - } - - function seedRandom(seed) { - BMMath.seedrandom(randSeed + seed); - } - - function sourceRectAtTime() { - return elem.sourceRectAtTime(); - } - - function substring(init, end) { - if (typeof value === 'string') { - if (end === undefined) { - return value.substring(init); - } - return value.substring(init, end); + if (index === -1) { + index = iKey + 1; + keyTime = data.k[iKey].t; } - return ''; } + } + var obKey = {}; + obKey.index = index; + obKey.time = keyTime / elem.comp.globalData.frameRate; + return obKey; + } - function substr(init, end) { - if (typeof value === 'string') { - if (end === undefined) { - return value.substr(init); - } - return value.substr(init, end); - } - return ''; - } - - function posterizeTime(framesPerSecond) { - time = framesPerSecond === 0 ? 0 : Math.floor(time * framesPerSecond) / framesPerSecond; - value = valueAtTime(time); - } - - var time; - var velocity; - var value; - var text; - var textIndex; - var textTotal; - var selectorValue; - var index = elem.data.ind; - var hasParent = !!(elem.hierarchy && elem.hierarchy.length); - var parent; - var randSeed = Math.floor(Math.random() * 1000000); - var globalData = elem.globalData; - function executeExpression(_value) { - // globalData.pushExpression(); - value = _value; - if (this.frameExpressionId === elem.globalData.frameId && this.propType !== 'textSelector') { - return value; - } - if (this.propType === 'textSelector') { - textIndex = this.textIndex; - textTotal = this.textTotal; - selectorValue = this.selectorValue; - } - if (!thisLayer) { - text = elem.layerInterface.text; - thisLayer = elem.layerInterface; - thisComp = elem.comp.compInterface; - toWorld = thisLayer.toWorld.bind(thisLayer); - fromWorld = thisLayer.fromWorld.bind(thisLayer); - fromComp = thisLayer.fromComp.bind(thisLayer); - toComp = thisLayer.toComp.bind(thisLayer); - mask = thisLayer.mask ? thisLayer.mask.bind(thisLayer) : null; - fromCompToSurface = fromComp; - } - if (!transform) { - transform = elem.layerInterface('ADBE Transform Group'); - $bm_transform = transform; - if (transform) { - anchorPoint = transform.anchorPoint; - /* position = transform.position; - rotation = transform.rotation; - scale = transform.scale; */ - } - } + function key(ind) { + var obKey; + var iKey; + var lenKey; + if (!data.k.length || typeof (data.k[0]) === 'number') { + throw new Error('The property has no keyframe at index ' + ind); + } + ind -= 1; + obKey = { + time: data.k[ind].t / elem.comp.globalData.frameRate, + value: [], + }; + var arr = Object.prototype.hasOwnProperty.call(data.k[ind], 's') ? data.k[ind].s : data.k[ind - 1].e; - if (elemType === 4 && !content) { - content = thisLayer('ADBE Root Vectors Group'); - } - if (!effect) { - effect = thisLayer(4); - } - hasParent = !!(elem.hierarchy && elem.hierarchy.length); - if (hasParent && !parent) { - parent = elem.hierarchy[0].layerInterface; - } - time = this.comp.renderedFrame / this.comp.globalData.frameRate; - if (_needsRandom) { - seedRandom(randSeed + time); - } - if (needsVelocity) { - velocity = velocityAtTime(time); - } - expression_function(); - this.frameExpressionId = elem.globalData.frameId; - - // TODO: Check if it's possible to return on ShapeInterface the .v value - // Changed this to a ternary operation because Rollup failed compiling it correctly - scoped_bm_rt = scoped_bm_rt.propType === propTypes.SHAPE - ? scoped_bm_rt.v - : scoped_bm_rt; - return scoped_bm_rt; - } - // Bundlers will see these as dead code and unless we reference them - executeExpression.__preventDeadCodeRemoval = [$bm_transform, anchorPoint, time, velocity, inPoint, outPoint, width, height, name, loop_in, loop_out, smooth, toComp, fromCompToSurface, toWorld, fromWorld, mask, position, rotation, scale, thisComp, numKeys, active, wiggle, loopInDuration, loopOutDuration, comp, lookAt, easeOut, easeIn, ease, nearestKey, key, text, textIndex, textTotal, selectorValue, framesToTime, timeToFrames, sourceRectAtTime, substring, substr, posterizeTime, index, globalData]; - return executeExpression; - } - - ob.initiateExpression = initiateExpression; - ob.__preventDeadCodeRemoval = [window, document, XMLHttpRequest, fetch, frames, $bm_neg, add, $bm_sum, $bm_sub, $bm_mul, $bm_div, $bm_mod, clamp, radians_to_degrees, degreesToRadians, degrees_to_radians, normalize, rgbToHsl, hslToRgb, linear, random, createPath]; - return ob; - }()); - - const expressionHelpers = (function () { - function searchExpressions(elem, data, prop) { - if (data.x) { - prop.k = true; - prop.x = true; - prop.initiateExpression = ExpressionManager.initiateExpression; - prop.effectsSequence.push(prop.initiateExpression(elem, data, prop).bind(prop)); - } - } - - function getValueAtTime(frameNum) { - frameNum *= this.elem.globalData.frameRate; - frameNum -= this.offsetTime; - if (frameNum !== this._cachingAtTime.lastFrame) { - this._cachingAtTime.lastIndex = this._cachingAtTime.lastFrame < frameNum ? this._cachingAtTime.lastIndex : 0; - this._cachingAtTime.value = this.interpolateValue(frameNum, this._cachingAtTime); - this._cachingAtTime.lastFrame = frameNum; - } - return this._cachingAtTime.value; - } - - function getSpeedAtTime(frameNum) { - var delta = -0.01; - var v1 = this.getValueAtTime(frameNum); - var v2 = this.getValueAtTime(frameNum + delta); - var speed = 0; - if (v1.length) { - var i; - for (i = 0; i < v1.length; i += 1) { - speed += Math.pow(v2[i] - v1[i], 2); - } - speed = Math.sqrt(speed) * 100; - } else { - speed = 0; - } - return speed; - } - - function getVelocityAtTime(frameNum) { - if (this.vel !== undefined) { - return this.vel; - } - var delta = -0.001; - // frameNum += this.elem.data.st; - var v1 = this.getValueAtTime(frameNum); - var v2 = this.getValueAtTime(frameNum + delta); - var velocity; - if (v1.length) { - velocity = createTypedArray('float32', v1.length); - var i; - for (i = 0; i < v1.length; i += 1) { - // removing frameRate - // if needed, don't add it here - // velocity[i] = this.elem.globalData.frameRate*((v2[i] - v1[i])/delta); - velocity[i] = (v2[i] - v1[i]) / delta; - } - } else { - velocity = (v2 - v1) / delta; - } - return velocity; + lenKey = arr.length; + for (iKey = 0; iKey < lenKey; iKey += 1) { + obKey[iKey] = arr[iKey]; + obKey.value[iKey] = arr[iKey]; } + return obKey; + } - function getStaticValueAtTime() { - return this.pv; + function framesToTime(fr, fps) { + if (!fps) { + fps = elem.comp.globalData.frameRate; } + return fr / fps; + } - function setGroupProperty(propertyGroup) { - this.propertyGroup = propertyGroup; + function timeToFrames(t, fps) { + if (!t && t !== 0) { + t = time; } + if (!fps) { + fps = elem.comp.globalData.frameRate; + } + return t * fps; + } - return { - searchExpressions: searchExpressions, - getSpeedAtTime: getSpeedAtTime, - getVelocityAtTime: getVelocityAtTime, - getValueAtTime: getValueAtTime, - getStaticValueAtTime: getStaticValueAtTime, - setGroupProperty: setGroupProperty, - }; - }()); - - function addPropertyDecorator() { - function loopOut(type, duration, durationFlag) { - if (!this.k || !this.keyframes) { - return this.pv; - } - type = type ? type.toLowerCase() : ''; - var currentFrame = this.comp.renderedFrame; - var keyframes = this.keyframes; - var lastKeyFrame = keyframes[keyframes.length - 1].t; - if (currentFrame <= lastKeyFrame) { - return this.pv; - } - var cycleDuration; - var firstKeyFrame; - if (!durationFlag) { - if (!duration || duration > keyframes.length - 1) { - duration = keyframes.length - 1; - } - firstKeyFrame = keyframes[keyframes.length - 1 - duration].t; - cycleDuration = lastKeyFrame - firstKeyFrame; - } else { - if (!duration) { - cycleDuration = Math.max(0, lastKeyFrame - this.elem.data.ip); - } else { - cycleDuration = Math.abs(lastKeyFrame - this.elem.comp.globalData.frameRate * duration); - } - firstKeyFrame = lastKeyFrame - cycleDuration; - } - var i; - var len; - var ret; - if (type === 'pingpong') { - var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); - if (iterations % 2 !== 0) { - return this.getValueAtTime(((cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line - } - } else if (type === 'offset') { - var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); - var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); - var current = this.getValueAtTime(((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame) / this.comp.globalData.frameRate, 0); // eslint-disable-line - var repeats = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); - if (this.pv.length) { - ret = new Array(initV.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = (endV[i] - initV[i]) * repeats + current[i]; - } - return ret; - } - return (endV - initV) * repeats + current; - } else if (type === 'continue') { - var lastValue = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); - var nextLastValue = this.getValueAtTime((lastKeyFrame - 0.001) / this.comp.globalData.frameRate, 0); - if (this.pv.length) { - ret = new Array(lastValue.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = lastValue[i] + (lastValue[i] - nextLastValue[i]) * ((currentFrame - lastKeyFrame) / this.comp.globalData.frameRate) / 0.0005; // eslint-disable-line - } - return ret; - } - return lastValue + (lastValue - nextLastValue) * (((currentFrame - lastKeyFrame)) / 0.001); - } - return this.getValueAtTime((((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line + function seedRandom(seed) { + BMMath.seedrandom(randSeed + seed); + } - } + function sourceRectAtTime() { + return elem.sourceRectAtTime(); + } - function loopIn(type, duration, durationFlag) { - if (!this.k) { - return this.pv; - } - type = type ? type.toLowerCase() : ''; - var currentFrame = this.comp.renderedFrame; - var keyframes = this.keyframes; - var firstKeyFrame = keyframes[0].t; - if (currentFrame >= firstKeyFrame) { - return this.pv; - } - var cycleDuration; - var lastKeyFrame; - if (!durationFlag) { - if (!duration || duration > keyframes.length - 1) { - duration = keyframes.length - 1; - } - lastKeyFrame = keyframes[duration].t; - cycleDuration = lastKeyFrame - firstKeyFrame; - } else { - if (!duration) { - cycleDuration = Math.max(0, this.elem.data.op - firstKeyFrame); - } else { - cycleDuration = Math.abs(this.elem.comp.globalData.frameRate * duration); - } - lastKeyFrame = firstKeyFrame + cycleDuration; - } - var i; - var len; - var ret; - if (type === 'pingpong') { - var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration); - if (iterations % 2 === 0) { - return this.getValueAtTime((((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line - } - } else if (type === 'offset') { - var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); - var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); - var current = this.getValueAtTime((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate, 0); - var repeats = Math.floor((firstKeyFrame - currentFrame) / cycleDuration) + 1; - if (this.pv.length) { - ret = new Array(initV.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = current[i] - (endV[i] - initV[i]) * repeats; - } - return ret; - } - return current - (endV - initV) * repeats; - } else if (type === 'continue') { - var firstValue = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); - var nextFirstValue = this.getValueAtTime((firstKeyFrame + 0.001) / this.comp.globalData.frameRate, 0); - if (this.pv.length) { - ret = new Array(firstValue.length); - len = ret.length; - for (i = 0; i < len; i += 1) { - ret[i] = firstValue[i] + ((firstValue[i] - nextFirstValue[i]) * (firstKeyFrame - currentFrame)) / 0.001; - } - return ret; - } - return firstValue + ((firstValue - nextFirstValue) * (firstKeyFrame - currentFrame)) / 0.001; + function substring(init, end) { + if (typeof value === 'string') { + if (end === undefined) { + return value.substring(init); } - return this.getValueAtTime(((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame))) / this.comp.globalData.frameRate, 0); // eslint-disable-line - + return value.substring(init, end); } + return ''; + } - function smooth(width, samples) { - if (!this.k) { - return this.pv; - } - width = (width || 0.4) * 0.5; - samples = Math.floor(samples || 5); - if (samples <= 1) { - return this.pv; - } - var currentTime = this.comp.renderedFrame / this.comp.globalData.frameRate; - var initFrame = currentTime - width; - var endFrame = currentTime + width; - var sampleFrequency = samples > 1 ? (endFrame - initFrame) / (samples - 1) : 1; - var i = 0; - var j = 0; - var value; - if (this.pv.length) { - value = createTypedArray('float32', this.pv.length); - } else { - value = 0; - } - var sampleValue; - while (i < samples) { - sampleValue = this.getValueAtTime(initFrame + i * sampleFrequency); - if (this.pv.length) { - for (j = 0; j < this.pv.length; j += 1) { - value[j] += sampleValue[j]; - } - } else { - value += sampleValue; - } - i += 1; - } - if (this.pv.length) { - for (j = 0; j < this.pv.length; j += 1) { - value[j] /= samples; - } - } else { - value /= samples; + function substr(init, end) { + if (typeof value === 'string') { + if (end === undefined) { + return value.substr(init); } + return value.substr(init, end); + } + return ''; + } + + function posterizeTime(framesPerSecond) { + time = framesPerSecond === 0 ? 0 : Math.floor(time * framesPerSecond) / framesPerSecond; + value = valueAtTime(time); + } + + var time; + var velocity; + var value; + var text; + var textIndex; + var textTotal; + var selectorValue; + var index = elem.data.ind; + var hasParent = !!(elem.hierarchy && elem.hierarchy.length); + var parent; + var randSeed = Math.floor(Math.random() * 1000000); + var globalData = elem.globalData; + function executeExpression(_value) { + // globalData.pushExpression(); + value = _value; + if (this.frameExpressionId === elem.globalData.frameId && this.propType !== 'textSelector') { return value; } + if (this.propType === 'textSelector') { + textIndex = this.textIndex; + textTotal = this.textTotal; + selectorValue = this.selectorValue; + } + if (!thisLayer) { + text = elem.layerInterface.text; + thisLayer = elem.layerInterface; + thisComp = elem.comp.compInterface; + toWorld = thisLayer.toWorld.bind(thisLayer); + fromWorld = thisLayer.fromWorld.bind(thisLayer); + fromComp = thisLayer.fromComp.bind(thisLayer); + toComp = thisLayer.toComp.bind(thisLayer); + mask = thisLayer.mask ? thisLayer.mask.bind(thisLayer) : null; + fromCompToSurface = fromComp; + } + if (!transform) { + transform = elem.layerInterface('ADBE Transform Group'); + $bm_transform = transform; + if (transform) { + anchorPoint = transform.anchorPoint; + /* position = transform.position; + rotation = transform.rotation; + scale = transform.scale; */ + } + } + + if (elemType === 4 && !content) { + content = thisLayer('ADBE Root Vectors Group'); + } + if (!effect) { + effect = thisLayer(4); + } + hasParent = !!(elem.hierarchy && elem.hierarchy.length); + if (hasParent && !parent) { + parent = elem.hierarchy[0].layerInterface; + } + time = this.comp.renderedFrame / this.comp.globalData.frameRate; + if (_needsRandom) { + seedRandom(randSeed + time); + } + if (needsVelocity) { + velocity = velocityAtTime(time); + } + expression_function(); + this.frameExpressionId = elem.globalData.frameId; + + // TODO: Check if it's possible to return on ShapeInterface the .v value + // Changed this to a ternary operation because Rollup failed compiling it correctly + scoped_bm_rt = scoped_bm_rt.propType === propTypes.SHAPE + ? scoped_bm_rt.v + : scoped_bm_rt; + return scoped_bm_rt; + } + // Bundlers will see these as dead code and unless we reference them + executeExpression.__preventDeadCodeRemoval = [$bm_transform, anchorPoint, time, velocity, inPoint, outPoint, width, height, name, loop_in, loop_out, smooth, toComp, fromCompToSurface, toWorld, fromWorld, mask, position, rotation, scale, thisComp, numKeys, active, wiggle, loopInDuration, loopOutDuration, comp, lookAt, easeOut, easeIn, ease, nearestKey, key, text, textIndex, textTotal, selectorValue, framesToTime, timeToFrames, sourceRectAtTime, substring, substr, posterizeTime, index, globalData]; + return executeExpression; + } + + ob.initiateExpression = initiateExpression; + ob.__preventDeadCodeRemoval = [window, document, XMLHttpRequest, fetch, frames, $bm_neg, add, $bm_sum, $bm_sub, $bm_mul, $bm_div, $bm_mod, clamp, radians_to_degrees, degreesToRadians, degrees_to_radians, normalize, rgbToHsl, hslToRgb, linear, random, createPath]; + return ob; +}()); + +const expressionHelpers = (function () { + function searchExpressions(elem, data, prop) { + if (data.x) { + prop.k = true; + prop.x = true; + prop.initiateExpression = ExpressionManager.initiateExpression; + prop.effectsSequence.push(prop.initiateExpression(elem, data, prop).bind(prop)); + } + } + + function getValueAtTime(frameNum) { + frameNum *= this.elem.globalData.frameRate; + frameNum -= this.offsetTime; + if (frameNum !== this._cachingAtTime.lastFrame) { + this._cachingAtTime.lastIndex = this._cachingAtTime.lastFrame < frameNum ? this._cachingAtTime.lastIndex : 0; + this._cachingAtTime.value = this.interpolateValue(frameNum, this._cachingAtTime); + this._cachingAtTime.lastFrame = frameNum; + } + return this._cachingAtTime.value; + } + + function getSpeedAtTime(frameNum) { + var delta = -0.01; + var v1 = this.getValueAtTime(frameNum); + var v2 = this.getValueAtTime(frameNum + delta); + var speed = 0; + if (v1.length) { + var i; + for (i = 0; i < v1.length; i += 1) { + speed += Math.pow(v2[i] - v1[i], 2); + } + speed = Math.sqrt(speed) * 100; + } else { + speed = 0; + } + return speed; + } - function getTransformValueAtTime(time) { - if (!this._transformCachingAtTime) { - this._transformCachingAtTime = { - v: new Matrix(), - }; - } - /// / - var matrix = this._transformCachingAtTime.v; - matrix.cloneFromProps(this.pre.props); - if (this.appliedTransformations < 1) { - var anchor = this.a.getValueAtTime(time); - matrix.translate( - -anchor[0] * this.a.mult, - -anchor[1] * this.a.mult, - anchor[2] * this.a.mult - ); - } - if (this.appliedTransformations < 2) { - var scale = this.s.getValueAtTime(time); - matrix.scale( - scale[0] * this.s.mult, - scale[1] * this.s.mult, - scale[2] * this.s.mult - ); + function getVelocityAtTime(frameNum) { + if (this.vel !== undefined) { + return this.vel; + } + var delta = -0.001; + // frameNum += this.elem.data.st; + var v1 = this.getValueAtTime(frameNum); + var v2 = this.getValueAtTime(frameNum + delta); + var velocity; + if (v1.length) { + velocity = createTypedArray('float32', v1.length); + var i; + for (i = 0; i < v1.length; i += 1) { + // removing frameRate + // if needed, don't add it here + // velocity[i] = this.elem.globalData.frameRate*((v2[i] - v1[i])/delta); + velocity[i] = (v2[i] - v1[i]) / delta; + } + } else { + velocity = (v2 - v1) / delta; + } + return velocity; + } + + function getStaticValueAtTime() { + return this.pv; + } + + function setGroupProperty(propertyGroup) { + this.propertyGroup = propertyGroup; + } + + return { + searchExpressions: searchExpressions, + getSpeedAtTime: getSpeedAtTime, + getVelocityAtTime: getVelocityAtTime, + getValueAtTime: getValueAtTime, + getStaticValueAtTime: getStaticValueAtTime, + setGroupProperty: setGroupProperty, + }; +}()); + +function addPropertyDecorator() { + function loopOut(type, duration, durationFlag) { + if (!this.k || !this.keyframes) { + return this.pv; + } + type = type ? type.toLowerCase() : ''; + var currentFrame = this.comp.renderedFrame; + var keyframes = this.keyframes; + var lastKeyFrame = keyframes[keyframes.length - 1].t; + if (currentFrame <= lastKeyFrame) { + return this.pv; + } + var cycleDuration; + var firstKeyFrame; + if (!durationFlag) { + if (!duration || duration > keyframes.length - 1) { + duration = keyframes.length - 1; + } + firstKeyFrame = keyframes[keyframes.length - 1 - duration].t; + cycleDuration = lastKeyFrame - firstKeyFrame; + } else { + if (!duration) { + cycleDuration = Math.max(0, lastKeyFrame - this.elem.data.ip); + } else { + cycleDuration = Math.abs(lastKeyFrame - this.elem.comp.globalData.frameRate * duration); + } + firstKeyFrame = lastKeyFrame - cycleDuration; + } + var i; + var len; + var ret; + if (type === 'pingpong') { + var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); + if (iterations % 2 !== 0) { + return this.getValueAtTime(((cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line + } + } else if (type === 'offset') { + var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); + var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); + var current = this.getValueAtTime(((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame) / this.comp.globalData.frameRate, 0); // eslint-disable-line + var repeats = Math.floor((currentFrame - firstKeyFrame) / cycleDuration); + if (this.pv.length) { + ret = new Array(initV.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = (endV[i] - initV[i]) * repeats + current[i]; } - if (this.sk && this.appliedTransformations < 3) { - var skew = this.sk.getValueAtTime(time); - var skewAxis = this.sa.getValueAtTime(time); - matrix.skewFromAxis(-skew * this.sk.mult, skewAxis * this.sa.mult); - } - if (this.r && this.appliedTransformations < 4) { - var rotation = this.r.getValueAtTime(time); - matrix.rotate(-rotation * this.r.mult); - } else if (!this.r && this.appliedTransformations < 4) { - var rotationZ = this.rz.getValueAtTime(time); - var rotationY = this.ry.getValueAtTime(time); - var rotationX = this.rx.getValueAtTime(time); - var orientation = this.or.getValueAtTime(time); - matrix.rotateZ(-rotationZ * this.rz.mult) - .rotateY(rotationY * this.ry.mult) - .rotateX(rotationX * this.rx.mult) - .rotateZ(-orientation[2] * this.or.mult) - .rotateY(orientation[1] * this.or.mult) - .rotateX(orientation[0] * this.or.mult); - } - if (this.data.p && this.data.p.s) { - var positionX = this.px.getValueAtTime(time); - var positionY = this.py.getValueAtTime(time); - if (this.data.p.z) { - var positionZ = this.pz.getValueAtTime(time); - matrix.translate( - positionX * this.px.mult, - positionY * this.py.mult, - -positionZ * this.pz.mult - ); - } else { - matrix.translate(positionX * this.px.mult, positionY * this.py.mult, 0); - } - } else { - var position = this.p.getValueAtTime(time); - matrix.translate( - position[0] * this.p.mult, - position[1] * this.p.mult, - -position[2] * this.p.mult - ); + return ret; + } + return (endV - initV) * repeats + current; + } else if (type === 'continue') { + var lastValue = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); + var nextLastValue = this.getValueAtTime((lastKeyFrame - 0.001) / this.comp.globalData.frameRate, 0); + if (this.pv.length) { + ret = new Array(lastValue.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = lastValue[i] + (lastValue[i] - nextLastValue[i]) * ((currentFrame - lastKeyFrame) / this.comp.globalData.frameRate) / 0.0005; // eslint-disable-line } - return matrix; - /// / + return ret; } + return lastValue + (lastValue - nextLastValue) * (((currentFrame - lastKeyFrame)) / 0.001); + } + return this.getValueAtTime((((currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line - function getTransformStaticValueAtTime() { - return this.v.clone(new Matrix()); - } + } - var getTransformProperty = TransformPropertyFactory.getTransformProperty; - TransformPropertyFactory.getTransformProperty = function (elem, data, container) { - var prop = getTransformProperty(elem, data, container); - if (prop.dynamicProperties.length) { - prop.getValueAtTime = getTransformValueAtTime.bind(prop); - } else { - prop.getValueAtTime = getTransformStaticValueAtTime.bind(prop); + function loopIn(type, duration, durationFlag) { + if (!this.k) { + return this.pv; + } + type = type ? type.toLowerCase() : ''; + var currentFrame = this.comp.renderedFrame; + var keyframes = this.keyframes; + var firstKeyFrame = keyframes[0].t; + if (currentFrame >= firstKeyFrame) { + return this.pv; + } + var cycleDuration; + var lastKeyFrame; + if (!durationFlag) { + if (!duration || duration > keyframes.length - 1) { + duration = keyframes.length - 1; + } + lastKeyFrame = keyframes[duration].t; + cycleDuration = lastKeyFrame - firstKeyFrame; + } else { + if (!duration) { + cycleDuration = Math.max(0, this.elem.data.op - firstKeyFrame); + } else { + cycleDuration = Math.abs(this.elem.comp.globalData.frameRate * duration); + } + lastKeyFrame = firstKeyFrame + cycleDuration; + } + var i; + var len; + var ret; + if (type === 'pingpong') { + var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration); + if (iterations % 2 === 0) { + return this.getValueAtTime((((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame)) / this.comp.globalData.frameRate, 0); // eslint-disable-line + } + } else if (type === 'offset') { + var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); + var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0); + var current = this.getValueAtTime((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate, 0); + var repeats = Math.floor((firstKeyFrame - currentFrame) / cycleDuration) + 1; + if (this.pv.length) { + ret = new Array(initV.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = current[i] - (endV[i] - initV[i]) * repeats; } - prop.setGroupProperty = expressionHelpers.setGroupProperty; - return prop; - }; - - var propertyGetProp = PropertyFactory.getProp; - PropertyFactory.getProp = function (elem, data, type, mult, container) { - var prop = propertyGetProp(elem, data, type, mult, container); - // prop.getVelocityAtTime = getVelocityAtTime; - // prop.loopOut = loopOut; - // prop.loopIn = loopIn; - if (prop.kf) { - prop.getValueAtTime = expressionHelpers.getValueAtTime.bind(prop); - } else { - prop.getValueAtTime = expressionHelpers.getStaticValueAtTime.bind(prop); - } - prop.setGroupProperty = expressionHelpers.setGroupProperty; - prop.loopOut = loopOut; - prop.loopIn = loopIn; - prop.smooth = smooth; - prop.getVelocityAtTime = expressionHelpers.getVelocityAtTime.bind(prop); - prop.getSpeedAtTime = expressionHelpers.getSpeedAtTime.bind(prop); - prop.numKeys = data.a === 1 ? data.k.length : 0; - prop.propertyIndex = data.ix; - var value = 0; - if (type !== 0) { - value = createTypedArray('float32', data.a === 1 ? data.k[0].s.length : data.k.length); - } - prop._cachingAtTime = { - lastFrame: initialDefaultFrame, - lastIndex: 0, - value: value, - }; - expressionHelpers.searchExpressions(elem, data, prop); - if (prop.k) { - container.addDynamicProperty(prop); + return ret; + } + return current - (endV - initV) * repeats; + } else if (type === 'continue') { + var firstValue = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0); + var nextFirstValue = this.getValueAtTime((firstKeyFrame + 0.001) / this.comp.globalData.frameRate, 0); + if (this.pv.length) { + ret = new Array(firstValue.length); + len = ret.length; + for (i = 0; i < len; i += 1) { + ret[i] = firstValue[i] + ((firstValue[i] - nextFirstValue[i]) * (firstKeyFrame - currentFrame)) / 0.001; } + return ret; + } + return firstValue + ((firstValue - nextFirstValue) * (firstKeyFrame - currentFrame)) / 0.001; + } + return this.getValueAtTime(((cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame))) / this.comp.globalData.frameRate, 0); // eslint-disable-line - return prop; - }; - - function getShapeValueAtTime(frameNum) { - // For now this caching object is created only when needed instead of creating it when the shape is initialized. - if (!this._cachingAtTime) { - this._cachingAtTime = { - shapeValue: shapePool.clone(this.pv), - lastIndex: 0, - lastTime: initialDefaultFrame, - }; - } + } - frameNum *= this.elem.globalData.frameRate; - frameNum -= this.offsetTime; - if (frameNum !== this._cachingAtTime.lastTime) { - this._cachingAtTime.lastIndex = this._cachingAtTime.lastTime < frameNum ? this._caching.lastIndex : 0; - this._cachingAtTime.lastTime = frameNum; - this.interpolateShape(frameNum, this._cachingAtTime.shapeValue, this._cachingAtTime); + function smooth(width, samples) { + if (!this.k) { + return this.pv; + } + width = (width || 0.4) * 0.5; + samples = Math.floor(samples || 5); + if (samples <= 1) { + return this.pv; + } + var currentTime = this.comp.renderedFrame / this.comp.globalData.frameRate; + var initFrame = currentTime - width; + var endFrame = currentTime + width; + var sampleFrequency = samples > 1 ? (endFrame - initFrame) / (samples - 1) : 1; + var i = 0; + var j = 0; + var value; + if (this.pv.length) { + value = createTypedArray('float32', this.pv.length); + } else { + value = 0; + } + var sampleValue; + while (i < samples) { + sampleValue = this.getValueAtTime(initFrame + i * sampleFrequency); + if (this.pv.length) { + for (j = 0; j < this.pv.length; j += 1) { + value[j] += sampleValue[j]; } - return this._cachingAtTime.shapeValue; + } else { + value += sampleValue; } + i += 1; + } + if (this.pv.length) { + for (j = 0; j < this.pv.length; j += 1) { + value[j] /= samples; + } + } else { + value /= samples; + } + return value; + } - var ShapePropertyConstructorFunction = ShapePropertyFactory.getConstructorFunction(); - var KeyframedShapePropertyConstructorFunction = ShapePropertyFactory.getKeyframedConstructorFunction(); + function getTransformValueAtTime(time) { + if (!this._transformCachingAtTime) { + this._transformCachingAtTime = { + v: new Matrix(), + }; + } + /// / + var matrix = this._transformCachingAtTime.v; + matrix.cloneFromProps(this.pre.props); + if (this.appliedTransformations < 1) { + var anchor = this.a.getValueAtTime(time); + matrix.translate( + -anchor[0] * this.a.mult, + -anchor[1] * this.a.mult, + anchor[2] * this.a.mult + ); + } + if (this.appliedTransformations < 2) { + var scale = this.s.getValueAtTime(time); + matrix.scale( + scale[0] * this.s.mult, + scale[1] * this.s.mult, + scale[2] * this.s.mult + ); + } + if (this.sk && this.appliedTransformations < 3) { + var skew = this.sk.getValueAtTime(time); + var skewAxis = this.sa.getValueAtTime(time); + matrix.skewFromAxis(-skew * this.sk.mult, skewAxis * this.sa.mult); + } + if (this.r && this.appliedTransformations < 4) { + var rotation = this.r.getValueAtTime(time); + matrix.rotate(-rotation * this.r.mult); + } else if (!this.r && this.appliedTransformations < 4) { + var rotationZ = this.rz.getValueAtTime(time); + var rotationY = this.ry.getValueAtTime(time); + var rotationX = this.rx.getValueAtTime(time); + var orientation = this.or.getValueAtTime(time); + matrix.rotateZ(-rotationZ * this.rz.mult) + .rotateY(rotationY * this.ry.mult) + .rotateX(rotationX * this.rx.mult) + .rotateZ(-orientation[2] * this.or.mult) + .rotateY(orientation[1] * this.or.mult) + .rotateX(orientation[0] * this.or.mult); + } + if (this.data.p && this.data.p.s) { + var positionX = this.px.getValueAtTime(time); + var positionY = this.py.getValueAtTime(time); + if (this.data.p.z) { + var positionZ = this.pz.getValueAtTime(time); + matrix.translate( + positionX * this.px.mult, + positionY * this.py.mult, + -positionZ * this.pz.mult + ); + } else { + matrix.translate(positionX * this.px.mult, positionY * this.py.mult, 0); + } + } else { + var position = this.p.getValueAtTime(time); + matrix.translate( + position[0] * this.p.mult, + position[1] * this.p.mult, + -position[2] * this.p.mult + ); + } + return matrix; + /// / + } + + function getTransformStaticValueAtTime() { + return this.v.clone(new Matrix()); + } + + var getTransformProperty = TransformPropertyFactory.getTransformProperty; + TransformPropertyFactory.getTransformProperty = function (elem, data, container) { + var prop = getTransformProperty(elem, data, container); + if (prop.dynamicProperties.length) { + prop.getValueAtTime = getTransformValueAtTime.bind(prop); + } else { + prop.getValueAtTime = getTransformStaticValueAtTime.bind(prop); + } + prop.setGroupProperty = expressionHelpers.setGroupProperty; + return prop; + }; + + var propertyGetProp = PropertyFactory.getProp; + PropertyFactory.getProp = function (elem, data, type, mult, container) { + var prop = propertyGetProp(elem, data, type, mult, container); + // prop.getVelocityAtTime = getVelocityAtTime; + // prop.loopOut = loopOut; + // prop.loopIn = loopIn; + if (prop.kf) { + prop.getValueAtTime = expressionHelpers.getValueAtTime.bind(prop); + } else { + prop.getValueAtTime = expressionHelpers.getStaticValueAtTime.bind(prop); + } + prop.setGroupProperty = expressionHelpers.setGroupProperty; + prop.loopOut = loopOut; + prop.loopIn = loopIn; + prop.smooth = smooth; + prop.getVelocityAtTime = expressionHelpers.getVelocityAtTime.bind(prop); + prop.getSpeedAtTime = expressionHelpers.getSpeedAtTime.bind(prop); + prop.numKeys = data.a === 1 ? data.k.length : 0; + prop.propertyIndex = data.ix; + var value = 0; + if (type !== 0) { + value = createTypedArray('float32', data.a === 1 ? data.k[0].s.length : data.k.length); + } + prop._cachingAtTime = { + lastFrame: initialDefaultFrame, + lastIndex: 0, + value: value, + }; + expressionHelpers.searchExpressions(elem, data, prop); + if (prop.k) { + container.addDynamicProperty(prop); + } - function ShapeExpressions() {} - ShapeExpressions.prototype = { - vertices: function (prop, time) { - if (this.k) { - this.getValue(); - } - var shapePath = this.v; - if (time !== undefined) { - shapePath = this.getValueAtTime(time, 0); - } - var i; - var len = shapePath._length; - var vertices = shapePath[prop]; - var points = shapePath.v; - var arr = createSizedArray(len); - for (i = 0; i < len; i += 1) { - if (prop === 'i' || prop === 'o') { - arr[i] = [vertices[i][0] - points[i][0], vertices[i][1] - points[i][1]]; - } else { - arr[i] = [vertices[i][0], vertices[i][1]]; - } - } - return arr; - }, - points: function (time) { - return this.vertices('v', time); - }, - inTangents: function (time) { - return this.vertices('i', time); - }, - outTangents: function (time) { - return this.vertices('o', time); - }, - isClosed: function () { - return this.v.c; - }, - pointOnPath: function (perc, time) { - var shapePath = this.v; - if (time !== undefined) { - shapePath = this.getValueAtTime(time, 0); - } - if (!this._segmentsLength) { - this._segmentsLength = bez.getSegmentsLength(shapePath); - } + return prop; + }; - var segmentsLength = this._segmentsLength; - var lengths = segmentsLength.lengths; - var lengthPos = segmentsLength.totalLength * perc; - var i = 0; - var len = lengths.length; - var accumulatedLength = 0; - var pt; - while (i < len) { - if (accumulatedLength + lengths[i].addedLength > lengthPos) { - var initIndex = i; - var endIndex = (shapePath.c && i === len - 1) ? 0 : i + 1; - var segmentPerc = (lengthPos - accumulatedLength) / lengths[i].addedLength; - pt = bez.getPointInSegment(shapePath.v[initIndex], shapePath.v[endIndex], shapePath.o[initIndex], shapePath.i[endIndex], segmentPerc, lengths[i]); - break; - } else { - accumulatedLength += lengths[i].addedLength; - } - i += 1; - } - if (!pt) { - pt = shapePath.c ? [shapePath.v[0][0], shapePath.v[0][1]] : [shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1]]; - } - return pt; - }, - vectorOnPath: function (perc, time, vectorType) { - // perc doesn't use triple equality because it can be a Number object as well as a primitive. - if (perc == 1) { // eslint-disable-line eqeqeq - perc = this.v.c; - } else if (perc == 0) { // eslint-disable-line eqeqeq - perc = 0.999; - } - var pt1 = this.pointOnPath(perc, time); - var pt2 = this.pointOnPath(perc + 0.001, time); - var xLength = pt2[0] - pt1[0]; - var yLength = pt2[1] - pt1[1]; - var magnitude = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2)); - if (magnitude === 0) { - return [0, 0]; - } - var unitVector = vectorType === 'tangent' ? [xLength / magnitude, yLength / magnitude] : [-yLength / magnitude, xLength / magnitude]; - return unitVector; - }, - tangentOnPath: function (perc, time) { - return this.vectorOnPath(perc, time, 'tangent'); - }, - normalOnPath: function (perc, time) { - return this.vectorOnPath(perc, time, 'normal'); - }, - setGroupProperty: expressionHelpers.setGroupProperty, - getValueAtTime: expressionHelpers.getStaticValueAtTime, - }; - extendPrototype([ShapeExpressions], ShapePropertyConstructorFunction); - extendPrototype([ShapeExpressions], KeyframedShapePropertyConstructorFunction); - KeyframedShapePropertyConstructorFunction.prototype.getValueAtTime = getShapeValueAtTime; - KeyframedShapePropertyConstructorFunction.prototype.initiateExpression = ExpressionManager.initiateExpression; - - var propertyGetShapeProp = ShapePropertyFactory.getShapeProp; - ShapePropertyFactory.getShapeProp = function (elem, data, type, arr, trims) { - var prop = propertyGetShapeProp(elem, data, type, arr, trims); - prop.propertyIndex = data.ix; - prop.lock = false; - if (type === 3) { - expressionHelpers.searchExpressions(elem, data.pt, prop); - } else if (type === 4) { - expressionHelpers.searchExpressions(elem, data.ks, prop); - } - if (prop.k) { - elem.addDynamicProperty(prop); - } - return prop; + function getShapeValueAtTime(frameNum) { + // For now this caching object is created only when needed instead of creating it when the shape is initialized. + if (!this._cachingAtTime) { + this._cachingAtTime = { + shapeValue: shapePool.clone(this.pv), + lastIndex: 0, + lastTime: initialDefaultFrame, }; } - function initialize$1() { - addPropertyDecorator(); + frameNum *= this.elem.globalData.frameRate; + frameNum -= this.offsetTime; + if (frameNum !== this._cachingAtTime.lastTime) { + this._cachingAtTime.lastIndex = this._cachingAtTime.lastTime < frameNum ? this._caching.lastIndex : 0; + this._cachingAtTime.lastTime = frameNum; + this.interpolateShape(frameNum, this._cachingAtTime.shapeValue, this._cachingAtTime); } + return this._cachingAtTime.shapeValue; + } - function addDecorator() { - function searchExpressions() { - if (this.data.d.x) { - this.calculateExpression = ExpressionManager.initiateExpression.bind(this)(this.elem, this.data.d, this); - this.addEffect(this.getExpressionValue.bind(this)); - return true; - } - return null; - } + var ShapePropertyConstructorFunction = ShapePropertyFactory.getConstructorFunction(); + var KeyframedShapePropertyConstructorFunction = ShapePropertyFactory.getKeyframedConstructorFunction(); - TextProperty.prototype.getExpressionValue = function (currentValue, text) { - var newValue = this.calculateExpression(text); - if (currentValue.t !== newValue) { - var newData = {}; - this.copyData(newData, currentValue); - newData.t = newValue.toString(); - newData.__complete = false; - return newData; + function ShapeExpressions() {} + ShapeExpressions.prototype = { + vertices: function (prop, time) { + if (this.k) { + this.getValue(); + } + var shapePath = this.v; + if (time !== undefined) { + shapePath = this.getValueAtTime(time, 0); + } + var i; + var len = shapePath._length; + var vertices = shapePath[prop]; + var points = shapePath.v; + var arr = createSizedArray(len); + for (i = 0; i < len; i += 1) { + if (prop === 'i' || prop === 'o') { + arr[i] = [vertices[i][0] - points[i][0], vertices[i][1] - points[i][1]]; + } else { + arr[i] = [vertices[i][0], vertices[i][1]]; + } + } + return arr; + }, + points: function (time) { + return this.vertices('v', time); + }, + inTangents: function (time) { + return this.vertices('i', time); + }, + outTangents: function (time) { + return this.vertices('o', time); + }, + isClosed: function () { + return this.v.c; + }, + pointOnPath: function (perc, time) { + var shapePath = this.v; + if (time !== undefined) { + shapePath = this.getValueAtTime(time, 0); + } + if (!this._segmentsLength) { + this._segmentsLength = bez.getSegmentsLength(shapePath); + } + + var segmentsLength = this._segmentsLength; + var lengths = segmentsLength.lengths; + var lengthPos = segmentsLength.totalLength * perc; + var i = 0; + var len = lengths.length; + var accumulatedLength = 0; + var pt; + while (i < len) { + if (accumulatedLength + lengths[i].addedLength > lengthPos) { + var initIndex = i; + var endIndex = (shapePath.c && i === len - 1) ? 0 : i + 1; + var segmentPerc = (lengthPos - accumulatedLength) / lengths[i].addedLength; + pt = bez.getPointInSegment(shapePath.v[initIndex], shapePath.v[endIndex], shapePath.o[initIndex], shapePath.i[endIndex], segmentPerc, lengths[i]); + break; + } else { + accumulatedLength += lengths[i].addedLength; } - return currentValue; - }; + i += 1; + } + if (!pt) { + pt = shapePath.c ? [shapePath.v[0][0], shapePath.v[0][1]] : [shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1]]; + } + return pt; + }, + vectorOnPath: function (perc, time, vectorType) { + // perc doesn't use triple equality because it can be a Number object as well as a primitive. + if (perc == 1) { // eslint-disable-line eqeqeq + perc = this.v.c; + } else if (perc == 0) { // eslint-disable-line eqeqeq + perc = 0.999; + } + var pt1 = this.pointOnPath(perc, time); + var pt2 = this.pointOnPath(perc + 0.001, time); + var xLength = pt2[0] - pt1[0]; + var yLength = pt2[1] - pt1[1]; + var magnitude = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2)); + if (magnitude === 0) { + return [0, 0]; + } + var unitVector = vectorType === 'tangent' ? [xLength / magnitude, yLength / magnitude] : [-yLength / magnitude, xLength / magnitude]; + return unitVector; + }, + tangentOnPath: function (perc, time) { + return this.vectorOnPath(perc, time, 'tangent'); + }, + normalOnPath: function (perc, time) { + return this.vectorOnPath(perc, time, 'normal'); + }, + setGroupProperty: expressionHelpers.setGroupProperty, + getValueAtTime: expressionHelpers.getStaticValueAtTime, + }; + extendPrototype([ShapeExpressions], ShapePropertyConstructorFunction); + extendPrototype([ShapeExpressions], KeyframedShapePropertyConstructorFunction); + KeyframedShapePropertyConstructorFunction.prototype.getValueAtTime = getShapeValueAtTime; + KeyframedShapePropertyConstructorFunction.prototype.initiateExpression = ExpressionManager.initiateExpression; + + var propertyGetShapeProp = ShapePropertyFactory.getShapeProp; + ShapePropertyFactory.getShapeProp = function (elem, data, type, arr, trims) { + var prop = propertyGetShapeProp(elem, data, type, arr, trims); + prop.propertyIndex = data.ix; + prop.lock = false; + if (type === 3) { + expressionHelpers.searchExpressions(elem, data.pt, prop); + } else if (type === 4) { + expressionHelpers.searchExpressions(elem, data.ks, prop); + } + if (prop.k) { + elem.addDynamicProperty(prop); + } + return prop; + }; +} + +function initialize$1() { + addPropertyDecorator(); +} + +function addDecorator() { + function searchExpressions() { + if (this.data.d.x) { + this.calculateExpression = ExpressionManager.initiateExpression.bind(this)(this.elem, this.data.d, this); + this.addEffect(this.getExpressionValue.bind(this)); + return true; + } + return null; + } + + TextProperty.prototype.getExpressionValue = function (currentValue, text) { + var newValue = this.calculateExpression(text); + if (currentValue.t !== newValue) { + var newData = {}; + this.copyData(newData, currentValue); + newData.t = newValue.toString(); + newData.__complete = false; + return newData; + } + return currentValue; + }; - TextProperty.prototype.searchProperty = function () { - var isKeyframed = this.searchKeyframes(); - var hasExpressions = this.searchExpressions(); - this.kf = isKeyframed || hasExpressions; - return this.kf; - }; + TextProperty.prototype.searchProperty = function () { + var isKeyframed = this.searchKeyframes(); + var hasExpressions = this.searchExpressions(); + this.kf = isKeyframed || hasExpressions; + return this.kf; + }; - TextProperty.prototype.searchExpressions = searchExpressions; - } + TextProperty.prototype.searchExpressions = searchExpressions; +} - function initialize() { - addDecorator(); - } +function initialize() { + addDecorator(); +} - // Registering expression plugin - setExpressionsPlugin(Expressions); - initialize$1(); - initialize(); +// Registering expression plugin +setExpressionsPlugin(Expressions); +initialize$1(); +initialize(); - return lottie; +return lottie; } ); diff --git a/examples/jsm/libs/motion-controllers.module.js b/examples/jsm/libs/motion-controllers.module.js index a0a0a162b92d0c..37944de1949a9a 100644 --- a/examples/jsm/libs/motion-controllers.module.js +++ b/examples/jsm/libs/motion-controllers.module.js @@ -3,177 +3,139 @@ */ const Constants = { - Handedness: { - NONE: 'none', - LEFT: 'left', - RIGHT: 'right' - }, - - ComponentState: { - DEFAULT: 'default', - TOUCHED: 'touched', - PRESSED: 'pressed' - }, - - ComponentProperty: { - BUTTON: 'button', - X_AXIS: 'xAxis', - Y_AXIS: 'yAxis', - STATE: 'state' - }, - - ComponentType: { - TRIGGER: 'trigger', - SQUEEZE: 'squeeze', - TOUCHPAD: 'touchpad', - THUMBSTICK: 'thumbstick', - BUTTON: 'button' - }, - - ButtonTouchThreshold: 0.05, - - AxisTouchThreshold: 0.1, - - VisualResponseProperty: { - TRANSFORM: 'transform', - VISIBILITY: 'visibility' - } + Handedness: { + NONE: 'none', + LEFT: 'left', + RIGHT: 'right' + }, + + ComponentState: { + DEFAULT: 'default', + TOUCHED: 'touched', + PRESSED: 'pressed' + }, + + ComponentProperty: { + BUTTON: 'button', + X_AXIS: 'xAxis', + Y_AXIS: 'yAxis', + STATE: 'state' + }, + + ComponentType: { + TRIGGER: 'trigger', + SQUEEZE: 'squeeze', + TOUCHPAD: 'touchpad', + THUMBSTICK: 'thumbstick', + BUTTON: 'button' + }, + + ButtonTouchThreshold: 0.05, + + AxisTouchThreshold: 0.1, + + VisualResponseProperty: { + TRANSFORM: 'transform', + VISIBILITY: 'visibility' + } }; /** * @description Static helper function to fetch a JSON file and turn it into a JS object * @param {string} path - Path to JSON file to be fetched */ -async function fetchJsonFile( path ) { - - const response = await fetch( path ); - if ( ! response.ok ) { - - throw new Error( response.statusText ); - - } else { - - return response.json(); - - } - +async function fetchJsonFile(path) { + const response = await fetch(path); + if (!response.ok) { + throw new Error(response.statusText); + } else { + return response.json(); + } } -async function fetchProfilesList( basePath ) { - - if ( ! basePath ) { - - throw new Error( 'No basePath supplied' ); - - } - - const profileListFileName = 'profilesList.json'; - const profilesList = await fetchJsonFile( `${basePath}/${profileListFileName}` ); - return profilesList; +async function fetchProfilesList(basePath) { + if (!basePath) { + throw new Error('No basePath supplied'); + } + const profileListFileName = 'profilesList.json'; + const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`); + return profilesList; } -async function fetchProfile( xrInputSource, basePath, defaultProfile = null, getAssetPath = true ) { - - if ( ! xrInputSource ) { - - throw new Error( 'No xrInputSource supplied' ); - - } - - if ( ! basePath ) { - - throw new Error( 'No basePath supplied' ); - - } - - // Get the list of profiles - const supportedProfilesList = await fetchProfilesList( basePath ); - - // Find the relative path to the first requested profile that is recognized - let match; - xrInputSource.profiles.some( ( profileId ) => { - - const supportedProfile = supportedProfilesList[ profileId ]; - if ( supportedProfile ) { - - match = { - profileId, - profilePath: `${basePath}/${supportedProfile.path}`, - deprecated: !! supportedProfile.deprecated - }; - - } - - return !! match; - - } ); - - if ( ! match ) { - - if ( ! defaultProfile ) { - - throw new Error( 'No matching profile name found' ); - - } - - const supportedProfile = supportedProfilesList[ defaultProfile ]; - if ( ! supportedProfile ) { - - throw new Error( `No matching profile name found and default profile "${defaultProfile}" missing.` ); - - } - - match = { - profileId: defaultProfile, - profilePath: `${basePath}/${supportedProfile.path}`, - deprecated: !! supportedProfile.deprecated - }; - - } - - const profile = await fetchJsonFile( match.profilePath ); - - let assetPath; - if ( getAssetPath ) { - - let layout; - if ( xrInputSource.handedness === 'any' ) { - - layout = profile.layouts[ Object.keys( profile.layouts )[ 0 ] ]; - - } else { - - layout = profile.layouts[ xrInputSource.handedness ]; - - } - - if ( ! layout ) { - - throw new Error( - `No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}` - ); - - } - - if ( layout.assetPath ) { - - assetPath = match.profilePath.replace( 'profile.json', layout.assetPath ); - - } - - } - - return { profile, assetPath }; - +async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) { + if (!xrInputSource) { + throw new Error('No xrInputSource supplied'); + } + + if (!basePath) { + throw new Error('No basePath supplied'); + } + + // Get the list of profiles + const supportedProfilesList = await fetchProfilesList(basePath); + + // Find the relative path to the first requested profile that is recognized + let match; + xrInputSource.profiles.some((profileId) => { + const supportedProfile = supportedProfilesList[profileId]; + if (supportedProfile) { + match = { + profileId, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !!supportedProfile.deprecated + }; + } + return !!match; + }); + + if (!match) { + if (!defaultProfile) { + throw new Error('No matching profile name found'); + } + + const supportedProfile = supportedProfilesList[defaultProfile]; + if (!supportedProfile) { + throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`); + } + + match = { + profileId: defaultProfile, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !!supportedProfile.deprecated + }; + } + + const profile = await fetchJsonFile(match.profilePath); + + let assetPath; + if (getAssetPath) { + let layout; + if (xrInputSource.handedness === 'any') { + layout = profile.layouts[Object.keys(profile.layouts)[0]]; + } else { + layout = profile.layouts[xrInputSource.handedness]; + } + if (!layout) { + throw new Error( + `No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}` + ); + } + + if (layout.assetPath) { + assetPath = match.profilePath.replace('profile.json', layout.assetPath); + } + } + + return { profile, assetPath }; } /** @constant {Object} */ const defaultComponentValues = { - xAxis: 0, - yAxis: 0, - button: 0, - state: Constants.ComponentState.DEFAULT + xAxis: 0, + yAxis: 0, + button: 0, + state: Constants.ComponentState.DEFAULT }; /** @@ -184,30 +146,26 @@ const defaultComponentValues = { * @param {number} x The original x coordinate in the range -1 to 1 * @param {number} y The original y coordinate in the range -1 to 1 */ -function normalizeAxes( x = 0, y = 0 ) { - - let xAxis = x; - let yAxis = y; - - // Determine if the point is outside the bounds of the circle - // and, if so, place it on the edge of the circle - const hypotenuse = Math.sqrt( ( x * x ) + ( y * y ) ); - if ( hypotenuse > 1 ) { - - const theta = Math.atan2( y, x ); - xAxis = Math.cos( theta ); - yAxis = Math.sin( theta ); - - } - - // Scale and move the circle so values are in the interpolation range. The circle's origin moves - // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5. - const result = { - normalizedXAxis: ( xAxis * 0.5 ) + 0.5, - normalizedYAxis: ( yAxis * 0.5 ) + 0.5 - }; - return result; - +function normalizeAxes(x = 0, y = 0) { + let xAxis = x; + let yAxis = y; + + // Determine if the point is outside the bounds of the circle + // and, if so, place it on the edge of the circle + const hypotenuse = Math.sqrt((x * x) + (y * y)); + if (hypotenuse > 1) { + const theta = Math.atan2(y, x); + xAxis = Math.cos(theta); + yAxis = Math.sin(theta); + } + + // Scale and move the circle so values are in the interpolation range. The circle's origin moves + // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5. + const result = { + normalizedXAxis: (xAxis * 0.5) + 0.5, + normalizedYAxis: (yAxis * 0.5) + 0.5 + }; + return result; } /** @@ -219,28 +177,23 @@ function normalizeAxes( x = 0, y = 0 ) { * interpolating between the range of motion nodes. */ class VisualResponse { - - constructor( visualResponseDescription ) { - - this.componentProperty = visualResponseDescription.componentProperty; - this.states = visualResponseDescription.states; - this.valueNodeName = visualResponseDescription.valueNodeName; - this.valueNodeProperty = visualResponseDescription.valueNodeProperty; - - if ( this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM ) { - - this.minNodeName = visualResponseDescription.minNodeName; - this.maxNodeName = visualResponseDescription.maxNodeName; - - } - - // Initializes the response's current value based on default data - this.value = 0; - this.updateFromComponent( defaultComponentValues ); - - } - - /** + constructor(visualResponseDescription) { + this.componentProperty = visualResponseDescription.componentProperty; + this.states = visualResponseDescription.states; + this.valueNodeName = visualResponseDescription.valueNodeName; + this.valueNodeProperty = visualResponseDescription.valueNodeProperty; + + if (this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM) { + this.minNodeName = visualResponseDescription.minNodeName; + this.maxNodeName = visualResponseDescription.maxNodeName; + } + + // Initializes the response's current value based on default data + this.value = 0; + this.updateFromComponent(defaultComponentValues); + } + + /** * Computes the visual response's interpolation weight based on component state * @param {Object} componentValues - The component from which to update * @param {number} xAxis - The reported X axis value of the component @@ -248,170 +201,132 @@ class VisualResponse { * @param {number} button - The reported value of the component's button * @param {string} state - The component's active state */ - updateFromComponent( { - xAxis, yAxis, button, state - } ) { - - const { normalizedXAxis, normalizedYAxis } = normalizeAxes( xAxis, yAxis ); - switch ( this.componentProperty ) { - - case Constants.ComponentProperty.X_AXIS: - this.value = ( this.states.includes( state ) ) ? normalizedXAxis : 0.5; - break; - case Constants.ComponentProperty.Y_AXIS: - this.value = ( this.states.includes( state ) ) ? normalizedYAxis : 0.5; - break; - case Constants.ComponentProperty.BUTTON: - this.value = ( this.states.includes( state ) ) ? button : 0; - break; - case Constants.ComponentProperty.STATE: - if ( this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY ) { - - this.value = ( this.states.includes( state ) ); - - } else { - - this.value = this.states.includes( state ) ? 1.0 : 0.0; - - } - - break; - default: - throw new Error( `Unexpected visualResponse componentProperty ${this.componentProperty}` ); - - } - - } - + updateFromComponent({ + xAxis, yAxis, button, state + }) { + const { normalizedXAxis, normalizedYAxis } = normalizeAxes(xAxis, yAxis); + switch (this.componentProperty) { + case Constants.ComponentProperty.X_AXIS: + this.value = (this.states.includes(state)) ? normalizedXAxis : 0.5; + break; + case Constants.ComponentProperty.Y_AXIS: + this.value = (this.states.includes(state)) ? normalizedYAxis : 0.5; + break; + case Constants.ComponentProperty.BUTTON: + this.value = (this.states.includes(state)) ? button : 0; + break; + case Constants.ComponentProperty.STATE: + if (this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY) { + this.value = (this.states.includes(state)); + } else { + this.value = this.states.includes(state) ? 1.0 : 0.0; + } + break; + default: + throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`); + } + } } class Component { - - /** + /** * @param {Object} componentId - Id of the component * @param {Object} componentDescription - Description of the component to be created */ - constructor( componentId, componentDescription ) { - - if ( ! componentId - || ! componentDescription - || ! componentDescription.visualResponses - || ! componentDescription.gamepadIndices - || Object.keys( componentDescription.gamepadIndices ).length === 0 ) { - - throw new Error( 'Invalid arguments supplied' ); - - } - - this.id = componentId; - this.type = componentDescription.type; - this.rootNodeName = componentDescription.rootNodeName; - this.touchPointNodeName = componentDescription.touchPointNodeName; - - // Build all the visual responses for this component - this.visualResponses = {}; - Object.keys( componentDescription.visualResponses ).forEach( ( responseName ) => { - - const visualResponse = new VisualResponse( componentDescription.visualResponses[ responseName ] ); - this.visualResponses[ responseName ] = visualResponse; - - } ); - - // Set default values - this.gamepadIndices = Object.assign( {}, componentDescription.gamepadIndices ); - - this.values = { - state: Constants.ComponentState.DEFAULT, - button: ( this.gamepadIndices.button !== undefined ) ? 0 : undefined, - xAxis: ( this.gamepadIndices.xAxis !== undefined ) ? 0 : undefined, - yAxis: ( this.gamepadIndices.yAxis !== undefined ) ? 0 : undefined - }; - - } - - get data() { - - const data = { id: this.id, ...this.values }; - return data; - - } - - /** + constructor(componentId, componentDescription) { + if (!componentId + || !componentDescription + || !componentDescription.visualResponses + || !componentDescription.gamepadIndices + || Object.keys(componentDescription.gamepadIndices).length === 0) { + throw new Error('Invalid arguments supplied'); + } + + this.id = componentId; + this.type = componentDescription.type; + this.rootNodeName = componentDescription.rootNodeName; + this.touchPointNodeName = componentDescription.touchPointNodeName; + + // Build all the visual responses for this component + this.visualResponses = {}; + Object.keys(componentDescription.visualResponses).forEach((responseName) => { + const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]); + this.visualResponses[responseName] = visualResponse; + }); + + // Set default values + this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices); + + this.values = { + state: Constants.ComponentState.DEFAULT, + button: (this.gamepadIndices.button !== undefined) ? 0 : undefined, + xAxis: (this.gamepadIndices.xAxis !== undefined) ? 0 : undefined, + yAxis: (this.gamepadIndices.yAxis !== undefined) ? 0 : undefined + }; + } + + get data() { + const data = { id: this.id, ...this.values }; + return data; + } + + /** * @description Poll for updated data based on current gamepad state * @param {Object} gamepad - The gamepad object from which the component data should be polled */ - updateFromGamepad( gamepad ) { - - // Set the state to default before processing other data sources - this.values.state = Constants.ComponentState.DEFAULT; - - // Get and normalize button - if ( this.gamepadIndices.button !== undefined - && gamepad.buttons.length > this.gamepadIndices.button ) { - - const gamepadButton = gamepad.buttons[ this.gamepadIndices.button ]; - this.values.button = gamepadButton.value; - this.values.button = ( this.values.button < 0 ) ? 0 : this.values.button; - this.values.button = ( this.values.button > 1 ) ? 1 : this.values.button; - - // Set the state based on the button - if ( gamepadButton.pressed || this.values.button === 1 ) { - - this.values.state = Constants.ComponentState.PRESSED; - - } else if ( gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold ) { - - this.values.state = Constants.ComponentState.TOUCHED; - - } - - } - - // Get and normalize x axis value - if ( this.gamepadIndices.xAxis !== undefined - && gamepad.axes.length > this.gamepadIndices.xAxis ) { - - this.values.xAxis = gamepad.axes[ this.gamepadIndices.xAxis ]; - this.values.xAxis = ( this.values.xAxis < - 1 ) ? - 1 : this.values.xAxis; - this.values.xAxis = ( this.values.xAxis > 1 ) ? 1 : this.values.xAxis; - - // If the state is still default, check if the xAxis makes it touched - if ( this.values.state === Constants.ComponentState.DEFAULT - && Math.abs( this.values.xAxis ) > Constants.AxisTouchThreshold ) { - - this.values.state = Constants.ComponentState.TOUCHED; - - } - - } - - // Get and normalize Y axis value - if ( this.gamepadIndices.yAxis !== undefined - && gamepad.axes.length > this.gamepadIndices.yAxis ) { - - this.values.yAxis = gamepad.axes[ this.gamepadIndices.yAxis ]; - this.values.yAxis = ( this.values.yAxis < - 1 ) ? - 1 : this.values.yAxis; - this.values.yAxis = ( this.values.yAxis > 1 ) ? 1 : this.values.yAxis; - - // If the state is still default, check if the yAxis makes it touched - if ( this.values.state === Constants.ComponentState.DEFAULT - && Math.abs( this.values.yAxis ) > Constants.AxisTouchThreshold ) { - - this.values.state = Constants.ComponentState.TOUCHED; - - } - - } - - // Update the visual response weights based on the current component data - Object.values( this.visualResponses ).forEach( ( visualResponse ) => { - - visualResponse.updateFromComponent( this.values ); - - } ); - - } - + updateFromGamepad(gamepad) { + // Set the state to default before processing other data sources + this.values.state = Constants.ComponentState.DEFAULT; + + // Get and normalize button + if (this.gamepadIndices.button !== undefined + && gamepad.buttons.length > this.gamepadIndices.button) { + const gamepadButton = gamepad.buttons[this.gamepadIndices.button]; + this.values.button = gamepadButton.value; + this.values.button = (this.values.button < 0) ? 0 : this.values.button; + this.values.button = (this.values.button > 1) ? 1 : this.values.button; + + // Set the state based on the button + if (gamepadButton.pressed || this.values.button === 1) { + this.values.state = Constants.ComponentState.PRESSED; + } else if (gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Get and normalize x axis value + if (this.gamepadIndices.xAxis !== undefined + && gamepad.axes.length > this.gamepadIndices.xAxis) { + this.values.xAxis = gamepad.axes[this.gamepadIndices.xAxis]; + this.values.xAxis = (this.values.xAxis < -1) ? -1 : this.values.xAxis; + this.values.xAxis = (this.values.xAxis > 1) ? 1 : this.values.xAxis; + + // If the state is still default, check if the xAxis makes it touched + if (this.values.state === Constants.ComponentState.DEFAULT + && Math.abs(this.values.xAxis) > Constants.AxisTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Get and normalize Y axis value + if (this.gamepadIndices.yAxis !== undefined + && gamepad.axes.length > this.gamepadIndices.yAxis) { + this.values.yAxis = gamepad.axes[this.gamepadIndices.yAxis]; + this.values.yAxis = (this.values.yAxis < -1) ? -1 : this.values.yAxis; + this.values.yAxis = (this.values.yAxis > 1) ? 1 : this.values.yAxis; + + // If the state is still default, check if the yAxis makes it touched + if (this.values.state === Constants.ComponentState.DEFAULT + && Math.abs(this.values.yAxis) > Constants.AxisTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Update the visual response weights based on the current component data + Object.values(this.visualResponses).forEach((visualResponse) => { + visualResponse.updateFromComponent(this.values); + }); + } } /** @@ -420,85 +335,63 @@ class Component { * @author Nell Waliczek / https://github.com/NellWaliczek */ class MotionController { - - /** + /** * @param {Object} xrInputSource - The XRInputSource to build the MotionController around * @param {Object} profile - The best matched profile description for the supplied xrInputSource * @param {Object} assetUrl */ - constructor( xrInputSource, profile, assetUrl ) { - - if ( ! xrInputSource ) { - - throw new Error( 'No xrInputSource supplied' ); - - } - - if ( ! profile ) { - - throw new Error( 'No profile supplied' ); - - } - - this.xrInputSource = xrInputSource; - this.assetUrl = assetUrl; - this.id = profile.profileId; - - // Build child components as described in the profile description - this.layoutDescription = profile.layouts[ xrInputSource.handedness ]; - this.components = {}; - Object.keys( this.layoutDescription.components ).forEach( ( componentId ) => { - - const componentDescription = this.layoutDescription.components[ componentId ]; - this.components[ componentId ] = new Component( componentId, componentDescription ); - - } ); - - // Initialize components based on current gamepad state - this.updateFromGamepad(); - - } - - get gripSpace() { - - return this.xrInputSource.gripSpace; - - } - - get targetRaySpace() { - - return this.xrInputSource.targetRaySpace; - - } - - /** + constructor(xrInputSource, profile, assetUrl) { + if (!xrInputSource) { + throw new Error('No xrInputSource supplied'); + } + + if (!profile) { + throw new Error('No profile supplied'); + } + + this.xrInputSource = xrInputSource; + this.assetUrl = assetUrl; + this.id = profile.profileId; + + // Build child components as described in the profile description + this.layoutDescription = profile.layouts[xrInputSource.handedness]; + this.components = {}; + Object.keys(this.layoutDescription.components).forEach((componentId) => { + const componentDescription = this.layoutDescription.components[componentId]; + this.components[componentId] = new Component(componentId, componentDescription); + }); + + // Initialize components based on current gamepad state + this.updateFromGamepad(); + } + + get gripSpace() { + return this.xrInputSource.gripSpace; + } + + get targetRaySpace() { + return this.xrInputSource.targetRaySpace; + } + + /** * @description Returns a subset of component data for simplified debugging */ - get data() { - - const data = []; - Object.values( this.components ).forEach( ( component ) => { - - data.push( component.data ); - - } ); - return data; - - } - - /** + get data() { + const data = []; + Object.values(this.components).forEach((component) => { + data.push(component.data); + }); + return data; + } + + /** * @description Poll for updated data based on current gamepad state */ - updateFromGamepad() { - - Object.values( this.components ).forEach( ( component ) => { - - component.updateFromGamepad( this.xrInputSource.gamepad ); - - } ); - - } - + updateFromGamepad() { + Object.values(this.components).forEach((component) => { + component.updateFromGamepad(this.xrInputSource.gamepad); + }); + } } export { Constants, MotionController, fetchProfile, fetchProfilesList }; diff --git a/examples/jsm/libs/opentype.module.js b/examples/jsm/libs/opentype.module.js index 71ca072a678ca9..36bfc7d72bc6d4 100644 --- a/examples/jsm/libs/opentype.module.js +++ b/examples/jsm/libs/opentype.module.js @@ -1,14070 +1,14097 @@ +const { opentype, BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer } = /* @__PURE__ */ ( () => { + /** * https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens */ -const { BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer } = /* @__PURE__ */ ( () => { - - /*! https://mths.be/codepointat v0.2.0 by @mathias */ - if (!String.prototype.codePointAt) { - (function() { - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (index != index) { // better `isNaN` - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( // check if it’s the start of a surrogate pair - first >= 0xD800 && first <= 0xDBFF && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; - if (defineProperty) { - defineProperty(String.prototype, 'codePointAt', { - 'value': codePointAt, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.codePointAt = codePointAt; - } - }()); - } +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} + +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ +} + +function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +var code_tree = new Tree(); +var lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d, num, base) { + if (!num) + { return base; } + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { lengths[i] = 0; } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + { throw new Error('Data error'); } + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +var tinyInflate = tinf_uncompress; + +// The Bounding Box object + +function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; +} +/** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ +function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; +} - var TINF_OK = 0; - var TINF_DATA_ERROR = -3; +/** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ +BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); +}; - function Tree() { - this.table = new Uint16Array(16); /* table of code length counts */ - this.trans = new Uint16Array(288); /* code -> symbol translation table */ +/** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } } - - function Data(source, dest) { - this.source = source; - this.sourceIndex = 0; - this.tag = 0; - this.bitcount = 0; - - this.dest = dest; - this.destLen = 0; - - this.ltree = new Tree(); /* dynamic length/symbol tree */ - this.dtree = new Tree(); /* dynamic distance tree */ + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } } +}; - /* --------------------------------------------------- * - * -- uninitialized global data (static structures) -- * - * --------------------------------------------------- */ - - var sltree = new Tree(); - var sdtree = new Tree(); - - /* extra bits and base tables for length codes */ - var length_bits = new Uint8Array(30); - var length_base = new Uint16Array(30); +/** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ +BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); +}; - /* extra bits and base tables for distance codes */ - var dist_bits = new Uint8Array(30); - var dist_base = new Uint16Array(30); +/** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); +}; - /* special ordering of code length codes */ - var clcidx = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, - 10, 5, 11, 4, 12, 3, 13, 2, - 14, 1, 15 - ]); +/** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box - /* used by tinf_decode_trees, avoids allocations every call */ - var code_tree = new Tree(); - var lengths = new Uint8Array(288 + 32); + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; - /* ----------------------- * - * -- utility functions -- * - * ----------------------- */ + this.addPoint(x0, y0); + this.addPoint(x, y); - /* build extra bits and base tables */ - function tinf_build_bits_base(bits, base, delta, first) { - var i, sum; + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; - /* build bits table */ - for (i = 0; i < delta; ++i) { bits[i] = 0; } - for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } - /* build base table */ - for (sum = first, i = 0; i < 30; ++i) { - base[i] = sum; - sum += 1 << bits[i]; - } + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } } +}; - /* build the fixed huffman trees */ - function tinf_build_fixed_trees(lt, dt) { - var i; - - /* build fixed length tree */ - for (i = 0; i < 7; ++i) { lt.table[i] = 0; } +/** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); +}; - lt.table[7] = 24; - lt.table[8] = 152; - lt.table[9] = 112; +// Geometric objects - for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } - for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } - for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } - for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } +/** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ +function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; +} - /* build fixed distance tree */ - for (i = 0; i < 5; ++i) { dt.table[i] = 0; } +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); +}; - dt.table[5] = 32; +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); +}; - for (i = 0; i < 32; ++i) { dt.trans[i] = i; } - } +/** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ - /* given an array of code lengths, build a tree */ - var offs = new Uint16Array(16); +/** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ +Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); +}; - function tinf_build_tree(t, lengths, off, num) { - var i, sum; +/** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ - /* clear code length count table */ - for (i = 0; i < 16; ++i) { t.table[i] = 0; } +/** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ +Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); +}; - /* scan symbol lengths, and sum code length counts */ - for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } +/** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ - t.table[0] = 0; +/** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ +Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); +}; - /* compute offset table for distribution sort */ - for (sum = 0, i = 0; i < 16; ++i) { - offs[i] = sum; - sum += t.table[i]; - } +/** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ +Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); +}; - /* create code->symbol translation table (symbols sorted by code) */ - for (i = 0; i < num; ++i) { - if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } +/** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ +Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } } + if (box.isEmpty()) { + box.addPoint(0, 0); } + return box; +}; - /* ---------------------- * - * -- decode functions -- * - * ---------------------- */ - - /* get one bit from source stream */ - function tinf_getbit(d) { - /* check if tag is empty */ - if (!d.bitcount--) { - /* load next tag */ - d.tag = d.source[d.sourceIndex++]; - d.bitcount = 7; +/** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ +Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } } - /* shift bit out of tag */ - var bit = d.tag & 1; - d.tag >>>= 1; + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } - return bit; + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); } +}; - /* read a num bit value from a stream and add base */ - function tinf_read_bits(d, num, base) { - if (!num) - { return base; } +/** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } } - var val = d.tag & (0xffff >>> (16 - num)); - d.tag >>>= num; - d.bitcount -= num; - return val + base; - } + function packValues() { + var arguments$1 = arguments; - /* given a data stream and a tree, decode a symbol */ - function tinf_decode_symbol(d, t) { - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var sum = 0, cur = 0, len = 0; - var tag = d.tag; + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } - /* get more bits while code value is above sum */ - do { - cur = 2 * cur + (tag & 1); - tag >>>= 1; - ++len; + s += floatToString(v); + } - sum += t.table[len]; - cur -= t.table[len]; - } while (cur >= 0); - - d.tag = tag; - d.bitcount -= len; + return s; + } - return t.trans[sum + cur]; + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } } - /* given a data stream, decode dynamic trees from it */ - function tinf_decode_trees(d, lt, dt) { - var hlit, hdist, hclen; - var i, num, length; + return d; +}; - /* get 5 bits HLIT (257-286) */ - hlit = tinf_read_bits(d, 5, 257); +/** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toSVG = function(decimalPlaces) { + var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; +}; +/** + * @constant + * @type {number} + */ +sizeOf.BYTE = constant(1); - dist = tinf_decode_symbol(d, dt); +/** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ +encode.CHAR = function(v) { + return [v.charCodeAt(0)]; +}; - /* possibly get more bits from distance code */ - offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); +/** + * @constant + * @type {number} + */ +sizeOf.CHAR = constant(1); - /* copy match */ - for (i = offs; i < offs + length; ++i) { - d.dest[d.destLen++] = d.dest[i]; - } - } - } +/** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ +encode.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + v = ''; + console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); } - - /* inflate an uncompressed block of data */ - function tinf_inflate_uncompressed_block(d) { - var length, invlength; - var i; - - /* unread from bitbuffer */ - while (d.bitcount > 8) { - d.sourceIndex--; - d.bitcount -= 8; + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); } - /* get length */ - length = d.source[d.sourceIndex + 1]; - length = 256 * length + d.source[d.sourceIndex]; - - /* get one's complement of length */ - invlength = d.source[d.sourceIndex + 3]; - invlength = 256 * invlength + d.source[d.sourceIndex + 2]; - - /* check length */ - if (length !== (~invlength & 0x0000ffff)) - { return TINF_DATA_ERROR; } + return b; +}; - d.sourceIndex += 4; +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + return 0; + } + return v.length; +}; - /* copy block */ - for (i = length; i; --i) - { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } +/** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; +}; - /* make sure we start next block on a byte boundary */ - d.bitcount = 0; +/** + * @constant + * @type {number} + */ +sizeOf.USHORT = constant(2); - return TINF_OK; +/** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); } - /* inflate stream from source to dest */ - function tinf_uncompress(source, dest) { - var d = new Data(source, dest); - var bfinal, btype, res; + return [(v >> 8) & 0xFF, v & 0xFF]; +}; - do { - /* read final block flag */ - bfinal = tinf_getbit(d); +/** + * @constant + * @type {number} + */ +sizeOf.SHORT = constant(2); - /* read block type (2 bits) */ - btype = tinf_read_bits(d, 2, 0); +/** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ +encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; - /* decompress block */ - switch (btype) { - case 0: - /* decompress uncompressed block */ - res = tinf_inflate_uncompressed_block(d); - break; - case 1: - /* decompress block with fixed huffman trees */ - res = tinf_inflate_block_data(d, sltree, sdtree); - break; - case 2: - /* decompress block with dynamic huffman trees */ - tinf_decode_trees(d, d.ltree, d.dtree); - res = tinf_inflate_block_data(d, d.ltree, d.dtree); - break; - default: - res = TINF_DATA_ERROR; - } +/** + * @constant + * @type {number} + */ +sizeOf.UINT24 = constant(3); - if (res !== TINF_OK) - { throw new Error('Data error'); } +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; - } while (!bfinal); +/** + * @constant + * @type {number} + */ +sizeOf.ULONG = constant(4); - if (d.destLen < d.dest.length) { - if (typeof d.dest.slice === 'function') - { return d.dest.slice(0, d.destLen); } - else - { return d.dest.subarray(0, d.destLen); } - } - - return d.dest; +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); } - /* -------------------- * - * -- initialization -- * - * -------------------- */ - - /* build fixed huffman trees */ - tinf_build_fixed_trees(sltree, sdtree); + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; - /* build extra bits and base tables */ - tinf_build_bits_base(length_bits, length_base, 4, 3); - tinf_build_bits_base(dist_bits, dist_base, 2, 1); +/** + * @constant + * @type {number} + */ +sizeOf.LONG = constant(4); - /* fix a special case */ - length_bits[28] = 0; - length_base[28] = 258; +encode.FIXED = encode.ULONG; +sizeOf.FIXED = sizeOf.ULONG; - var tinyInflate = tinf_uncompress; +encode.FWORD = encode.SHORT; +sizeOf.FWORD = sizeOf.SHORT; - // The Bounding Box object +encode.UFWORD = encode.USHORT; +sizeOf.UFWORD = sizeOf.USHORT; - function derive(v0, v1, v2, v3, t) { - return Math.pow(1 - t, 3) * v0 + - 3 * Math.pow(1 - t, 2) * t * v1 + - 3 * (1 - t) * Math.pow(t, 2) * v2 + - Math.pow(t, 3) * v3; - } - /** - * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. - * It is used to calculate the bounding box of a glyph or text path. - * - * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. - * - * @exports opentype.BoundingBox - * @class - * @constructor - */ - function BoundingBox() { - this.x1 = Number.NaN; - this.y1 = Number.NaN; - this.x2 = Number.NaN; - this.y2 = Number.NaN; - } +/** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ +encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; - /** - * Returns true if the bounding box is empty, that is, no points have been added to the box yet. - */ - BoundingBox.prototype.isEmpty = function() { - return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); - }; +/** + * @constant + * @type {number} + */ +sizeOf.LONGDATETIME = constant(8); - /** - * Add the point to the bounding box. - * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. - * @param {number} x - The X coordinate of the point. - * @param {number} y - The Y coordinate of the point. - */ - BoundingBox.prototype.addPoint = function(x, y) { - if (typeof x === 'number') { - if (isNaN(this.x1) || isNaN(this.x2)) { - this.x1 = x; - this.x2 = x; - } - if (x < this.x1) { - this.x1 = x; - } - if (x > this.x2) { - this.x2 = x; - } - } - if (typeof y === 'number') { - if (isNaN(this.y1) || isNaN(this.y2)) { - this.y1 = y; - this.y2 = y; - } - if (y < this.y1) { - this.y1 = y; - } - if (y > this.y2) { - this.y2 = y; - } - } - }; +/** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ +encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; +}; - /** - * Add a X coordinate to the bounding box. - * This extends the bounding box to include the X coordinate. - * This function is used internally inside of addBezier. - * @param {number} x - The X coordinate of the point. - */ - BoundingBox.prototype.addX = function(x) { - this.addPoint(x, null); - }; +/** + * @constant + * @type {number} + */ +sizeOf.TAG = constant(4); - /** - * Add a Y coordinate to the bounding box. - * This extends the bounding box to include the Y coordinate. - * This function is used internally inside of addBezier. - * @param {number} y - The Y coordinate of the point. - */ - BoundingBox.prototype.addY = function(y) { - this.addPoint(null, y); - }; +// CFF data types /////////////////////////////////////////////////////////// - /** - * Add a Bézier curve to the bounding box. - * This extends the bounding box to include the entire Bézier. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the first control point. - * @param {number} y1 - The Y coordinate of the first control point. - * @param {number} x2 - The X coordinate of the second control point. - * @param {number} y2 - The Y coordinate of the second control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ - BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { - // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html - // and https://github.com/icons8/svg-path-bounding-box - - var p0 = [x0, y0]; - var p1 = [x1, y1]; - var p2 = [x2, y2]; - var p3 = [x, y]; - - this.addPoint(x0, y0); - this.addPoint(x, y); - - for (var i = 0; i <= 1; i++) { - var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; - var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; - var c = 3 * p1[i] - 3 * p0[i]; - - if (a === 0) { - if (b === 0) { continue; } - var t = -c / b; - if (0 < t && t < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } - } - continue; - } +encode.Card8 = encode.BYTE; +sizeOf.Card8 = sizeOf.BYTE; - var b2ac = Math.pow(b, 2) - 4 * c * a; - if (b2ac < 0) { continue; } - var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); - if (0 < t1 && t1 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - } - var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); - if (0 < t2 && t2 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - } - } - }; +encode.Card16 = encode.USHORT; +sizeOf.Card16 = sizeOf.USHORT; - /** - * Add a quadratic curve to the bounding box. - * This extends the bounding box to include the entire quadratic curve. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the control point. - * @param {number} y1 - The Y coordinate of the control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ - BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { - var cp1x = x0 + 2 / 3 * (x1 - x0); - var cp1y = y0 + 2 / 3 * (y1 - y0); - var cp2x = cp1x + 1 / 3 * (x - x0); - var cp2y = cp1y + 1 / 3 * (y - y0); - this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); - }; +encode.OffSize = encode.BYTE; +sizeOf.OffSize = sizeOf.BYTE; - // Geometric objects +encode.SID = encode.USHORT; +sizeOf.SID = sizeOf.USHORT; - /** - * A bézier path containing a set of path commands similar to a SVG path. - * Paths can be drawn on a context using `draw`. - * @exports opentype.Path - * @class - * @constructor - */ - function Path() { - this.commands = []; - this.fill = 'black'; - this.stroke = null; - this.strokeWidth = 1; +// Convert a numeric operand or charstring number to a variable-size list of bytes. +/** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ +encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); } +}; - /** - * @param {number} x - * @param {number} y - */ - Path.prototype.moveTo = function(x, y) { - this.commands.push({ - type: 'M', - x: x, - y: y - }); - }; +/** + * @param {number} + * @returns {number} + */ +sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; +}; - /** - * @param {number} x - * @param {number} y - */ - Path.prototype.lineTo = function(x, y) { - this.commands.push({ - type: 'L', - x: x, - y: y - }); - }; +/** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ +encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; +}; - /** - * Draws cubic curve - * @function - * curveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - */ +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER16 = constant(3); - /** - * Draws cubic curve - * @function - * bezierCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - * @see curveTo - */ - Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { - this.commands.push({ - type: 'C', - x1: x1, - y1: y1, - x2: x2, - y2: y2, - x: x, - y: y - }); - }; +/** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ +encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; - /** - * Draws quadratic curve - * @function - * quadraticCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER32 = constant(5); - /** - * Draws quadratic curve - * @function - * quadTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { - this.commands.push({ - type: 'Q', - x1: x1, - y1: y1, - x: x, - y: y - }); - }; +/** + * @param {number} + * @returns {Array} + */ +encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } - /** - * Closes the path - * @function closePath - * @memberof opentype.Path.prototype - */ + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } - /** - * Close the path - * @function close - * @memberof opentype.Path.prototype - */ - Path.prototype.close = Path.prototype.closePath = function() { - this.commands.push({ - type: 'Z' - }); - }; + return out; +}; - /** - * Add the given path or list of commands to the commands of this path. - * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. - */ - Path.prototype.extend = function(pathOrCommands) { - if (pathOrCommands.commands) { - pathOrCommands = pathOrCommands.commands; - } else if (pathOrCommands instanceof BoundingBox) { - var box = pathOrCommands; - this.moveTo(box.x1, box.y1); - this.lineTo(box.x2, box.y1); - this.lineTo(box.x2, box.y2); - this.lineTo(box.x1, box.y2); - this.close(); - return; - } +/** + * @param {number} + * @returns {number} + */ +sizeOf.REAL = function(v) { + return encode.REAL(v).length; +}; - Array.prototype.push.apply(this.commands, pathOrCommands); - }; +encode.NAME = encode.CHARARRAY; +sizeOf.NAME = sizeOf.CHARARRAY; - /** - * Calculate the bounding box of the path. - * @returns {opentype.BoundingBox} - */ - Path.prototype.getBoundingBox = function() { - var box = new BoundingBox(); - - var startX = 0; - var startY = 0; - var prevX = 0; - var prevY = 0; - for (var i = 0; i < this.commands.length; i++) { - var cmd = this.commands[i]; - switch (cmd.type) { - case 'M': - box.addPoint(cmd.x, cmd.y); - startX = prevX = cmd.x; - startY = prevY = cmd.y; - break; - case 'L': - box.addPoint(cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Q': - box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'C': - box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Z': - prevX = startX; - prevY = startY; - break; - default: - throw new Error('Unexpected path command ' + cmd.type); - } - } - if (box.isEmpty()) { - box.addPoint(0, 0); - } - return box; - }; +encode.STRING = encode.CHARARRAY; +sizeOf.STRING = sizeOf.CHARARRAY; - /** - * Draw the path to a 2D context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. - */ - Path.prototype.draw = function(ctx) { - ctx.beginPath(); - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - ctx.moveTo(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - ctx.lineTo(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - ctx.closePath(); - } - } +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } - if (this.fill) { - ctx.fillStyle = this.fill; - ctx.fill(); - } + return String.fromCharCode.apply(null, codePoints); +}; - if (this.stroke) { - ctx.strokeStyle = this.stroke; - ctx.lineWidth = this.strokeWidth; - ctx.stroke(); - } - }; +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } - /** - * Convert the Path to a string of path data instructions - * See http://www.w3.org/TR/SVG/paths.html#PathData - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ - Path.prototype.toPathData = function(decimalPlaces) { - decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + return String.fromCharCode.apply(null, codePoints); +}; - function floatToString(v) { - if (Math.round(v) === v) { - return '' + Math.round(v); - } else { - return v.toFixed(decimalPlaces); - } - } +/** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ +encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } - function packValues() { - var arguments$1 = arguments; + return b; +}; - var s = ''; - for (var i = 0; i < arguments.length; i += 1) { - var v = arguments$1[i]; - if (v >= 0 && i > 0) { - s += ' '; - } +/** + * @param {string} + * @returns {number} + */ +sizeOf.UTF16 = function(v) { + return v.length * 2; +}; - s += floatToString(v); - } +// Data for converting old eight-bit Macintosh encodings to Unicode. +// This representation is optimized for decoding; encoding is slower +// and needs more memory. The assumption is that all opentype.js users +// want to open fonts, but saving a font will be comparatively rare +// so it can be more expensive. Keyed by IANA character set name. +// +// Python script for generating these strings: +// +// s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) +// print(s.encode('utf-8')) +/** + * @private + */ +var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' +}; - return s; - } - - var d = ''; - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - d += 'M' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - d += 'L' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - d += 'Z'; - } +/** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ +decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; } + } - return d; - }; + return result; +}; - /** - * Convert the path to an SVG element, as a string. - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ - Path.prototype.toSVG = function(decimalPlaces) { - var svg = '= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } } + result[i] = c; + // result.push(c); } - var check = { fail: fail, argument: argument, assert: argument }; - - // Data types used in the OpenType font file. - var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 - var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 - - /** - * @exports opentype.decode - * @class - */ - var decode = {}; - /** - * @exports opentype.encode - * @class - */ - var encode = {}; - /** - * @exports opentype.sizeOf - * @class - */ - var sizeOf = {}; + return result; +}; - // Return a function that always returns the same value. - function constant(v) { - return function() { - return v; - }; +/** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ +sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; } +}; - // OpenType data types ////////////////////////////////////////////////////// +// Helper for encode.VARDELTAS +function isByteEncodable(value) { + return value >= -128 && value <= 127; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; + } + result.push(0x80 | (runLength - 1)); + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } - /** - * Convert an 8-bit unsigned integer to a list of 1 byte. - * @param {number} - * @returns {Array} - */ - encode.BYTE = function(v) { - check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); - return [v]; - }; - /** - * @constant - * @type {number} - */ - sizeOf.BYTE = constant(1); + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } - /** - * Convert a 8-bit signed integer to a list of 1 byte. - * @param {string} - * @returns {Array} - */ - encode.CHAR = function(v) { - return [v.charCodeAt(0)]; - }; + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; +} - /** - * @constant - * @type {number} - */ - sizeOf.CHAR = constant(1); +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; - /** - * Convert an ASCII string to a list of bytes. - * @param {string} - * @returns {Array} - */ - encode.CHARARRAY = function(v) { - if (typeof v === 'undefined') { - v = ''; - console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); - } - var b = []; - for (var i = 0; i < v.length; i += 1) { - b[i] = v.charCodeAt(i); + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; } - return b; - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.CHARARRAY = function(v) { - if (typeof v === 'undefined') { - return 0; + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; } - return v.length; - }; - - /** - * Convert a 16-bit unsigned integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ - encode.USHORT = function(v) { - return [(v >> 8) & 0xFF, v & 0xFF]; - }; - /** - * @constant - * @type {number} - */ - sizeOf.USHORT = constant(2); + ++pos; + ++runLength; + } + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; +} - /** - * Convert a 16-bit signed integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ - encode.SHORT = function(v) { - // Two's complement - if (v >= LIMIT16) { - v = -(2 * LIMIT16 - v); +/** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ +encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); } + } + return result; +}; - return [(v >> 8) & 0xFF, v & 0xFF]; - }; +// Convert a list of values to a CFF INDEX structure. +// The values should be objects containing name / type / value. +/** + * @param {Array} l + * @returns {Array} + */ +encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); +}; - /** - * @constant - * @type {number} - */ - sizeOf.SHORT = constant(2); +/** + * @param {Array} + * @returns {number} + */ +sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; +}; - /** - * Convert a 24-bit unsigned integer to a list of 3 bytes. - * @param {number} - * @returns {Array} - */ - encode.UINT24 = function(v) { - return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; +/** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ +encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; - /** - * @constant - * @type {number} - */ - sizeOf.UINT24 = constant(3); + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } - /** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ - encode.ULONG = function(v) { - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; + return d; +}; - /** - * @constant - * @type {number} - */ - sizeOf.ULONG = constant(4); +/** + * @param {Object} + * @returns {number} + */ +sizeOf.DICT = function(m) { + return encode.DICT(m).length; +}; - /** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ - encode.LONG = function(v) { - // Two's complement - if (v >= LIMIT32) { - v = -(2 * LIMIT32 - v); - } +/** + * @param {number} + * @returns {Array} + */ +encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } +}; - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; +/** + * @param {Array} v + * @param {string} + * @returns {Array} + */ +encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } - /** - * @constant - * @type {number} - */ - sizeOf.LONG = constant(4); + return d; +}; - encode.FIXED = encode.ULONG; - sizeOf.FIXED = sizeOf.ULONG; +encode.OP = encode.BYTE; +sizeOf.OP = sizeOf.BYTE; - encode.FWORD = encode.SHORT; - sizeOf.FWORD = sizeOf.SHORT; +// memoize charstring encoding using WeakMap if available +var wmm = typeof WeakMap === 'function' && new WeakMap(); - encode.UFWORD = encode.USHORT; - sizeOf.UFWORD = sizeOf.USHORT; +/** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ +encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; + } + } - /** - * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. - * @param {number} - * @returns {Array} - */ - encode.LONGDATETIME = function(v) { - return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; + var d = []; + var length = ops.length; - /** - * @constant - * @type {number} - */ - sizeOf.LONGDATETIME = constant(8); + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } - /** - * Convert a 4-char tag to a list of 4 bytes. - * @param {string} - * @returns {Array} - */ - encode.TAG = function(v) { - check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); - return [v.charCodeAt(0), - v.charCodeAt(1), - v.charCodeAt(2), - v.charCodeAt(3)]; - }; + if (wmm) { + wmm.set(ops, d); + } - /** - * @constant - * @type {number} - */ - sizeOf.TAG = constant(4); + return d; +}; - // CFF data types /////////////////////////////////////////////////////////// +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; +}; - encode.Card8 = encode.BYTE; - sizeOf.Card8 = sizeOf.BYTE; +// Utility functions //////////////////////////////////////////////////////// - encode.Card16 = encode.USHORT; - sizeOf.Card16 = sizeOf.USHORT; +/** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ +encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); +}; - encode.OffSize = encode.BYTE; - sizeOf.OffSize = sizeOf.BYTE; +/** + * @param {Object} + * @returns {number} + */ +sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); +}; - encode.SID = encode.USHORT; - sizeOf.SID = sizeOf.USHORT; - - // Convert a numeric operand or charstring number to a variable-size list of bytes. - /** - * Convert a numeric operand or charstring number to a variable-size list of bytes. - * @param {number} - * @returns {Array} - */ - encode.NUMBER = function(v) { - if (v >= -107 && v <= 107) { - return [v + 139]; - } else if (v >= 108 && v <= 1131) { - v = v - 108; - return [(v >> 8) + 247, v & 0xFF]; - } else if (v >= -1131 && v <= -108) { - v = -v - 108; - return [(v >> 8) + 251, v & 0xFF]; - } else if (v >= -32768 && v <= 32767) { - return encode.NUMBER16(v); +/** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ +encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); } else { - return encode.NUMBER32(v); + d = d.concat(bytes); } - }; + } - /** - * @param {number} - * @returns {number} - */ - sizeOf.NUMBER = function(v) { - return encode.NUMBER(v).length; - }; + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } - /** - * Convert a signed number between -32768 and +32767 to a three-byte value. - * This ensures we always use three bytes, but is not the most compact format. - * @param {number} - * @returns {Array} - */ - encode.NUMBER16 = function(v) { - return [28, (v >> 8) & 0xFF, v & 0xFF]; - }; + return d; +}; - /** - * @constant - * @type {number} - */ - sizeOf.NUMBER16 = constant(3); +/** + * @param {opentype.Table} + * @returns {number} + */ +sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; - /** - * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. - * This is useful if you want to be sure you always use four bytes, - * at the expense of wasting a few bytes for smaller numbers. - * @param {number} - * @returns {Array} - */ - encode.NUMBER32 = function(v) { - return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } - /** - * @constant - * @type {number} - */ - sizeOf.NUMBER32 = constant(5); + numBytes += sizeOfFunction(value); - /** - * @param {number} - * @returns {Array} - */ - encode.REAL = function(v) { - var value = v.toString(); - - // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) - // This code converts it back to a number without the epsilon. - var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); - if (m) { - var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); - value = (Math.round(v * epsilon) / epsilon).toString(); - } - - var nibbles = ''; - for (var i = 0, ii = value.length; i < ii; i += 1) { - var c = value[i]; - if (c === 'e') { - nibbles += value[++i] === '-' ? 'c' : 'b'; - } else if (c === '.') { - nibbles += 'a'; - } else if (c === '-') { - nibbles += 'e'; - } else { - nibbles += c; - } + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; } + } - nibbles += (nibbles.length & 1) ? 'f' : 'ff'; - var out = [30]; - for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { - out.push(parseInt(nibbles.substr(i$1, 2), 16)); - } + return numBytes; +}; - return out; - }; +encode.RECORD = encode.TABLE; +sizeOf.RECORD = sizeOf.TABLE; - /** - * @param {number} - * @returns {number} - */ - sizeOf.REAL = function(v) { - return encode.REAL(v).length; - }; +// Merge in a list of bytes. +encode.LITERAL = function(v) { + return v; +}; - encode.NAME = encode.CHARARRAY; - sizeOf.NAME = sizeOf.CHARARRAY; +sizeOf.LITERAL = function(v) { + return v.length; +}; - encode.STRING = encode.CHARARRAY; - sizeOf.STRING = sizeOf.CHARARRAY; +// Table metadata - /** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ - decode.UTF8 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes; - for (var j = 0; j < numChars; j++, offset += 1) { - codePoints[j] = data.getUint8(offset); +/** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ +function Table(tableName, fields, options) { + // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, + // as this will result in wrong encoding order of the coverage data on serialization to bytes. + // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. + if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; } + } - return String.fromCharCode.apply(null, codePoints); - }; - - /** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ - decode.UTF16 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes / 2; - for (var j = 0; j < numChars; j++, offset += 2) { - codePoints[j] = data.getUint16(offset); + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } } + } +} - return String.fromCharCode.apply(null, codePoints); - }; +/** + * Encodes the table and returns an array of bytes + * @return {Array} + */ +Table.prototype.encode = function() { + return encode.TABLE(this); +}; - /** - * Convert a JavaScript string to UTF16-BE. - * @param {string} - * @returns {Array} - */ - encode.UTF16 = function(v) { - var b = []; - for (var i = 0; i < v.length; i += 1) { - var codepoint = v.charCodeAt(i); - b[b.length] = (codepoint >> 8) & 0xFF; - b[b.length] = codepoint & 0xFF; - } +/** + * Get the size of the table. + * @return {number} + */ +Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); +}; - return b; - }; +/** + * @private + */ +function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; +} - /** - * @param {string} - * @returns {number} - */ - sizeOf.UTF16 = function(v) { - return v.length * 2; - }; +/** + * @private + */ +function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; + } + return fields; +} - // Data for converting old eight-bit Macintosh encodings to Unicode. - // This representation is optimized for decoding; encoding is slower - // and needs more memory. The assumption is that all opentype.js users - // want to open fonts, but saving a font will be comparatively rare - // so it can be more expensive. Keyed by IANA character set name. - // - // Python script for generating these strings: - // - // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) - // print(s.encode('utf-8')) - /** - * @private - */ - var eightBitMacEncodings = { - 'x-mac-croatian': // Python: 'mac_croatian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + - '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', - 'x-mac-cyrillic': // Python: 'mac_cyrillic' - 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + - 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', - 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + - 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', - 'x-mac-greek': // Python: 'mac_greek' - 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + - 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', - 'x-mac-icelandic': // Python: 'mac_iceland' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT - 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + - 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', - 'x-mac-ce': // Python: 'mac_latin2' - 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + - 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', - macintosh: // Python: 'mac_roman' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-romanian': // Python: 'mac_romanian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-turkish': // Python: 'mac_turkish' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' - }; +/** + * @private + */ +function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; +} - /** - * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript - * string, or 'undefined' if the encoding is unsupported. For example, we do - * not support Chinese, Japanese or Korean because these would need large - * mapping tables. - * @param {DataView} dataView - * @param {number} offset - * @param {number} dataLength - * @param {string} encoding - * @returns {string} - */ - decode.MACSTRING = function(dataView, offset, dataLength, encoding) { - var table = eightBitMacEncodings[encoding]; - if (table === undefined) { - return undefined; - } - - var result = ''; - for (var i = 0; i < dataLength; i++) { - var c = dataView.getUint8(offset + i); - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c <= 0x7F) { - result += String.fromCharCode(c); - } else { - result += table[c & 0x7F]; - } - } +// Common Layout Tables - return result; - }; +/** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else if (coverageTable.format === 2) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 2}] + .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { + return [ + {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, + {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; + })) + ); + } else { + check.assert(false, 'Coverage format must be 1 or 2.'); + } +} +Coverage.prototype = Object.create(Table.prototype); +Coverage.prototype.constructor = Coverage; + +function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); +} +ScriptList.prototype = Object.create(Table.prototype); +ScriptList.prototype.constructor = ScriptList; - // Helper function for encode.MACSTRING. Returns a dictionary for mapping - // Unicode character codes to their 8-bit MacOS equivalent. This table - // is not exactly a super cheap data structure, but we do not care because - // encoding Macintosh strings is only rarely needed in typical applications. - var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); - var macEncodingCacheKeys; - var getMacEncodingTable = function (encoding) { - // Since we use encoding as a cache key for WeakMap, it has to be - // a String object and not a literal. And at least on NodeJS 2.10.1, - // WeakMap requires that the same String instance is passed for cache hits. - if (!macEncodingCacheKeys) { - macEncodingCacheKeys = {}; - for (var e in eightBitMacEncodings) { - /*jshint -W053 */ // Suppress "Do not use String as a constructor." - macEncodingCacheKeys[e] = new String(e); - } - } +/** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); +} +FeatureList.prototype = Object.create(Table.prototype); +FeatureList.prototype.constructor = FeatureList; - var cacheKey = macEncodingCacheKeys[encoding]; - if (cacheKey === undefined) { - return undefined; - } +/** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ +function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); +} +LookupList.prototype = Object.create(Table.prototype); +LookupList.prototype.constructor = LookupList; + +// Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) +// Don't use offsets inside Records (probable bug), only in Tables. +var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, +}; - // We can't do "if (cache.has(key)) {return cache.get(key)}" here: - // since garbage collection may run at any time, it could also kick in - // between the calls to cache.has() and cache.get(). In that case, - // we would return 'undefined' even though we do support the encoding. - if (macEncodingTableCache) { - var cachedTable = macEncodingTableCache.get(cacheKey); - if (cachedTable !== undefined) { - return cachedTable; - } - } +// Parsing utility functions + +// Retrieve an unsigned byte from the DataView. +function getByte(dataView, offset) { + return dataView.getUint8(offset); +} + +// Retrieve an unsigned 16-bit short from the DataView. +// The value is stored in big endian. +function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); +} + +// Retrieve a signed 16-bit short from the DataView. +// The value is stored in big endian. +function getShort(dataView, offset) { + return dataView.getInt16(offset, false); +} + +// Retrieve an unsigned 32-bit long from the DataView. +// The value is stored in big endian. +function getULong(dataView, offset) { + return dataView.getUint32(offset, false); +} + +// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. +// The value is stored in big endian. +function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; +} + +// Retrieve a 4-character tag from the DataView. +// Tags are used to identify tables. +function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; +} + +// Retrieve an offset from the DataView. +// Offsets are 1 to 4 bytes in length, depending on the offSize argument. +function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; +} + +// Retrieve a number of bytes from start offset to the end offset from the DataView. +function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; +} + +// Convert the list of bytes to a string. +function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; +} + +var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 +}; - var decodingTable = eightBitMacEncodings[encoding]; - if (decodingTable === undefined) { - return undefined; - } +// A stateful parser that changes the offset whenever a value is retrieved. +// The data is a DataView. +function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; +} + +Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; - var encodingTable = {}; - for (var i = 0; i < decodingTable.length; i++) { - encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; - } +Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; - if (macEncodingTableCache) { - macEncodingTableCache.set(cacheKey, encodingTable); - } +Parser.prototype.parseCard8 = Parser.prototype.parseByte; - return encodingTable; - }; +Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; - /** - * Encodes an old-style Macintosh string. Returns a byte array upon success. - * If the requested encoding is unsupported, or if the input string contains - * a character that cannot be expressed in the encoding, the function returns - * 'undefined'. - * @param {string} str - * @param {string} encoding - * @returns {Array} - */ - encode.MACSTRING = function(str, encoding) { - var table = getMacEncodingTable(encoding); - if (table === undefined) { - return undefined; - } - - var result = []; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c >= 0x80) { - c = table[c]; - if (c === undefined) { - // str contains a Unicode character that cannot be encoded - // in the requested encoding. - return undefined; - } - } - result[i] = c; - // result.push(c); - } +Parser.prototype.parseCard16 = Parser.prototype.parseUShort; +Parser.prototype.parseSID = Parser.prototype.parseUShort; +Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; - return result; - }; +Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; - /** - * @param {string} str - * @param {string} encoding - * @returns {number} - */ - sizeOf.MACSTRING = function(str, encoding) { - var b = encode.MACSTRING(str, encoding); - if (b !== undefined) { - return b.length; - } else { - return 0; - } - }; +Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; +}; - // Helper for encode.VARDELTAS - function isByteEncodable(value) { - return value >= -128 && value <= 127; +Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + +Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); } - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsZeroes(deltas, pos, result) { - var runLength = 0; - var numDeltas = deltas.length; - while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { - ++pos; - ++runLength; - } - result.push(0x80 | (runLength - 1)); - return pos; + return string; +}; + +Parser.prototype.parseTag = function() { + return this.parseString(4); +}; + +// LONGDATETIME is a 64-bit integer. +// JavaScript and unix timestamps traditionally use 32 bits, so we +// only take the last 32 bits. +// + Since until 2038 those bits will be filled by zeros we can ignore them. +Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; +}; + +Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; +}; + +Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; } - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsBytes(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; - if (!isByteEncodable(value)) { - break; - } + this.relativeOffset += typeOffsets[type] * amount; +}; - // Within a byte-encoded run of deltas, a single zero is best - // stored literally as 0x00 value. However, if we have two or - // more zeroes in a sequence, it is better to start a new run. - // Fore example, the sequence of deltas [15, 15, 0, 15, 15] - // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero - // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) - // when starting a new run. - if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { - break; - } +///// Parsing lists and records /////////////////////////////// - ++pos; - ++runLength; - } - result.push(runLength - 1); - for (var i = offset; i < pos; ++i) { - result.push((deltas[i] + 256) & 0xff); - } - return pos; +// Parse a list of 32 bit unsigned integers. +Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; } - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsWords(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; + this.relativeOffset += count * 4; + return offsets; +}; - // Within a word-encoded run of deltas, it is easiest to start - // a new run (with a different encoding) whenever we encounter - // a zero value. For example, the sequence [0x6666, 0, 0x7777] - // needs 7 bytes when storing the zero inside the current run - // (42 66 66 00 00 77 77), and equally 7 bytes when starting a - // new run (40 66 66 80 40 77 77). - if (value === 0) { - break; - } +// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream +// or provided as an argument. +Parser.prototype.parseOffset16List = +Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; +}; - // Within a word-encoded run of deltas, a single value in the - // range (-128..127) should be encoded within the current run - // because it is more compact. For example, the sequence - // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value - // literally (42 66 66 00 02 77 77), but 8 bytes when starting - // a new run (40 66 66 00 02 40 77 77). - if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { - break; - } +// Parses a list of 16 bit signed integers. +Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } - ++pos; - ++runLength; - } - result.push(0x40 | (runLength - 1)); - for (var i = offset; i < pos; ++i) { - var val = deltas[i]; - result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); - } - return pos; + this.relativeOffset += count * 2; + return list; +}; + +// Parses a list of bytes. +Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); } - /** - * Encode a list of variation adjustment deltas. - * - * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. - * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted - * when generating instances of variation fonts. - * - * @see https://www.microsoft.com/typography/otspec/gvar.htm - * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html - * @param {Array} - * @return {Array} - */ - encode.VARDELTAS = function(deltas) { - var pos = 0; - var result = []; - while (pos < deltas.length) { - var value = deltas[pos]; - if (value === 0) { - pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); - } else if (value >= -128 && value <= 127) { - pos = encodeVarDeltaRunAsBytes(deltas, pos, result); - } else { - pos = encodeVarDeltaRunAsWords(deltas, pos, result); - } - } - return result; - }; + this.relativeOffset += count; + return list; +}; - // Convert a list of values to a CFF INDEX structure. - // The values should be objects containing name / type / value. - /** - * @param {Array} l - * @returns {Array} - */ - encode.INDEX = function(l) { - //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, - // i, v; - // Because we have to know which data type to use to encode the offsets, - // we have to go through the values twice: once to encode the data and - // calculate the offsets, then again to encode the offsets using the fitting data type. - var offset = 1; // First offset is always 1. - var offsets = [offset]; - var data = []; - for (var i = 0; i < l.length; i += 1) { - var v = encode.OBJECT(l[i]); - Array.prototype.push.apply(data, v); - offset += v.length; - offsets.push(offset); - } - - if (data.length === 0) { - return [0, 0]; - } - - var encodedOffsets = []; - var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; - var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; - for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { - var encodedOffset = offsetEncoder(offsets[i$1]); - Array.prototype.push.apply(encodedOffsets, encodedOffset); - } - - return Array.prototype.concat(encode.Card16(l.length), - encode.OffSize(offSize), - encodedOffsets, - data); - }; +/** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ +Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; - /** - * @param {Array} - * @returns {number} - */ - sizeOf.INDEX = function(v) { - return encode.INDEX(v).length; - }; +Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; - /** - * Convert an object to a CFF DICT structure. - * The keys should be numeric. - * The values should be objects containing name / type / value. - * @param {Object} m - * @returns {Array} - */ - encode.DICT = function(m) { - var d = []; - var keys = Object.keys(m); - var length = keys.length; +/** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ +Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; - for (var i = 0; i < length; i += 1) { - // Object.keys() return string keys, but our keys are always numeric. - var k = parseInt(keys[i], 0); - var v = m[k]; - // Value comes before the key. - d = d.concat(encode.OPERAND(v.value, v.type)); - d = d.concat(encode.OPERATOR(k)); +Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); } + records[i] = rec; + } + return records; +}; - return d; - }; +// Parse a data structure into an object +// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } +Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } +}; - /** - * @param {Object} - * @returns {number} - */ - sizeOf.DICT = function(m) { - return encode.DICT(m).length; - }; +/** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ +Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; + + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } + + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } + + return valueRecord; +}; - /** - * @param {number} - * @returns {Array} - */ - encode.OPERATOR = function(v) { - if (v < 1200) { - return [v]; - } else { - return [12, v - 1200]; - } - }; +/** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ +Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; +}; - /** - * @param {Array} v - * @param {string} - * @returns {Array} - */ - encode.OPERAND = function(v, type) { - var d = []; - if (Array.isArray(type)) { - for (var i = 0; i < type.length; i += 1) { - check.argument(v.length === type.length, 'Not enough arguments given for type' + type); - d = d.concat(encode.OPERAND(v[i], type[i])); - } - } else { - if (type === 'SID') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'offset') { - // We make it easy for ourselves and always encode offsets as - // 4 bytes. This makes offset calculation for the top dict easier. - d = d.concat(encode.NUMBER32(v)); - } else if (type === 'number') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'real') { - d = d.concat(encode.REAL(v)); - } else { - throw new Error('Unknown operand type ' + type); - // FIXME Add support for booleans - } - } +Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; - return d; - }; +Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; - encode.OP = encode.BYTE; - sizeOf.OP = sizeOf.BYTE; +/** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ +Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list; +}; - // memoize charstring encoding using WeakMap if available - var wmm = typeof WeakMap === 'function' && new WeakMap(); +///// Complex tables parsing ////////////////////////////////// - /** - * Convert a list of CharString operations to bytes. - * @param {Array} - * @returns {Array} - */ - encode.CHARSTRING = function(ops) { - // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". - if (wmm) { - var cachedValue = wmm.get(ops); - if (cachedValue !== undefined) { - return cachedValue; - } +// Parse a coverage table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +// parser.offset must point to the start of the table containing the coverage. +Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; } + return { + format: 2, + ranges: ranges + }; + } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); +}; - var d = []; - var length = ops.length; +// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); +}; - for (var i = 0; i < length; i += 1) { - var op = ops[i]; - d = d.concat(encode[op.type](op.value)); - } +///// Static methods /////////////////////////////////// +// These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. - if (wmm) { - wmm.set(ops, d); - } +Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; +}; - return d; +Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); }; +}; - /** - * @param {Array} - * @returns {number} - */ - sizeOf.CHARSTRING = function(ops) { - return encode.CHARSTRING(ops).length; +Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); }; +}; - // Utility functions //////////////////////////////////////////////////////// +Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; +}; - /** - * Convert an object containing name / type / value to bytes. - * @param {Object} - * @returns {Array} - */ - encode.OBJECT = function(v) { - var encodingFunction = encode[v.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); - return encodingFunction(v.value); +Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); }; +}; - /** - * @param {Object} - * @returns {number} - */ - sizeOf.OBJECT = function(v) { - var sizeOfFunction = sizeOf[v.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); - return sizeOfFunction(v.value); +Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); }; +}; - /** - * Convert a table object to bytes. - * A table contains a list of fields containing the metadata (name, type and default value). - * The table itself has the field values set as attributes. - * @param {opentype.Table} - * @returns {Array} - */ - encode.TABLE = function(table) { - var d = []; - var length = table.fields.length; - var subtables = []; - var subtableOffsets = []; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var encodingFunction = encode[field.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } +Parser.tag = Parser.prototype.parseTag; +Parser.byte = Parser.prototype.parseByte; +Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; +Parser.uShortList = Parser.prototype.parseUShortList; +Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; +Parser.uLongList = Parser.prototype.parseULongList; +Parser.struct = Parser.prototype.parseStruct; +Parser.coverage = Parser.prototype.parseCoverage; +Parser.classDef = Parser.prototype.parseClassDef; + +///// Script, Feature, Lookup lists /////////////////////////////////////////////// +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm + +var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList +}; + +Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; +}; + +Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; +}; + +Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; +}; + +Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; +}; - var bytes = encodingFunction(value); +var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, +}; - if (field.type === 'TABLE') { - subtableOffsets.push(d.length); - d = d.concat([0, 0]); - subtables.push(bytes); +// The `cmap` table stores the mappings from characters to glyphs. + +function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); + + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; + + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } +} + +function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } } else { - d = d.concat(bytes); + glyphIndex = (c + idDelta) & 0xFFFF; } + + cmap.glyphIndexMap[c] = glyphIndex; } + } +} + +// Parse the `cmap` table. This table stores the mappings from characters to glyphs. +// There are many available formats, but we only support the Windows format 4 and 12. +// This function returns a `CmapEncoding` object or null if no supported format could be found. +function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); - for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { - var o = subtableOffsets[i$1]; - var offset = d.length; - check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); - d[o] = offset >> 8; - d[o + 1] = offset & 0xff; - d = d.concat(subtables[i$1]); + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; } + } - return d; - }; + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } - /** - * @param {opentype.Table} - * @returns {number} - */ - sizeOf.TABLE = function(table) { - var numBytes = 0; - var length = table.fields.length; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var sizeOfFunction = sizeOf[field.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); - numBytes += sizeOfFunction(value); + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); + } - // Subtables take 2 more bytes for offsets. - if (field.type === 'TABLE') { - numBytes += 2; - } + return cmap; +} + +function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); +} + +function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); +} + +// Make cmap table, format 4 by default, 12 if needed only +function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; + + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; } + } - return numBytes; - }; + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, - encode.RECORD = encode.TABLE; - sizeOf.RECORD = sizeOf.TABLE; + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; - // Merge in a list of bytes. - encode.LITERAL = function(v) { - return v; - }; + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } - sizeOf.LITERAL = function(v) { - return v.length; - }; + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); - // Table metadata + var t = new table.Table('cmap', cmapTable); - /** - * @exports opentype.Table - * @class - * @param {string} tableName - * @param {Array} fields - * @param {Object} options - * @constructor - */ - function Table(tableName, fields, options) { - // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, - // as this will result in wrong encoding order of the coverage data on serialization to bytes. - // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. - if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { - for (var i = 0; i < fields.length; i += 1) { - var field = fields[i]; - this[field.name] = field.value; + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount = t.segments.length; + var segCountToRemove = 0; + + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; } - this.tableName = tableName; - this.fields = fields; - if (options) { - var optionKeys = Object.keys(options); - for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { - var k = optionKeys[i$1]; - var v = options[k]; - if (this[k] !== undefined) { - this[k] = v; + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } + + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; +} + +var cmap = { parse: parseCmapTable, make: makeCmapTable }; + +// Glyph encoding + +var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + +var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + +var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + +var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + +/** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ +function DefaultEncoding(font) { + this.font = font; +} + +DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; } } } } + return null; +}; - /** - * Encodes the table and returns an array of bytes - * @return {Array} - */ - Table.prototype.encode = function() { - return encode.TABLE(this); - }; +/** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ +function CmapEncoding(cmap) { + this.cmap = cmap; +} - /** - * Get the size of the table. - * @return {number} - */ - Table.prototype.sizeOf = function() { - return sizeOf.TABLE(this); - }; +/** + * @param {string} c - the character + * @return {number} The glyph index. + */ +CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; +}; - /** - * @private - */ - function ushortList(itemName, list, count) { - if (count === undefined) { - count = list.length; - } - var fields = new Array(list.length + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < list.length; i++) { - fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; - } - return fields; +/** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ +function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; +} + +/** + * @param {string} s - The character + * @return {number} The index. + */ +CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); +}; + +/** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ +function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } + + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; } +} - /** - * @private - */ - function tableList(itemName, records, itemCallback) { - var count = records.length; - var fields = new Array(count + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; - } - return fields; +/** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ +GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); +}; + +/** + * @param {number} gid + * @return {string} + */ +GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; +}; + +function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); } - /** - * @private - */ - function recordList(itemName, records, itemCallback) { - var count = records.length; - var fields = []; - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields = fields.concat(itemCallback(records[i], i)); + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); } - return fields; } +} - // Common Layout Tables +function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; - /** - * @exports opentype.Coverage - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ - function Coverage(coverageTable) { - if (coverageTable.format === 1) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 1}] - .concat(ushortList('glyph', coverageTable.glyphs)) - ); - } else if (coverageTable.format === 2) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 2}] - .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { - return [ - {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, - {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, - {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; - })) - ); + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; } else { - check.assert(false, 'Coverage format must be 1 or 2.'); + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); } } - Coverage.prototype = Object.create(Table.prototype); - Coverage.prototype.constructor = Coverage; +} - function ScriptList(scriptListTable) { - Table.call(this, 'scriptListTable', - recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { - var script = scriptRecord.script; - var defaultLangSys = script.defaultLangSys; - check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); - return [ - {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, - {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ - {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] - .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} - ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { - var langSys = langSysRecord.langSys; - return [ - {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, - {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} - ].concat(ushortList('featureIndex', langSys.featureIndexes)))} - ]; - })))} - ]; - }) - ); +/** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ +function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); } - ScriptList.prototype = Object.create(Table.prototype); - ScriptList.prototype.constructor = ScriptList; +} - /** - * @exports opentype.FeatureList - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ - function FeatureList(featureListTable) { - Table.call(this, 'featureListTable', - recordList('featureRecord', featureListTable, function(featureRecord, i) { - var feature = featureRecord.feature; - return [ - {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, - {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ - {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} - ]; - }) - ); - } - FeatureList.prototype = Object.create(Table.prototype); - FeatureList.prototype.constructor = FeatureList; +// Drawing utility functions. - /** - * @exports opentype.LookupList - * @class - * @param {opentype.Table} - * @param {Object} - * @constructor - * @extends opentype.Table - */ - function LookupList(lookupListTable, subtableMakers) { - Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { - var subtableCallback = subtableMakers[lookupTable.lookupType]; - check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); - return new Table('lookupTable', [ - {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, - {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} - ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); - })); - } - LookupList.prototype = Object.create(Table.prototype); - LookupList.prototype.constructor = LookupList; - - // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) - // Don't use offsets inside Records (probable bug), only in Tables. - var table = { - Table: Table, - Record: Table, - Coverage: Coverage, - ScriptList: ScriptList, - FeatureList: FeatureList, - LookupList: LookupList, - ushortList: ushortList, - tableList: tableList, - recordList: recordList, +// Draw a line on the given context from point `x1,y1` to point `x2,y2`. +function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +} + +var draw = { line: line }; + +// The Glyph object +// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + +function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } }; +} +/** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ + +// A Glyph is an individual mark that often corresponds to a character. +// Some glyphs, such as ligatures, are a combination of many characters. +// Glyphs are the basic building blocks of a font. +// +// The `Glyph` class contains utility methods for drawing the path and its points. +/** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ +function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); +} + +/** + * @param {GlyphOptions} + */ +Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; - // Parsing utility functions + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; - // Retrieve an unsigned byte from the DataView. - function getByte(dataView, offset) { - return dataView.getUint8(offset); + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; } - // Retrieve an unsigned 16-bit short from the DataView. - // The value is stored in big endian. - function getUShort(dataView, offset) { - return dataView.getUint16(offset, false); + if ('yMin' in options) { + this.yMin = options.yMin; } - // Retrieve a signed 16-bit short from the DataView. - // The value is stored in big endian. - function getShort(dataView, offset) { - return dataView.getInt16(offset, false); + if ('xMax' in options) { + this.xMax = options.xMax; } - // Retrieve an unsigned 32-bit long from the DataView. - // The value is stored in big endian. - function getULong(dataView, offset) { - return dataView.getUint32(offset, false); + if ('yMax' in options) { + this.yMax = options.yMax; } - // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. - // The value is stored in big endian. - function getFixed(dataView, offset) { - var decimal = dataView.getInt16(offset, false); - var fraction = dataView.getUint16(offset + 2, false); - return decimal + fraction / 65535; + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; } - // Retrieve a 4-character tag from the DataView. - // Tags are used to identify tables. - function getTag(dataView, offset) { - var tag = ''; - for (var i = offset; i < offset + 4; i += 1) { - tag += String.fromCharCode(dataView.getInt8(i)); - } + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); +}; - return tag; +/** + * @param {number} + */ +Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; } - // Retrieve an offset from the DataView. - // Offsets are 1 to 4 bytes in length, depending on the offSize argument. - function getOffset(dataView, offset, offSize) { - var v = 0; - for (var i = 0; i < offSize; i += 1) { - v <<= 8; - v += dataView.getUint8(offset + i); + this.unicodes.push(unicode); +}; + +/** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ +Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); +}; + +/** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ +Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); } + } - return v; + return p; +}; + +/** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ +Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; } - // Retrieve a number of bytes from start offset to the end offset from the DataView. - function getBytes(dataView, startOffset, endOffset) { - var bytes = []; - for (var i = startOffset; i < endOffset; i += 1) { - bytes.push(dataView.getUint8(i)); + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; } - - return bytes; } - // Convert the list of bytes to a string. - function bytesToString(bytes) { - var s = ''; - for (var i = 0; i < bytes.length; i += 1) { - s += String.fromCharCode(bytes[i]); + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +}; + +/** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ +Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); } - return s; + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } } - var typeOffsets = { - byte: 1, - uShort: 2, - short: 2, - uLong: 4, - fixed: 4, - longDateTime: 8, - tag: 4 + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing }; - // A stateful parser that changes the offset whenever a value is retrieved. - // The data is a DataView. - function Parser(data, offset) { - this.data = data; - this.offset = offset; - this.relativeOffset = 0; + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; } - Parser.prototype.parseByte = function() { - var v = this.data.getUint8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; - }; - - Parser.prototype.parseChar = function() { - var v = this.data.getInt8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; - }; + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } - Parser.prototype.parseCard8 = Parser.prototype.parseByte; + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } - Parser.prototype.parseUShort = function() { - var v = this.data.getUint16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; - }; + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } - Parser.prototype.parseCard16 = Parser.prototype.parseUShort; - Parser.prototype.parseSID = Parser.prototype.parseUShort; - Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; +}; - Parser.prototype.parseShort = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; - }; +/** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ +Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); +}; - Parser.prototype.parseF2Dot14 = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; - this.relativeOffset += 2; - return v; - }; +/** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } - Parser.prototype.parseULong = function() { - var v = getULong(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; - }; + ctx.closePath(); + ctx.fill(); + } - Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; - Parser.prototype.parseFixed = function() { - var v = getFixed(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; - }; + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } - Parser.prototype.parseString = function(length) { - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - var string = ''; - this.relativeOffset += length; - for (var i = 0; i < length; i++) { - string += String.fromCharCode(dataView.getUint8(offset + i)); + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); } - return string; - }; + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } - Parser.prototype.parseTag = function() { - return this.parseString(4); - }; + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); +}; - // LONGDATETIME is a 64-bit integer. - // JavaScript and unix timestamps traditionally use 32 bits, so we - // only take the last 32 bits. - // + Since until 2038 those bits will be filled by zeros we can ignore them. - Parser.prototype.parseLongDateTime = function() { - var v = getULong(this.data, this.offset + this.relativeOffset + 4); - // Subtract seconds between 01/01/1904 and 01/01/1970 - // to convert Apple Mac timestamp to Standard Unix timestamp - v -= 2082844800; - this.relativeOffset += 8; - return v; - }; +/** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); +}; - Parser.prototype.parseVersion = function(minorBase) { - var major = getUShort(this.data, this.offset + this.relativeOffset); +// The GlyphSet object - // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 - // Default returns the correct number if minor = 0xN000 where N is 0-9 - // Set minorBase to 1 for tables that use minor = N where N is 0-9 - var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); - this.relativeOffset += 4; - if (minorBase === undefined) { minorBase = 0x1000; } - return major + minor / minorBase / 10; - }; +// Define a property on the glyph that depends on the path being loaded. +function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); +} - Parser.prototype.skip = function(type, amount) { - if (amount === undefined) { - amount = 1; +/** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ +function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; } + } - this.relativeOffset += typeOffsets[type] * amount; - }; - - ///// Parsing lists and records /////////////////////////////// + this.length = (glyphs && glyphs.length) || 0; +} - // Parse a list of 32 bit unsigned integers. - Parser.prototype.parseULongList = function(count) { - if (count === undefined) { count = this.parseULong(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint32(offset); - offset += 4; +/** + * @param {number} index + * @return {opentype.Glyph} + */ +GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); } - this.relativeOffset += count * 4; - return offsets; - }; + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; - // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream - // or provided as an argument. - Parser.prototype.parseOffset16List = - Parser.prototype.parseUShortList = function(count) { - if (count === undefined) { count = this.parseUShort(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint16(offset); - offset += 2; + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } } - this.relativeOffset += count * 2; - return offsets; - }; + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; + } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } - // Parses a list of 16 bit signed integers. - Parser.prototype.parseShortList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getInt16(offset); - offset += 2; + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); } + } - this.relativeOffset += count * 2; - return list; - }; + return this.glyphs[index]; +}; - // Parses a list of bytes. - Parser.prototype.parseByteList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getUint8(offset++); - } +/** + * @param {number} index + * @param {Object} + */ +GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; +}; - this.relativeOffset += count; - return list; - }; +/** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ +function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); +} - /** - * Parse a list of items. - * Record count is optional, if omitted it is read from the stream. - * itemCallback is one of the Parser methods. - */ - Parser.prototype.parseList = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseUShort(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; - }; +/** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ +function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; - Parser.prototype.parseList32 = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseULong(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); + + return glyph; }; +} +/** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ +function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; - /** - * Parse a list of records. - * Record count is optional, if omitted it is read from the stream. - * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - */ - Parser.prototype.parseRecordList = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseUShort(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; - } - return records; + return glyph; }; +} - Parser.prototype.parseRecordList32 = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseULong(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; +var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + +// The `CFF` table contains the glyph outlines in PostScript format. + +// Custom equals function that can also check lists. +function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; } - return records; - }; - // Parse a data structure into an object - // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - Parser.prototype.parseStruct = function(description) { - if (typeof description === 'function') { - return description.call(this); - } else { - var fields = Object.keys(description); - var struct = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = description[fieldName]; - struct[fieldName] = fieldType.call(this); + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; } - return struct; } - }; - /** - * Parse a GPOS valueRecord - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat is optional, if omitted it is read from the stream. - */ - Parser.prototype.parseValueRecord = function(valueFormat) { - if (valueFormat === undefined) { - valueFormat = this.parseUShort(); - } - if (valueFormat === 0) { - // valueFormat2 in kerning pairs is most often 0 - // in this case return undefined instead of an empty object, to save space - return; + return true; + } else { + return false; + } +} + +// Subroutines are encoded using the negative half of the number space. +// See type 2 chapter 4.7 "Subroutine operators". +function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; +} + +// Parse a `CFF` INDEX array. +// An index array consists of a list of offsets, then a list of objects at those offsets. +function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); } - var valueRecord = {}; - if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } - if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } - if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } - if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } - - // Device table (non-variable font) / VariationIndex table (variable font) not supported - // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls - if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } - - return valueRecord; - }; + objects.push(value); + } - /** - * Parse a list of GPOS valueRecords - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat and valueCount are read from the stream. - */ - Parser.prototype.parseValueRecordList = function() { - var valueFormat = this.parseUShort(); - var valueCount = this.parseUShort(); - var values = new Array(valueCount); - for (var i = 0; i < valueCount; i++) { - values[i] = this.parseValueRecord(valueFormat); - } - return values; - }; + return {objects: objects, startOffset: start, endOffset: endOffset}; +} - Parser.prototype.parsePointer = function(description) { - var structOffset = this.parseOffset16(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); +function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; } - return undefined; - }; - Parser.prototype.parsePointer32 = function(description) { - var structOffset = this.parseOffset32(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); - } - return undefined; - }; + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } - /** - * Parse a list of offsets to lists of 16-bit integers, - * or a list of offsets to lists of offsets to any kind of items. - * If itemCallback is not provided, a list of list of UShort is assumed. - * If provided, itemCallback is called on each item and must parse the item. - * See examples in tables/gsub.js - */ - Parser.prototype.parseListOfLists = function(itemCallback) { - var offsets = this.parseOffset16List(); - var count = offsets.length; - var relativeOffset = this.relativeOffset; - var list = new Array(count); - for (var i = 0; i < count; i++) { - var start = offsets[i]; - if (start === 0) { - // NULL offset - // Add i as owned property to list. Convenient with assert. - list[i] = undefined; - continue; - } - this.relativeOffset = start; - if (itemCallback) { - var subOffsets = this.parseOffset16List(); - var subList = new Array(subOffsets.length); - for (var j = 0; j < subOffsets.length; j++) { - this.relativeOffset = start + subOffsets[j]; - subList[j] = itemCallback.call(this); - } - list[i] = subList; - } else { - list[i] = this.parseUShortList(); - } - } - this.relativeOffset = relativeOffset; - return list; - }; + return {offsets: offsets, startOffset: start, endOffset: endOffset}; +} +function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } - ///// Complex tables parsing ////////////////////////////////// + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; +} - // Parse a coverage table in a GSUB, GPOS or GDEF table. - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - // parser.offset must point to the start of the table containing the coverage. - Parser.prototype.parseCoverage = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - var count = this.parseUShort(); - if (format === 1) { - return { - format: 1, - glyphs: this.parseUShortList(count) - }; - } else if (format === 2) { - var ranges = new Array(count); - for (var i = 0; i < count; i++) { - ranges[i] = { - start: this.parseUShort(), - end: this.parseUShort(), - index: this.parseUShort() - }; - } - return { - format: 2, - ranges: ranges - }; - } - throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); - }; +// Parse a `CFF` DICT real value. +function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; - // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - Parser.prototype.parseClassDef = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - if (format === 1) { - return { - format: 1, - startGlyph: this.parseUShort(), - classes: this.parseUShortList() - }; - } else if (format === 2) { - return { - format: 2, - ranges: this.parseRecordList({ - start: Parser.uShort, - end: Parser.uShort, - classId: Parser.uShort - }) - }; + if (n1 === eof) { + break; } - throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); - }; - ///// Static methods /////////////////////////////////// - // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. - - Parser.list = function(count, itemCallback) { - return function() { - return this.parseList(count, itemCallback); - }; - }; + s += lookup[n1]; - Parser.list32 = function(count, itemCallback) { - return function() { - return this.parseList32(count, itemCallback); - }; - }; + if (n2 === eof) { + break; + } - Parser.recordList = function(count, recordDescription) { - return function() { - return this.parseRecordList(count, recordDescription); - }; - }; + s += lookup[n2]; + } - Parser.recordList32 = function(count, recordDescription) { - return function() { - return this.parseRecordList32(count, recordDescription); - }; - }; + return parseFloat(s); +} - Parser.pointer = function(description) { - return function() { - return this.parsePointer(description); - }; - }; +// Parse a `CFF` DICT operand. +function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } - Parser.pointer32 = function(description) { - return function() { - return this.parsePointer32(description); - }; - }; + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } - Parser.tag = Parser.prototype.parseTag; - Parser.byte = Parser.prototype.parseByte; - Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; - Parser.uShortList = Parser.prototype.parseUShortList; - Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; - Parser.uLongList = Parser.prototype.parseULongList; - Parser.struct = Parser.prototype.parseStruct; - Parser.coverage = Parser.prototype.parseCoverage; - Parser.classDef = Parser.prototype.parseClassDef; - - ///// Script, Feature, Lookup lists /////////////////////////////////////////////// - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - - var langSysTable = { - reserved: Parser.uShort, - reqFeatureIndex: Parser.uShort, - featureIndexes: Parser.uShortList - }; + if (b0 === 30) { + return parseFloatOperand(parser); + } - Parser.prototype.parseScriptList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - script: Parser.pointer({ - defaultLangSys: Parser.pointer(langSysTable), - langSysRecords: Parser.recordList({ - tag: Parser.tag, - langSys: Parser.pointer(langSysTable) - }) - }) - })) || []; - }; + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } - Parser.prototype.parseFeatureList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - feature: Parser.pointer({ - featureParams: Parser.offset16, - lookupListIndexes: Parser.uShortList - }) - })) || []; - }; + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } - Parser.prototype.parseLookupList = function(lookupTableParsers) { - return this.parsePointer(Parser.list(Parser.pointer(function() { - var lookupType = this.parseUShort(); - check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); - var lookupFlag = this.parseUShort(); - var useMarkFilteringSet = lookupFlag & 0x10; - return { - lookupType: lookupType, - lookupFlag: lookupFlag, - subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), - markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined - }; - }))) || []; - }; + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } - Parser.prototype.parseFeatureVariationsList = function() { - return this.parsePointer32(function() { - var majorVersion = this.parseUShort(); - var minorVersion = this.parseUShort(); - check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); - var featureVariations = this.parseRecordList32({ - conditionSetOffset: Parser.offset32, - featureTableSubstitutionOffset: Parser.offset32 - }); - return featureVariations; - }) || []; - }; + throw new Error('Invalid b0 ' + b0); +} - var parse = { - getByte: getByte, - getCard8: getByte, - getUShort: getUShort, - getCard16: getUShort, - getShort: getShort, - getULong: getULong, - getFixed: getFixed, - getTag: getTag, - getOffset: getOffset, - getBytes: getBytes, - bytesToString: bytesToString, - Parser: Parser, - }; +// Convert the entries returned by `parseDict` to a proper dictionary. +// If a value is a list of one, it is unpacked. +function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } - // The `cmap` table stores the mappings from characters to glyphs. + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); + } - function parseCmapTableFormat12(cmap, p) { - //Skip reserved. - p.parseUShort(); + o[key] = value; + } - // Length in bytes of the sub-tables. - cmap.length = p.parseULong(); - cmap.language = p.parseULong(); + return o; +} - var groupCount; - cmap.groupCount = groupCount = p.parseULong(); - cmap.glyphIndexMap = {}; +// Parse a `CFF` DICT object. +// A dictionary contains key-value pairs in a compact tokenized format. +function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; - for (var i = 0; i < groupCount; i += 1) { - var startCharCode = p.parseULong(); - var endCharCode = p.parseULong(); - var startGlyphId = p.parseULong(); + while (parser.relativeOffset < size) { + var op = parser.parseByte(); - for (var c = startCharCode; c <= endCharCode; c += 1) { - cmap.glyphIndexMap[c] = startGlyphId; - startGlyphId++; + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); } } - function parseCmapTableFormat4(cmap, p, data, start, offset) { - // Length in bytes of the sub-tables. - cmap.length = p.parseUShort(); - cmap.language = p.parseUShort(); + return entriesToObject(entries); +} - // segCount is stored x 2. - var segCount; - cmap.segCount = segCount = p.parseUShort() >> 1; +// Given a String Index (SID), return the value of the string. +// Strings below index 392 are standard CFF strings and are not encoded in the font. +function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); + return index; +} - // The "unrolled" mapping from character codes to glyph indices. - cmap.glyphIndexMap = {}; - var endCountParser = new parse.Parser(data, start + offset + 14); - var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); - var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); - var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); - var glyphIndexOffset = start + offset + 16 + segCount * 8; - for (var i = 0; i < segCount - 1; i += 1) { - var glyphIndex = (void 0); - var endCount = endCountParser.parseUShort(); - var startCount = startCountParser.parseUShort(); - var idDelta = idDeltaParser.parseShort(); - var idRangeOffset = idRangeOffsetParser.parseUShort(); - for (var c = startCount; c <= endCount; c += 1) { - if (idRangeOffset !== 0) { - // The idRangeOffset is relative to the current position in the idRangeOffset array. - // Take the current offset in the idRangeOffset array. - glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); - - // Add the value of the idRangeOffset, which will move us into the glyphIndex array. - glyphIndexOffset += idRangeOffset; - - // Then add the character index of the current segment, multiplied by 2 for USHORTs. - glyphIndexOffset += (c - startCount) * 2; - glyphIndex = parse.getUShort(data, glyphIndexOffset); - if (glyphIndex !== 0) { - glyphIndex = (glyphIndex + idDelta) & 0xFFFF; - } - } else { - glyphIndex = (c + idDelta) & 0xFFFF; - } +// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. +// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. +function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; - cmap.glyphIndexMap[c] = glyphIndex; - } - } - } + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; - // Parse the `cmap` table. This table stores the mappings from characters to glyphs. - // There are many available formats, but we only support the Windows format 4 and 12. - // This function returns a `CmapEncoding` object or null if no supported format could be found. - function parseCmapTable(data, start) { - var cmap = {}; - cmap.version = parse.getUShort(data, start); - check.argument(cmap.version === 0, 'cmap table version should be 0.'); - - // The cmap table can contain many sub-tables, each with their own format. - // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. - cmap.numTables = parse.getUShort(data, start + 2); - var offset = -1; - for (var i = cmap.numTables - 1; i >= 0; i -= 1) { - var platformId = parse.getUShort(data, start + 4 + (i * 8)); - var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); - if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || - (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { - offset = parse.getULong(data, start + 4 + (i * 8) + 4); - break; + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; +} + +// Parse the CFF header. +function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; +} + +var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} +]; + +var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} +]; + +// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. +// The top dictionary contains the essential metadata for the font, together with the private dictionary. +function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); +} + +// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. +function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); +} + +// Returns a list of "Top DICT"s found using an INDEX list. +// Used to read both the usual high-level Top DICTs and also the FDArray +// discovered inside CID-keyed fonts. When a Top DICT has a reference to +// a Private DICT that is read and saved into the Top DICT. +// +// In addition to the expected/optional values as outlined in TOP_DICT_META +// the following values might be saved into the Top DICT. +// +// _subrs [] array of local CFF subroutines from Private DICT +// _subrsBias bias value computed from number of subroutines +// (see calcCFFSubroutineBias() and parseCFFCharstring()) +// _defaultWidthX default widths for CFF characters +// _nominalWidthX bias added to width embedded within glyph description +// +// _privateDict saved copy of parsed Private DICT from Top DICT +function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + topDict._defaultWidthX = 0; + topDict._nominalWidthX = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; +} + +// Parse the CFF charset table, which contains internal names for all the glyphs. +// This function will return a list of glyph names. +// See Adobe TN #5176 chapter 13, "Charsets". +function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; } } - - if (offset === -1) { - // There is no cmap table in the font that we support. - throw new Error('No valid cmap sub-tables found.'); + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; +} + +// Parse the CFF encoding data. Only one encoding can be specified per font. +// See Adobe TN #5176 chapter 12, "Encodings". +function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); +} + +// Take in charstring code and return a Glyph object. +// The encoding is described in the Type 2 Charstring Format +// https://www.microsoft.com/typography/OTSPEC/charstr2.htm +function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + + function newContour(x, y) { + if (open) { + p.closePath(); } - var p = new parse.Parser(data, start + offset); - cmap.format = p.parseUShort(); + p.moveTo(x, y); + open = true; + } - if (cmap.format === 12) { - parseCmapTableFormat12(cmap, p); - } else if (cmap.format === 4) { - parseCmapTableFormat4(cmap, p, data, start, offset); - } else { - throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); - } + function parseStems() { + var hasWidthArg; - return cmap; - } + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } - function addSegment(t, code, glyphIndex) { - t.segments.push({ - end: code, - start: code, - delta: -(code - glyphIndex), - offset: 0, - glyphIndex: glyphIndex - }); + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; } - function addTerminatorSegment(t) { - t.segments.push({ - end: 0xFFFF, - start: 0xFFFF, - delta: 1, - offset: 0 - }); - } + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } - // Make cmap table, format 4 by default, 12 if needed only - function makeCmapTable(glyphs) { - // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) - var isPlan0Only = true; - var i; + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } - // Check if we need to add cmap format 12 or if format 4 only is fine - for (i = glyphs.length - 1; i > 0; i -= 1) { - var g = glyphs.get(i); - if (g.unicode > 65535) { - console.log('Adding CMAP format 12 (needed!)'); - isPlan0Only = false; - break; - } - } + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } - var cmapTable = [ - {name: 'version', type: 'USHORT', value: 0}, - {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, - - // CMAP 4 header - {name: 'platformID', type: 'USHORT', value: 3}, - {name: 'encodingID', type: 'USHORT', value: 1}, - {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} - ]; - - if (!isPlan0Only) - { cmapTable = cmapTable.concat([ - // CMAP 12 header - {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere - {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, - {name: 'cmap12Offset', type: 'ULONG', value: 0} - ]); } - - cmapTable = cmapTable.concat([ - // CMAP 4 Subtable - {name: 'format', type: 'USHORT', value: 4}, - {name: 'cmap4Length', type: 'USHORT', value: 0}, - {name: 'language', type: 'USHORT', value: 0}, - {name: 'segCountX2', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); + y += stack.shift(); + p.lineTo(x, y); + } - var t = new table.Table('cmap', cmapTable); + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } - t.segments = []; - for (i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - addSegment(t, glyph.unicodes[j], i); - } + x += stack.shift(); + p.lineTo(x, y); + } - t.segments = t.segments.sort(function (a, b) { - return a.start - b.start; - }); - } + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - addTerminatorSegment(t); + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } - var segCount = t.segments.length; - var segCountToRemove = 0; + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } - // CMAP 4 - // Set up parallel segment arrays. - var endCounts = []; - var startCounts = []; - var idDeltas = []; - var idRangeOffsets = []; - var glyphIds = []; + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } - // CMAP 12 - var cmap12Groups = []; - - // Reminder this loop is not following the specification at 100% - // The specification -> find suites of characters and make a group - // Here we're doing one group for each letter - // Doing as the spec can save 8 times (or more) space - for (i = 0; i < segCount; i += 1) { - var segment = t.segments[i]; - - // CMAP 4 - if (segment.end <= 65535 && segment.start <= 65535) { - endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); - startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); - idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); - idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); - if (segment.glyphId !== undefined) { - glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); - } - } else { - // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 - segCountToRemove += 1; - } + if (open) { + p.closePath(); + open = false; + } - // CMAP 12 - // Skip Terminator Segment - if (!isPlan0Only && segment.glyphIndex !== undefined) { - cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); - } - } + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } - // CMAP 4 Subtable - t.segCountX2 = (segCount - segCountToRemove) * 2; - t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; - t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); - t.rangeShift = t.segCountX2 - t.searchRange; - - t.fields = t.fields.concat(endCounts); - t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); - t.fields = t.fields.concat(startCounts); - t.fields = t.fields.concat(idDeltas); - t.fields = t.fields.concat(idRangeOffsets); - t.fields = t.fields.concat(glyphIds); - - t.cmap4Length = 14 + // Subtable header - endCounts.length * 2 + - 2 + // reservedPad - startCounts.length * 2 + - idDeltas.length * 2 + - idRangeOffsets.length * 2 + - glyphIds.length * 2; - - if (!isPlan0Only) { - // CMAP 12 Subtable - var cmap12Length = 16 + // Subtable header - cmap12Groups.length * 4; - - t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; - t.fields = t.fields.concat([ - {name: 'cmap12Format', type: 'USHORT', value: 12}, - {name: 'cmap12Reserved', type: 'USHORT', value: 0}, - {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, - {name: 'cmap12Language', type: 'ULONG', value: 0}, - {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} - ]); - - t.fields = t.fields.concat(cmap12Groups); - } - - return t; - } - - var cmap = { parse: parseCmapTable, make: makeCmapTable }; - - // Glyph encoding - - var cffStandardStrings = [ - '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', - 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', - 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', - 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', - 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', - 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', - 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', - 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', - 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', - 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', - 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', - 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', - 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', - 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', - 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', - 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', - 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', - 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', - 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', - 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', - 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', - 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', - 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', - 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', - 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', - 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', - 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', - 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', - 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', - 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', - 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', - '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; - - var cffStandardEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', - 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', - 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', - 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', - 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', - 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', - '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', - 'lslash', 'oslash', 'oe', 'germandbls']; - - var cffExpertEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', - 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', - 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', - 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', - 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', - 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', - 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', - 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', - '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', - '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', - 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', - 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', - 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', - 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', - 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', - 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', - 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', - 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; - - var standardNames = [ - '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', - 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', - 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', - 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', - 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', - 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', - 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', - 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', - 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', - 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', - 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', - 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', - 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', - 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', - 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', - 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', - 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', - 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', - 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', - 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', - 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', - 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', - 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } - /** - * This is the encoding used for fonts created from scratch. - * It loops through all glyphs and finds the appropriate unicode value. - * Since it's linear time, other encodings will be faster. - * @exports opentype.DefaultEncoding - * @class - * @constructor - * @param {opentype.Font} - */ - function DefaultEncoding(font) { - this.font = font; - } - - DefaultEncoding.prototype.charToGlyphIndex = function(c) { - var code = c.codePointAt(0); - var glyphs = this.font.glyphs; - if (glyphs) { - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - if (glyph.unicodes[j] === code) { - return i; + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); } - } - } - } - return null; - }; - /** - * @exports opentype.CmapEncoding - * @class - * @constructor - * @param {Object} cmap - a object with the cmap encoded data - */ - function CmapEncoding(cmap) { - this.cmap = cmap; - } + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } - /** - * @param {string} c - the character - * @return {number} The glyph index. - */ - CmapEncoding.prototype.charToGlyphIndex = function(c) { - return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; - }; + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } - /** - * @exports opentype.CffEncoding - * @class - * @constructor - * @param {string} encoding - The encoding - * @param {Array} charset - The character set. - */ - function CffEncoding(encoding, charset) { - this.encoding = encoding; - this.charset = charset; - } - - /** - * @param {string} s - The character - * @return {number} The index. - */ - CffEncoding.prototype.charToGlyphIndex = function(s) { - var code = s.codePointAt(0); - var charName = this.encoding[code]; - return this.charset.indexOf(charName); - }; + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - /** - * @exports opentype.GlyphNames - * @class - * @constructor - * @param {Object} post - */ - function GlyphNames(post) { - switch (post.version) { - case 1: - this.names = standardNames.slice(); - break; - case 2: - this.names = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - if (post.glyphNameIndex[i] < standardNames.length) { - this.names[i] = standardNames[post.glyphNameIndex[i]]; - } else { - this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); } - } - break; - case 2.5: - this.names = new Array(post.numberOfGlyphs); - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; - } + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - break; - case 3: - this.names = []; - break; - default: - this.names = []; - break; - } - } + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } - /** - * Gets the index of a glyph by name. - * @param {string} name - The glyph name - * @return {number} The index - */ - GlyphNames.prototype.nameToGlyphIndex = function(name) { - return this.names.indexOf(name); - }; + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } - /** - * @param {number} gid - * @return {string} - */ - GlyphNames.prototype.glyphIndexToName = function(gid) { - return this.names[gid]; - }; + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - function addGlyphNamesAll(font) { - var glyph; - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - glyph = font.glyphs.get(glyphIndex); - glyph.addUnicode(parseInt(c)); - } + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } - for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { - glyph = font.glyphs.get(i$1); - if (font.cffEncoding) { - if (font.isCIDFont) { - glyph.name = 'gid' + i$1; - } else { - glyph.name = font.cffEncoding.charset[i$1]; - } - } else if (font.glyphNames.names) { - glyph.name = font.glyphNames.glyphIndexToName(i$1); + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } } } } - function addGlyphNamesToUnicodeMap(font) { - font._IndexToUnicodeMap = {}; + parse(code); - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); + glyph.advanceWidth = width; + return p; +} - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - if (font._IndexToUnicodeMap[glyphIndex] === undefined) { - font._IndexToUnicodeMap[glyphIndex] = { - unicodes: [parseInt(c)] - }; - } else { - font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); +function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); } + return fdSelect; +} - /** - * @alias opentype.addGlyphNames - * @param {opentype.Font} - * @param {Object} - */ - function addGlyphNames(font, opt) { - if (opt.lowMemory) { - addGlyphNamesToUnicodeMap(font); - } else { - addGlyphNamesAll(font); - } +// Parse the `CFF` table, which contains the glyph outlines in PostScript format. +function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); } - // Drawing utility functions. + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; - // Draw a line on the given context from point `x1,y1` to point `x2,y2`. - function line(ctx, x1, y1, x2, y2) { - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; } - var draw = { line: line }; + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; + } - // The Glyph object - // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } - function getPathDefinition(glyph, path) { - var _path = path || new Path(); - return { - configurable: true, + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; - get: function() { - if (typeof _path === 'function') { - _path = _path(); - } + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } - return _path; - }, + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } - set: function(p) { - _path = p; - } + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } } - /** - * @typedef GlyphOptions - * @type Object - * @property {string} [name] - The glyph name - * @property {number} [unicode] - * @property {Array} [unicodes] - * @property {number} [xMin] - * @property {number} [yMin] - * @property {number} [xMax] - * @property {number} [yMax] - * @property {number} [advanceWidth] - */ +} - // A Glyph is an individual mark that often corresponds to a character. - // Some glyphs, such as ligatures, are a combination of many characters. - // Glyphs are the basic building blocks of a font. - // - // The `Glyph` class contains utility methods for drawing the path and its points. - /** - * @exports opentype.Glyph - * @class - * @param {GlyphOptions} - * @constructor - */ - function Glyph(options) { - // By putting all the code on a prototype function (which is only declared once) - // we reduce the memory requirements for larger fonts by some 2% - this.bindConstructorValues(options); +// Convert a string to a String ID (SID). +// The list of strings is modified in place. +function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; } - /** - * @param {GlyphOptions} - */ - Glyph.prototype.bindConstructorValues = function(options) { - this.index = options.index || 0; + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); + } - // These three values cannot be deferred for memory optimization: - this.name = options.name || null; - this.unicode = options.unicode || undefined; - this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + return sid; +} - // But by binding these values only when necessary, we reduce can - // the memory requirements by almost 3% for larger fonts. - if ('xMin' in options) { - this.xMin = options.xMin; - } +function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); +} - if ('yMin' in options) { - this.yMin = options.yMin; - } +function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } - if ('xMax' in options) { - this.xMax = options.xMax; - } + return t; +} - if ('yMax' in options) { - this.yMax = options.yMax; - } +// Given a dictionary's metadata, create a DICT structure. +function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } - if ('advanceWidth' in options) { - this.advanceWidth = options.advanceWidth; + m[entry.op] = {name: entry.name, type: entry.type, value: value}; } + } - // The path for a glyph is the most memory intensive, and is bound as a value - // with a getter/setter to ensure we actually do path parsing only once the - // path is actually needed by anything. - Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); - }; + return m; +} - /** - * @param {number} - */ - Glyph.prototype.addUnicode = function(unicode) { - if (this.unicodes.length === 0) { - this.unicode = unicode; - } +// The Top DICT houses the global font attributes. +function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; +} - this.unicodes.push(unicode); - }; +function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; +} - /** - * Calculate the minimum bounding box for this glyph. - * @return {opentype.BoundingBox} - */ - Glyph.prototype.getBoundingBox = function() { - return this.path.getBoundingBox(); - }; +function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } - /** - * Convert the glyph to a Path we can draw on a drawing context. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - * @param {opentype.Font} if hinting is to be used, the font - * @return {opentype.Path} - */ - Glyph.prototype.getPath = function(x, y, fontSize, options, font) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - var commands; - var hPoints; - if (!options) { options = { }; } - var xScale = options.xScale; - var yScale = options.yScale; - - if (options.hinting && font && font.hinting) { - // in case of hinting, the hinting engine takes care - // of scaling the points (not the path) before hinting. - hPoints = this.path && font.hinting.exec(this, fontSize); - // in case the hinting engine failed hPoints is undefined - // and thus reverts to plain rending - } - - if (hPoints) { - // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency - commands = font.hinting.getCommands(hPoints); - x = Math.round(x); - y = Math.round(y); - // TODO in case of hinting xyScaling is not yet supported - xScale = yScale = 1; - } else { - commands = this.path.commands; - var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; - if (xScale === undefined) { xScale = scale; } - if (yScale === undefined) { yScale = scale; } - } - - var p = new Path(); - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type === 'M') { - p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'L') { - p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Q') { - p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'C') { - p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Z') { - p.closePath(); - } - } + return t; +} - return p; - }; +function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); +} - /** - * Split the glyph into contours. - * This function is here for backwards compatibility, and to - * provide raw access to the TrueType glyph outlines. - * @return {Array} - */ - Glyph.prototype.getContours = function() { - if (this.points === undefined) { - return []; - } - - var contours = []; - var currentContour = []; - for (var i = 0; i < this.points.length; i += 1) { - var pt = this.points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; - } +function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; +} + +function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: Math.round(_13 * x + _23 * cmd.x1), + y1: Math.round(_13 * y + _23 * cmd.y1), + x2: Math.round(_13 * cmd.x + _23 * cmd.x1), + y2: Math.round(_13 * cmd.y + _23 * cmd.y1) + }; } - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; - }; + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; +} + +function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); - /** - * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. - * @return {Object} - */ - Glyph.prototype.getMetrics = function() { - var commands = this.path.commands; - var xCoords = []; - var yCoords = []; - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type !== 'Z') { - xCoords.push(cmd.x); - yCoords.push(cmd.y); - } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } - if (cmd.type === 'Q' || cmd.type === 'C') { - xCoords.push(cmd.x1); - yCoords.push(cmd.y1); - } + return t; +} - if (cmd.type === 'C') { - xCoords.push(cmd.x2); - yCoords.push(cmd.y2); - } - } +function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; +} + +function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); - var metrics = { - xMin: Math.min.apply(null, xCoords), - yMin: Math.min.apply(null, yCoords), - xMax: Math.max.apply(null, xCoords), - yMax: Math.max.apply(null, yCoords), - leftSideBearing: this.leftSideBearing + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; +} + +var cff = { parse: parseCFFTable, make: makeCFFTable }; + +// The `head` table contains global information about the font. + +// Parse the header `head` table +function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; +} + +function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); +} + +var head = { parse: parseHeadTable, make: makeHeadTable }; + +// The `hhea` table contains information for horizontal layout. + +// Parse the horizontal header `hhea` table +function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; +} + +function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); +} + +var hhea = { parse: parseHheaTable, make: makeHheaTable }; + +// The `hmtx` table contains the horizontal metrics for all glyphs. + +function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } +} + +function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing }; + } +} - if (!isFinite(metrics.xMin)) { - metrics.xMin = 0; - } +// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. +// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. +function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } +} - if (!isFinite(metrics.xMax)) { - metrics.xMax = this.advanceWidth; - } +function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } - if (!isFinite(metrics.yMin)) { - metrics.yMin = 0; - } + return t; +} + +var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + +// The `ltag` table stores IETF BCP-47 language tags. It allows supporting + +function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); - if (!isFinite(metrics.yMax)) { - metrics.yMax = 0; + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; } - metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); - return metrics; - }; + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); + } - /** - * Draw the glyph on the given context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - */ - Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { - this.getPath(x, y, fontSize, options).draw(ctx); - }; + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; +} - /** - * Draw the points of the glyph. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ - Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { - function drawCircles(l, x, y, scale) { - ctx.beginPath(); - for (var j = 0; j < l.length; j += 1) { - ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); - ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); - } +function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); - ctx.closePath(); - ctx.fill(); - } + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + + tags.push(tag); + } + + return tags; +} + +var ltag = { make: makeLtagTable, parse: parseLtagTable }; + +// The `maxp` table establishes the memory requirements for the font. + +// Parse the maximum profile `maxp` table. +function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; +} + +function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); +} + +var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + +// The `name` naming table. + +// NameIDs for the name table. +var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 +]; + +var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' +}; - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - var scale = 1 / this.path.unitsPerEm * fontSize; +// MacOS language ID → MacOS script ID +// +// Note that the script ID is not sufficient to determine what encoding +// to use in TrueType files. For some languages, MacOS used a modification +// of a mainstream script. For example, an Icelandic name would be stored +// with smRoman in the TrueType naming table, but the actual encoding +// is a special Icelandic version of the normal Macintosh Roman encoding. +// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal +// Syllables but MacOS had run out of available script codes, so this was +// done as a (pretty radical) "modification" of Ethiopic. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman +}; - var blueCircles = []; - var redCircles = []; - var path = this.path; - for (var i = 0; i < path.commands.length; i += 1) { - var cmd = path.commands[i]; - if (cmd.x !== undefined) { - blueCircles.push({x: cmd.x, y: -cmd.y}); - } +// While Microsoft indicates a region/country for all its language +// IDs, we omit the region code if it's equal to the "most likely +// region subtag" according to Unicode CLDR. For scripts, we omit +// the subtag if it is equal to the Suppress-Script entry in the +// IANA language subtag registry for IETF BCP 47. +// +// For example, Microsoft states that its language code 0x041A is +// Croatian in Croatia. We transform this to the BCP 47 language code 'hr' +// and not 'hr-HR' because Croatia is the default country for Croatian, +// according to Unicode CLDR. As another example, Microsoft states +// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform +// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script +// for the Croatian language, according to IANA. +// +// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html +// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry +var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' +}; - if (cmd.x1 !== undefined) { - redCircles.push({x: cmd.x1, y: -cmd.y1}); +// Returns a IETF BCP 47 language code, for example 'zh-Hant' +// for 'Chinese in the traditional script'. +function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; } - if (cmd.x2 !== undefined) { - redCircles.push({x: cmd.x2, y: -cmd.y2}); - } - } + break; - ctx.fillStyle = 'blue'; - drawCircles(blueCircles, x, y, scale); - ctx.fillStyle = 'red'; - drawCircles(redCircles, x, y, scale); - }; + case 1: // Macintosh + return macLanguages[languageID]; + + case 3: // Windows + return windowsLanguages[languageID]; + } + + return undefined; +} + +var utf16 = 'utf-16'; + +// MacOS script ID → encoding. This table stores the default case, +// which can be overridden by macLanguageEncodings. +var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic +}; - /** - * Draw lines indicating important font measurements. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ - Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { - var scale; - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - scale = 1 / this.path.unitsPerEm * fontSize; - ctx.lineWidth = 1; - - // Draw the origin - ctx.strokeStyle = 'black'; - draw.line(ctx, x, -10000, x, 10000); - draw.line(ctx, -10000, y, 10000, y); - - // This code is here due to memory optimization: by not using - // defaults in the constructor, we save a notable amount of memory. - var xMin = this.xMin || 0; - var yMin = this.yMin || 0; - var xMax = this.xMax || 0; - var yMax = this.yMax || 0; - var advanceWidth = this.advanceWidth || 0; - - // Draw the glyph box - ctx.strokeStyle = 'blue'; - draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); - draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); - draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); - draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); - - // Draw the advance width - ctx.strokeStyle = 'green'; - draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); - }; +// MacOS language ID → encoding. This table stores the exceptional +// cases, which override macScriptEncodings. For writing MacOS naming +// tables, we need to emit a MacOS script ID. Therefore, we cannot +// merge macScriptEncodings into macLanguageEncodings. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript +}; - // The GlyphSet object - - // Define a property on the glyph that depends on the path being loaded. - function defineDependentProperty(glyph, externalName, internalName) { - Object.defineProperty(glyph, externalName, { - get: function() { - // Request the path property to make sure the path is loaded. - glyph.path; // jshint ignore:line - return glyph[internalName]; - }, - set: function(newValue) { - glyph[internalName] = newValue; - }, - enumerable: true, - configurable: true - }); - } +function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; - /** - * A GlyphSet represents all glyphs available in the font, but modelled using - * a deferred glyph loader, for retrieving glyphs only once they are absolutely - * necessary, to keep the memory footprint down. - * @exports opentype.GlyphSet - * @class - * @param {opentype.Font} - * @param {Array} - */ - function GlyphSet(font, glyphs) { - this.font = font; - this.glyphs = {}; - if (Array.isArray(glyphs)) { - for (var i = 0; i < glyphs.length; i++) { - var glyph = glyphs[i]; - glyph.path.unitsPerEm = font.unitsPerEm; - this.glyphs[i] = glyph; + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; } - } - this.length = (glyphs && glyphs.length) || 0; + break; } - /** - * @param {number} index - * @return {opentype.Glyph} - */ - GlyphSet.prototype.get = function(index) { - // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. - if (this.glyphs[index] === undefined) { - this.font._push(index); - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); + return undefined; +} + +// Parse the naming `name` table. +// FIXME: Format 1 additional fields are not supported yet. +// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. +function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); } - var glyph = this.glyphs[index]; - var unicodeObj = this.font._IndexToUnicodeMap[index]; + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } - if (unicodeObj) { - for (var j = 0; j < unicodeObj.unicodes.length; j++) - { glyph.addUnicode(unicodeObj.unicodes[j]); } + translations[language] = text; } + } + } - if (this.font.cffEncoding) { - if (this.font.isCIDFont) { - glyph.name = 'gid' + index; - } else { - glyph.name = this.font.cffEncoding.charset[index]; + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); + } + + return name; +} + +// {23: 'foo'} → {'foo': 23} +// ['bar', 'baz'] → {'bar': 0, 'baz': 1} +function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); + } + + return result; +} + +function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); +} + +// Finds the position of needle in haystack, or -1 if not there. +// Like String.indexOf(), but for arrays. +function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; } - } else if (this.font.glyphNames.names) { - glyph.name = this.font.glyphNames.glyphIndexToName(index); } - this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; - this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; - } else { - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); - } + return pos; } + } - return this.glyphs[index]; - }; + return -1; +} - /** - * @param {number} index - * @param {Object} - */ - GlyphSet.prototype.push = function(index, loader) { - this.glyphs[index] = loader; - this.length++; - }; +function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } - /** - * @alias opentype.glyphLoader - * @param {opentype.Font} font - * @param {number} index - * @return {opentype.Glyph} - */ - function glyphLoader(font, index) { - return new Glyph({index: index, font: font}); } - /** - * Generate a stub glyph that can be filled with all metadata *except* - * the "points" and "path" properties, which must be loaded only once - * the glyph's path is actually requested for text shaping. - * @alias opentype.ttfGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseGlyph - * @param {Object} data - * @param {number} position - * @param {Function} buildPath - * @return {opentype.Glyph} - */ - function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - parseGlyph(glyph, data, position); - var path = buildPath(font.glyphs, glyph); - path.unitsPerEm = font.unitsPerEm; - return path; - }; + return offset; +} - defineDependentProperty(glyph, 'xMin', '_xMin'); - defineDependentProperty(glyph, 'xMax', '_xMax'); - defineDependentProperty(glyph, 'yMin', '_yMin'); - defineDependentProperty(glyph, 'yMax', '_yMax'); +function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; - return glyph; - }; - } - /** - * @alias opentype.cffGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseCFFCharstring - * @param {string} charstring - * @return {opentype.Glyph} - */ - function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - var path = parseCFFCharstring(font, glyph, charstring); - path.unitsPerEm = font.unitsPerEm; - return path; - }; + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } - return glyph; - }; + nameID = parseInt(id); + + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + } + + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); } - var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); - // The `CFF` table contains the glyph outlines in PostScript format. + var nameRecords = []; + var stringPool = []; - // Custom equals function that can also check lists. - function equals(a, b) { - if (a === b) { - return true; - } else if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) { - return false; - } + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; - for (var i = 0; i < a.length; i += 1) { - if (!equals(a[i], b[i])) { - return false; + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); } + + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); } - return true; - } else { - return false; + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); + + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } } } - // Subroutines are encoded using the negative half of the number space. - // See type 2 chapter 4.7 "Subroutine operators". - function calcCFFSubroutineBias(subrs) { - var bias; - if (subrs.length < 1240) { - bias = 107; - } else if (subrs.length < 33900) { - bias = 1131; - } else { - bias = 32768; - } - - return bias; - } - - // Parse a `CFF` INDEX array. - // An index array consists of a list of offsets, then a list of objects at those offsets. - function parseCFFIndex(data, start, conversionFn) { - var offsets = []; - var objects = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; +} + +var _name = { parse: parseNameTable, make: makeNameTable }; + +// The `OS/2` table contains metrics required in OpenType fonts. + +var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles +]; + +function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; +} + +// Parse the OS/2 and Windows metrics `OS/2` table +function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; +} + +function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); +} + +var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + +// The `post` table stores additional PostScript information, such as glyph names. + +// Parse the PostScript `post` table +function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } - for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { - var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); - if (conversionFn) { - value = conversionFn(value); + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } } - objects.push(value); - } + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } - return {objects: objects, startOffset: start, endOffset: endOffset}; + break; } + return post; +} + +function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); +} - function parseCFFIndexLowMemory(data, start) { - var offsets = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } +var post = { parse: parsePostTable, make: makePostTable }; - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } +// The `GSUB` table contains ligatures, among other things. - return {offsets: offsets, startOffset: start, endOffset: endOffset}; - } - function getCffIndexObject(i, offsets, data, start, conversionFn) { - var count = parse.getCard16(data, start); - var objectOffset = 0; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - } +var subtableParsers = new Array(9); // subtableParsers[0] is unused - var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); - if (conversionFn) { - value = conversionFn(value); - } - return value; +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS +subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); +}; - // Parse a `CFF` DICT real value. - function parseFloatOperand(parser) { - var s = ''; - var eof = 15; - var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; - while (true) { - var b = parser.parseByte(); - var n1 = b >> 4; - var n2 = b & 15; - - if (n1 === eof) { - break; - } +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS +subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; +}; - s += lookup[n1]; +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS +subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; +}; - if (n2 === eof) { - break; - } +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS +subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; +}; - s += lookup[n2]; - } +var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort +}; - return parseFloat(s); +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF +subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); +}; - // Parse a `CFF` DICT operand. - function parseOperand(parser, b0) { - var b1; - var b2; - var b3; - var b4; - if (b0 === 28) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - return b1 << 8 | b2; - } +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC +subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); +}; - if (b0 === 29) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - b3 = parser.parseByte(); - b4 = parser.parseByte(); - return b1 << 24 | b2 << 16 | b3 << 8 | b4; - } +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES +subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; +}; - if (b0 === 30) { - return parseFloatOperand(parser); - } +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS +subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; +}; - if (b0 >= 32 && b0 <= 246) { - return b0 - 139; - } +// https://www.microsoft.com/typography/OTSPEC/gsub.htm +function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } - if (b0 >= 247 && b0 <= 250) { - b1 = parser.parseByte(); - return (b0 - 247) * 256 + b1 + 108; - } +} - if (b0 >= 251 && b0 <= 254) { - b1 = parser.parseByte(); - return -(b0 - 251) * 256 - b1 - 108; - } +// GSUB Writing ////////////////////////////////////////////// +var subtableMakers = new Array(9); - throw new Error('Invalid b0 ' + b0); +subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); } +}; - // Convert the entries returned by `parseDict` to a proper dictionary. - // If a value is a list of one, it is unpacked. - function entriesToObject(entries) { - var o = {}; - for (var i = 0; i < entries.length; i += 1) { - var key = entries[i][0]; - var values = entries[i][1]; - var value = (void 0); - if (values.length === 1) { - value = values[0]; - } else { - value = values; - } +subtableMakers[2] = function makeLookup2(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { + return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); + }))); +}; - if (o.hasOwnProperty(key) && !isNaN(o[key])) { - throw new Error('Object ' + o + ' already has key ' + key); - } +subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); +}; - o[key] = value; - } +subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); +}; - return o; - } +subtableMakers[6] = function makeLookup6(subtable) { + if (subtable.substFormat === 1) { + var returnTable = new table.Table('chainContextTable', [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { + return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { + var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) + .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) + .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) + .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); + + chainRule.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + return new table.Table('chainRuleTable', tableData); + })); + }))); + return returnTable; + } else if (subtable.substFormat === 2) { + check.assert(false, 'lookup type 6 format 2 is not yet supported.'); + } else if (subtable.substFormat === 3) { + var tableData = [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; + + tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); + subtable.backtrackCoverage.forEach(function (coverage, i) { + tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); + subtable.inputCoverage.forEach(function (coverage, i) { + tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); + subtable.lookaheadCoverage.forEach(function (coverage, i) { + tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); - // Parse a `CFF` DICT object. - // A dictionary contains key-value pairs in a compact tokenized format. - function parseCFFDict(data, start, size) { - start = start !== undefined ? start : 0; - var parser = new parse.Parser(data, start); - var entries = []; - var operands = []; - size = size !== undefined ? size : data.length; + tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); + subtable.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); - while (parser.relativeOffset < size) { - var op = parser.parseByte(); + var returnTable$1 = new table.Table('chainContextTable', tableData); - // The first byte for each dict item distinguishes between operator (key) and operand (value). - // Values <= 21 are operators. - if (op <= 21) { - // Two-byte operators have an initial escape byte of 12. - if (op === 12) { - op = 1200 + parser.parseByte(); - } + return returnTable$1; + } - entries.push([op, operands]); - operands = []; - } else { - // Since the operands (values) come before the operators (keys), we store all operands in a list - // until we encounter an operator. - operands.push(parseOperand(parser, op)); - } - } + check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); +}; - return entriesToObject(entries); - } +function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); +} + +var gsub = { parse: parseGsubTable, make: makeGsubTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +// Parse the metadata `meta` table. +// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html +function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); + + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); + + tags[tag] = text; + } + return tags; +} + +function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; + + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); - // Given a String Index (SID), return the value of the string. - // Strings below index 392 are standard CFF strings and are not encoded in the font. - function getCFFString(strings, index) { - if (index <= 390) { - index = cffStandardStrings[index]; - } else { - index = strings[index - 391]; - } + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; - return index; + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); } - // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. - // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. - function interpretDict(dict, meta, strings) { - var newDict = {}; - var value; + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - // Because we also want to include missing values, we start out from the meta list - // and lookup values in the dict. - for (var i = 0; i < meta.length; i += 1) { - var m = meta[i]; - - if (Array.isArray(m.type)) { - var values = []; - values.length = m.type.length; - for (var j = 0; j < m.type.length; j++) { - value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; - if (value === undefined) { - value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; - } - if (m.type[j] === 'SID') { - value = getCFFString(strings, value); - } - values[j] = value; - } - newDict[m.name] = values; - } else { - value = dict[m.op]; - if (value === undefined) { - value = m.value !== undefined ? m.value : null; - } + return result; +} - if (m.type === 'SID') { - value = getCFFString(strings, value); - } - newDict[m.name] = value; - } - } +var meta = { parse: parseMetaTable, make: makeMetaTable }; - return newDict; - } - - // Parse the CFF header. - function parseCFFHeader(data, start) { - var header = {}; - header.formatMajor = parse.getCard8(data, start); - header.formatMinor = parse.getCard8(data, start + 1); - header.size = parse.getCard8(data, start + 2); - header.offsetSize = parse.getCard8(data, start + 3); - header.startOffset = start; - header.endOffset = start + 4; - return header; - } - - var TOP_DICT_META = [ - {name: 'version', op: 0, type: 'SID'}, - {name: 'notice', op: 1, type: 'SID'}, - {name: 'copyright', op: 1200, type: 'SID'}, - {name: 'fullName', op: 2, type: 'SID'}, - {name: 'familyName', op: 3, type: 'SID'}, - {name: 'weight', op: 4, type: 'SID'}, - {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, - {name: 'italicAngle', op: 1202, type: 'number', value: 0}, - {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, - {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, - {name: 'paintType', op: 1205, type: 'number', value: 0}, - {name: 'charstringType', op: 1206, type: 'number', value: 2}, - { - name: 'fontMatrix', - op: 1207, - type: ['real', 'real', 'real', 'real', 'real', 'real'], - value: [0.001, 0, 0, 0.001, 0, 0] - }, - {name: 'uniqueId', op: 13, type: 'number'}, - {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, - {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, - {name: 'xuid', op: 14, type: [], value: null}, - {name: 'charset', op: 15, type: 'offset', value: 0}, - {name: 'encoding', op: 16, type: 'offset', value: 0}, - {name: 'charStrings', op: 17, type: 'offset', value: 0}, - {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, - {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, - {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, - {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, - {name: 'cidFontType', op: 1233, type: 'number', value: 0}, - {name: 'cidCount', op: 1234, type: 'number', value: 8720}, - {name: 'uidBase', op: 1235, type: 'number'}, - {name: 'fdArray', op: 1236, type: 'offset'}, - {name: 'fdSelect', op: 1237, type: 'offset'}, - {name: 'fontName', op: 1238, type: 'SID'} - ]; +// The `COLR` table adds support for multi-colored glyphs - var PRIVATE_DICT_META = [ - {name: 'subrs', op: 19, type: 'offset', value: 0}, - {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, - {name: 'nominalWidthX', op: 21, type: 'number', value: 0} - ]; +function parseColrTable(data, start) { + var p = new Parser(data, start); + var version = p.parseUShort(); + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var numBaseGlyphRecords = p.parseUShort(); + var baseGlyphRecordsOffset = p.parseOffset32(); + var layerRecordsOffset = p.parseOffset32(); + var numLayerRecords = p.parseUShort(); + p.relativeOffset = baseGlyphRecordsOffset; + var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, { + glyphID: Parser.uShort, + firstLayerIndex: Parser.uShort, + numLayers: Parser.uShort, + }); + p.relativeOffset = layerRecordsOffset; + var layerRecords = p.parseRecordList(numLayerRecords, { + glyphID: Parser.uShort, + paletteIndex: Parser.uShort + }); - // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. - // The top dictionary contains the essential metadata for the font, together with the private dictionary. - function parseCFFTopDict(data, strings) { - var dict = parseCFFDict(data, 0, data.byteLength); - return interpretDict(dict, TOP_DICT_META, strings); - } + return { + version: version, + baseGlyphRecords: baseGlyphRecords, + layerRecords: layerRecords, + }; +} - // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. - function parseCFFPrivateDict(data, start, size, strings) { - var dict = parseCFFDict(data, start, size); - return interpretDict(dict, PRIVATE_DICT_META, strings); - } +function makeColrTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0x0000; + var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = []; + var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = []; - // Returns a list of "Top DICT"s found using an INDEX list. - // Used to read both the usual high-level Top DICTs and also the FDArray - // discovered inside CID-keyed fonts. When a Top DICT has a reference to - // a Private DICT that is read and saved into the Top DICT. - // - // In addition to the expected/optional values as outlined in TOP_DICT_META - // the following values might be saved into the Top DICT. - // - // _subrs [] array of local CFF subroutines from Private DICT - // _subrsBias bias value computed from number of subroutines - // (see calcCFFSubroutineBias() and parseCFFCharstring()) - // _defaultWidthX default widths for CFF characters - // _nominalWidthX bias added to width embedded within glyph description - // - // _privateDict saved copy of parsed Private DICT from Top DICT - function gatherCFFTopDicts(data, start, cffIndex, strings) { - var topDictArray = []; - for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { - var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); - var topDict = parseCFFTopDict(topDictData, strings); - topDict._subrs = []; - topDict._subrsBias = 0; - topDict._defaultWidthX = 0; - topDict._nominalWidthX = 0; - var privateSize = topDict.private[0]; - var privateOffset = topDict.private[1]; - if (privateSize !== 0 && privateOffset !== 0) { - var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); - topDict._defaultWidthX = privateDict.defaultWidthX; - topDict._nominalWidthX = privateDict.nominalWidthX; - if (privateDict.subrs !== 0) { - var subrOffset = privateOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset + start); - topDict._subrs = subrIndex.objects; - topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); - } - topDict._privateDict = privateDict; - } - topDictArray.push(topDict); + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var baseGlyphRecordsOffset = 14; + var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6); + return new table.Table('COLR', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length }, + { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset }, + { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset }, + { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [ + { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID }, + { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex }, + { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(), + layerRecords.map(function (layer, i) { return [ + { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID }, + { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() )); +} + +var colr = { parse: parseColrTable, make: makeColrTable }; + +// The `CPAL` define a contiguous list of colors (colorRecords) + +// Parse the header `head` table +function parseCpalTable(data, start) { + var p = new Parser(data, start); + var version = p.parseShort(); + var numPaletteEntries = p.parseShort(); + var numPalettes = p.parseShort(); + var numColorRecords = p.parseShort(); + var colorRecordsArrayOffset = p.parseOffset32(); + var colorRecordIndices = p.parseUShortList(numPalettes); + p.relativeOffset = colorRecordsArrayOffset; + var colorRecords = p.parseULongList(numColorRecords); + return { + version: version, + numPaletteEntries: numPaletteEntries, + colorRecords: colorRecords, + colorRecordIndices: colorRecordIndices, + }; +} + +function makeCpalTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0; + var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0; + var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = []; + var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0]; + + check.argument(version === 0, 'Only CPALv0 are supported.'); + check.argument(colorRecords.length, 'No colorRecords given.'); + check.argument(colorRecordIndices.length, 'No colorRecordIndices given.'); + check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices'); + return new table.Table('CPAL', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length }, + { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length }, + { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length }, + { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }), + colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) )); +} + +var cpal = { parse: parseCpalTable, make: makeCpalTable }; + +// The `sfnt` wrapper provides organization for the tables in the font. + +function log2(v) { + return Math.log(v) / Math.log(2) | 0; +} + +function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; +} + +function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); +} + +function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); } - return topDictArray; } - // Parse the CFF charset table, which contains internal names for all the glyphs. - // This function will return a list of glyph names. - // See Adobe TN #5176 chapter 13, "Charsets". - function parseCFFCharset(data, start, nGlyphs, strings) { - var sid; - var count; - var parser = new parse.Parser(data, start); + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); - // The .notdef glyph is not included, so subtract 1. - nGlyphs -= 1; - var charset = ['.notdef']; + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; +} - var format = parser.parseCard8(); - if (format === 0) { - for (var i = 0; i < nGlyphs; i += 1) { - sid = parser.parseSID(); - charset.push(getCFFString(strings, sid)); - } - } else if (format === 1) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard8(); - for (var i$1 = 0; i$1 <= count; i$1 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else if (format === 2) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard16(); - for (var i$2 = 0; i$2 <= count; i$2 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else { - throw new Error('Unknown charset format ' + format); +// Get the metrics for a character. If the string has more than one character +// this function returns metrics for the first available character. +// You can provide optional fallback metrics if no characters are available. +function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); } + } - return charset; + return notFoundMetrics; +} + +function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; } - // Parse the CFF encoding data. Only one encoding can be specified per font. - // See Adobe TN #5176 chapter 12, "Encodings". - function parseCFFEncoding(data, start, charset) { - var code; - var enc = {}; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - var nCodes = parser.parseCard8(); - for (var i = 0; i < nCodes; i += 1) { - code = parser.parseCard8(); - enc[code] = i; - } - } else if (format === 1) { - var nRanges = parser.parseCard8(); - code = 1; - for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { - var first = parser.parseCard8(); - var nLeft = parser.parseCard8(); - for (var j = first; j <= first + nLeft; j += 1) { - enc[j] = code; - code += 1; - } - } - } else { - throw new Error('Unknown encoding format ' + format); - } - - return new CffEncoding(enc, charset); - } - - // Take in charstring code and return a Glyph object. - // The encoding is described in the Type 2 Charstring Format - // https://www.microsoft.com/typography/OTSPEC/charstr2.htm - function parseCFFCharstring(font, glyph, code) { - var c1x; - var c1y; - var c2x; - var c2y; - var p = new Path(); - var stack = []; - var nStems = 0; - var haveWidth = false; - var open = false; - var x = 0; - var y = 0; - var subrs; - var subrsBias; - var defaultWidthX; - var nominalWidthX; - if (font.isCIDFont) { - var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; - var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; - subrs = fdDict._subrs; - subrsBias = fdDict._subrsBias; - defaultWidthX = fdDict._defaultWidthX; - nominalWidthX = fdDict._nominalWidthX; - } else { - subrs = font.tables.cff.topDict._subrs; - subrsBias = font.tables.cff.topDict._subrsBias; - defaultWidthX = font.tables.cff.topDict._defaultWidthX; - nominalWidthX = font.tables.cff.topDict._nominalWidthX; + return sum / vs.length; +} + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); } - var width = defaultWidthX; - function newContour(x, y) { - if (open) { - p.closePath(); + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; } + } - p.moveTo(x, y); - open = true; + if (lastCharIndex < unicode) { + lastCharIndex = unicode; } - function parseStems() { - var hasWidthArg; + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); - // The number of stem operators on the stack is always even. - // If the value is uneven, that means a width is specified. - hasWidthArg = stack.length % 2 !== 0; - if (hasWidthArg && !haveWidth) { - width = stack.shift() + nominalWidthX; - } + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); - nStems += stack.length >> 1; - stack.length = 0; - haveWidth = true; - } - - function parse(code) { - var b1; - var b2; - var b3; - var b4; - var codeIndex; - var subrCode; - var jpx; - var jpy; - var c3x; - var c3y; - var c4x; - var c4y; - - var i = 0; - while (i < code.length) { - var v = code[i]; - i += 1; - switch (v) { - case 1: // hstem - parseStems(); - break; - case 3: // vstem - parseStems(); - break; - case 4: // vmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); - y += stack.pop(); - newContour(x, y); - break; - case 5: // rlineto - while (stack.length > 0) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; - break; - case 6: // hlineto - while (stack.length > 0) { - x += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); + } + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); + } + if (font.tables.cpal) { + tables.push(cpal.make(font.tables.cpal)); + } + if (font.tables.colr) { + tables.push(colr.make(font.tables.colr)); + } + if (metaTable) { + tables.push(metaTable); + } - y += stack.shift(); - p.lineTo(x, y); - } + var sfntTable = makeSfntTable(tables); - break; - case 7: // vlineto - while (stack.length > 0) { - y += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } - x += stack.shift(); - p.lineTo(x, y); - } + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; +} + +var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + +// The Layout object is the prototype of Substitution objects, and provides + +function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +// binary search in a list of ranges (coverage, class definition) +function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; + } +} - break; - case 8: // rrcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } +/** + * @exports opentype.Layout + * @class + */ +function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; +} - break; - case 10: // callsubr - codeIndex = stack.pop() + subrsBias; - subrCode = subrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } +Layout.prototype = { - break; - case 11: // return - return; - case 12: // flex operators - v = code[i]; - i += 1; - switch (v) { - case 35: // flex - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - y = c4y + stack.shift(); // dy6 - stack.shift(); // flex depth - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 34: // hflex - // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- - c1x = x + stack.shift(); // dx1 - c1y = y; // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = y; // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 36: // hflex1 - // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 37: // flex1 - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - if (Math.abs(c4x - x) > Math.abs(c4y - y)) { - x = c4x + stack.shift(); - } else { - y = c4y + stack.shift(); - } - - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - default: - console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); - stack.length = 0; - } - break; - case 14: // endchar - if (stack.length > 0 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, - if (open) { - p.closePath(); - open = false; - } + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, - break; - case 18: // hstemhm - parseStems(); - break; - case 19: // hintmask - case 20: // cntrmask - parseStems(); - i += (nStems + 7) >> 3; - break; - case 21: // rmoveto - if (stack.length > 2 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, - y += stack.pop(); - x += stack.pop(); - newContour(x, y); - break; - case 22: // hmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, - x += stack.pop(); - newContour(x, y); - break; - case 23: // vstemhm - parseStems(); - break; - case 24: // rcurveline - while (stack.length > 2) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - break; - case 25: // rlinecurve - while (stack.length > 6) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } - - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - break; - case 26: // vvcurveto - if (stack.length % 2) { - x += stack.shift(); - } - - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x; - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 27: // hhcurveto - if (stack.length % 2) { - y += stack.shift(); - } - - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y; - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 28: // shortint - b1 = code[i]; - b2 = code[i + 1]; - stack.push(((b1 << 24) | (b2 << 16)) >> 16); - i += 2; - break; - case 29: // callgsubr - codeIndex = stack.pop() + font.gsubrsBias; - subrCode = font.gsubrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } - - break; - case 30: // vhcurveto - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } - - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 31: // hvcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, - break; - default: - if (v < 32) { - console.log('Glyph ' + glyph.index + ': unknown operator ' + v); - } else if (v < 247) { - stack.push(v - 139); - } else if (v < 251) { - b1 = code[i]; - i += 1; - stack.push((v - 247) * 256 + b1 + 108); - } else if (v < 255) { - b1 = code[i]; - i += 1; - stack.push(-(v - 251) * 256 - b1 - 108); - } else { - b1 = code[i]; - b2 = code[i + 1]; - b3 = code[i + 2]; - b4 = code[i + 3]; - i += 4; - stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); - } + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; } } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } } + }, - parse(code); - - glyph.advanceWidth = width; - return p; - } - - function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { - var fdSelect = []; - var fdIndex; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - // Simple list of nGlyphs elements - for (var iGid = 0; iGid < nGlyphs; iGid++) { - fdIndex = parser.parseCard8(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); } - fdSelect.push(fdIndex); } - } else if (format === 3) { - // Ranges - var nRanges = parser.parseCard16(); - var first = parser.parseCard16(); - if (first !== 0) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; } - var next; - for (var iRange = 0; iRange < nRanges; iRange++) { - fdIndex = parser.parseCard8(); - next = parser.parseCard16(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - if (next > nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + return tables; + }, + + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; } - for (; first < next; first++) { - fdSelect.push(fdIndex); + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); } - first = next; - } - if (next !== nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); } - } else { - throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + return glyphs; } - return fdSelect; } - // Parse the `CFF` table, which contains the glyph outlines in PostScript format. - function parseCFFTable(data, start, font, opt) { - font.tables.cff = {}; - var header = parseCFFHeader(data, start); - var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); - var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); - var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); - var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); - font.gsubrs = globalSubrIndex.objects; - font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); +}; - var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); - if (topDictArray.length !== 1) { - throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); - } +// The Position object provides utility methods to manipulate - var topDict = topDictArray[0]; - font.tables.cff.topDict = topDict; +/** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Position(font) { + Layout.call(this, font, 'gpos'); +} - if (topDict._privateDict) { - font.defaultWidthX = topDict._privateDict.defaultWidthX; - font.nominalWidthX = topDict._privateDict.nominalWidthX; - } +Position.prototype = Layout.prototype; - if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { - font.isCIDFont = true; - } +/** + * Init some data for faster and easier access later. + */ +Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); +}; - if (font.isCIDFont) { - var fdArrayOffset = topDict.fdArray; - var fdSelectOffset = topDict.fdSelect; - if (fdArrayOffset === 0 || fdSelectOffset === 0) { - throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); +/** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ +Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; } - fdArrayOffset += start; - var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); - var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); - topDict._fdArray = fdArray; - fdSelectOffset += start; - topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); - } - - var privateDictOffset = start + topDict.private[1]; - var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); - font.defaultWidthX = privateDict.defaultWidthX; - font.nominalWidthX = privateDict.nominalWidthX; - - if (privateDict.subrs !== 0) { - var subrOffset = privateDictOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset); - font.subrs = subrIndex.objects; - font.subrsBias = calcCFFSubroutineBias(font.subrs); - } else { - font.subrs = []; - font.subrsBias = 0; } + } + return 0; +}; - // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. - var charStringsIndex; - if (opt.lowMemory) { - charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.offsets.length; - } else { - charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.objects.length; - } +/** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ +Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } +}; - var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); - if (topDict.encoding === 0) { - // Standard encoding - font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); - } else if (topDict.encoding === 1) { - // Expert encoding - font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); - } else { - font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); - } +// The Substitution object provides utility methods to manipulate - // Prefer the CMAP encoding to the CFF encoding. - font.encoding = font.encoding || font.cffEncoding; +/** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Substitution(font) { + Layout.call(this, font, 'gsub'); +} - font.glyphs = new glyphset.GlyphSet(font); - if (opt.lowMemory) { - font._push = function(i) { - var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - }; - } else { - for (var i = 0; i < font.nGlyphs; i += 1) { - var charString = charStringsIndex.objects[i]; - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - } +// Check if 2 arrays of primitives are equal. +function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; +} + +// Find the first subtable of a lookup table in a particular format. +function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; } } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; +} - // Convert a string to a String ID (SID). - // The list of strings is modified in place. - function encodeString(s, strings) { - var sid; +Substitution.prototype = Layout.prototype; - // Is the string in the CFF standard strings? - var i = cffStandardStrings.indexOf(s); - if (i >= 0) { - sid = i; - } +/** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ +Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; +}; - // Is the string already in the string index? - i = strings.indexOf(s); - if (i >= 0) { - sid = i + cffStandardStrings.length; - } else { - sid = cffStandardStrings.length + strings.length; - strings.push(s); +/** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } } - - return sid; } + return substitutions; +}; - function makeHeader() { - return new table.Record('Header', [ - {name: 'major', type: 'Card8', value: 1}, - {name: 'minor', type: 'Card8', value: 0}, - {name: 'hdrSize', type: 'Card8', value: 4}, - {name: 'major', type: 'Card8', value: 1} - ]); - } +/** + * List all multiple substitutions (lookup type 2) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('ccmp', 'stch') + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getMultiple = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 2); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); - function makeNameIndex(fontNames) { - var t = new table.Record('Name INDEX', [ - {name: 'names', type: 'INDEX', value: []} - ]); - t.names = []; - for (var i = 0; i < fontNames.length; i += 1) { - t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + var replacements = subtable.sequences[j]; + substitutions.push({ sub: glyph, by: replacements }); + } } + } + return substitutions; +}; - return t; +/** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ +Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } } + return alternates; +}; - // Given a dictionary's metadata, create a DICT structure. - function makeDict(meta, attrs, strings) { - var m = {}; - for (var i = 0; i < meta.length; i += 1) { - var entry = meta[i]; - var value = attrs[entry.name]; - if (value !== undefined && !equals(value, entry.value)) { - if (entry.type === 'SID') { - value = encodeString(value, strings); +/** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ +Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); } - - m[entry.op] = {name: entry.name, type: entry.type, value: value}; } } - - return m; } + return ligatures; +}; - // The Top DICT houses the global font attributes. - function makeTopDict(attrs, strings) { - var t = new table.Record('Top DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(TOP_DICT_META, attrs, strings); - return t; - } +/** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; +}; - function makeTopDictIndex(topDict) { - var t = new table.Record('Top DICT INDEX', [ - {name: 'topDicts', type: 'INDEX', value: []} - ]); - t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; - return t; - } +/** + * Add or modify a multiple substitution (lookup type 2) + * @param {string} feature - 4-letter feature name ('ccmp', 'stch') + * @param {Object} substitution - { sub: id, by: [id] } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addMultiple = function(feature, substitution, script, language) { + check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); + var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + sequences: [] + }); + check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.sequences.splice(pos, 0, 0); + } + subtable.sequences[pos] = substitution.by; +}; - function makeStringIndex(strings) { - var t = new table.Record('String INDEX', [ - {name: 'strings', type: 'INDEX', value: []} - ]); - t.strings = []; - for (var i = 0; i < strings.length; i += 1) { - t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); - } +/** + * Add or modify an alternate substitution (lookup type 3) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; +}; - return t; +/** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); } +}; - function makeGlobalSubrIndex() { - // Currently we don't use subroutines. - return new table.Record('Global Subr INDEX', [ - {name: 'subrs', type: 'INDEX', value: []} - ]); +/** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': + return this.getLigatures(feature, script, language); + case 'ccmp': + return this.getMultiple(feature, script, language) + .concat(this.getLigatures(feature, script, language)); + case 'stch': + return this.getMultiple(feature, script, language); + } + return undefined; +}; + +/** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + case 'ccmp': + if (sub.by instanceof Array) { + return this.addMultiple(feature, sub, script, language); + } + return this.addLigature(feature, sub, script, language); + } + return undefined; +}; + +function isBrowser() { + return typeof window !== 'undefined'; +} + +function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; } - function makeCharsets(glyphNames, strings) { - var t = new table.Record('Charsets', [ - {name: 'format', type: 'Card8', value: 0} - ]); - for (var i = 0; i < glyphNames.length; i += 1) { - var glyphName = glyphNames[i]; - var glyphSID = encodeString(glyphName, strings); - t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); - } - - return t; - } - - function glyphToOps(glyph) { - var ops = []; - var path = glyph.path; - ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); - var x = 0; - var y = 0; - for (var i = 0; i < path.commands.length; i += 1) { - var dx = (void 0); - var dy = (void 0); - var cmd = path.commands[i]; - if (cmd.type === 'Q') { - // CFF only supports bézier curves, so convert the quad to a bézier. - var _13 = 1 / 3; - var _23 = 2 / 3; - - // We're going to create a new command so we don't change the original path. - // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. - cmd = { - type: 'C', - x: cmd.x, - y: cmd.y, - x1: Math.round(_13 * x + _23 * cmd.x1), - y1: Math.round(_13 * y + _23 * cmd.y1), - x2: Math.round(_13 * cmd.x + _23 * cmd.x1), - y2: Math.round(_13 * cmd.y + _23 * cmd.y1) - }; - } + return ab; +} - if (cmd.type === 'M') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rmoveto', type: 'OP', value: 21}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'L') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rlineto', type: 'OP', value: 5}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'C') { - var dx1 = Math.round(cmd.x1 - x); - var dy1 = Math.round(cmd.y1 - y); - var dx2 = Math.round(cmd.x2 - cmd.x1); - var dy2 = Math.round(cmd.y2 - cmd.y1); - dx = Math.round(cmd.x - cmd.x2); - dy = Math.round(cmd.y - cmd.y2); - ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); - ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); - ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); - ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rrcurveto', type: 'OP', value: 8}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } +function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } - // Contours are closed automatically. - } + return buffer; +} - ops.push({name: 'endchar', type: 'OP', value: 14}); - return ops; +function checkArgument(expression, message) { + if (!expression) { + throw message; } +} - function makeCharStringsIndex(glyphs) { - var t = new table.Record('CharStrings INDEX', [ - {name: 'charStrings', type: 'INDEX', value: []} - ]); +// The `glyf` table describes the glyphs in TrueType outline format. - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var ops = glyphToOps(glyph); - t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); +// Parse the coordinate data for a glyph. +function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; } - return t; - } + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; +} + +// Parse a TrueType glyph. +function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } - function makePrivateDict(attrs, strings) { - var t = new table.Record('Private DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); - return t; - } - - function makeCFFTable(glyphs, options) { - var t = new table.Table('CFF ', [ - {name: 'header', type: 'RECORD'}, - {name: 'nameIndex', type: 'RECORD'}, - {name: 'topDictIndex', type: 'RECORD'}, - {name: 'stringIndex', type: 'RECORD'}, - {name: 'globalSubrIndex', type: 'RECORD'}, - {name: 'charsets', type: 'RECORD'}, - {name: 'charStringsIndex', type: 'RECORD'}, - {name: 'privateDict', type: 'RECORD'} - ]); + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); - var fontScale = 1 / options.unitsPerEm; - // We use non-zero values for the offsets so that the DICT encodes them. - // This is important because the size of the Top DICT plays a role in offset calculation, - // and the size shouldn't change after we've written correct offsets. - var attrs = { - version: options.version, - fullName: options.fullName, - familyName: options.familyName, - weight: options.weightName, - fontBBox: options.fontBBox || [0, 0, 0, 0], - fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], - charset: 999, - encoding: 0, - charStrings: 999, - private: [0, 999] - }; + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } - var privateAttrs = {}; - - var glyphNames = []; - var glyph; - - // Skip first glyph (.notdef) - for (var i = 1; i < glyphs.length; i += 1) { - glyph = glyphs.get(i); - glyphNames.push(glyph.name); - } - - var strings = []; - - t.header = makeHeader(); - t.nameIndex = makeNameIndex([options.postScriptName]); - var topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - t.globalSubrIndex = makeGlobalSubrIndex(); - t.charsets = makeCharsets(glyphNames, strings); - t.charStringsIndex = makeCharStringsIndex(glyphs); - t.privateDict = makePrivateDict(privateAttrs, strings); - - // Needs to come at the end, to encode all custom strings used in the font. - t.stringIndex = makeStringIndex(strings); - - var startOffset = t.header.sizeOf() + - t.nameIndex.sizeOf() + - t.topDictIndex.sizeOf() + - t.stringIndex.sizeOf() + - t.globalSubrIndex.sizeOf(); - attrs.charset = startOffset; - - // We use the CFF standard encoding; proper encoding will be handled in cmap. - attrs.encoding = 0; - attrs.charStrings = attrs.charset + t.charsets.sizeOf(); - attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); - - // Recreate the Top DICT INDEX with the correct offsets. - topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - - return t; - } - - var cff = { parse: parseCFFTable, make: makeCFFTable }; - - // The `head` table contains global information about the font. - - // Parse the header `head` table - function parseHeadTable(data, start) { - var head = {}; - var p = new parse.Parser(data, start); - head.version = p.parseVersion(); - head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; - head.checkSumAdjustment = p.parseULong(); - head.magicNumber = p.parseULong(); - check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); - head.flags = p.parseUShort(); - head.unitsPerEm = p.parseUShort(); - head.created = p.parseLongDateTime(); - head.modified = p.parseLongDateTime(); - head.xMin = p.parseShort(); - head.yMin = p.parseShort(); - head.xMax = p.parseShort(); - head.yMax = p.parseShort(); - head.macStyle = p.parseUShort(); - head.lowestRecPPEM = p.parseUShort(); - head.fontDirectionHint = p.parseShort(); - head.indexToLocFormat = p.parseShort(); - head.glyphDataFormat = p.parseShort(); - return head; - } - - function makeHeadTable(options) { - // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 - var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; - var createdTimestamp = timestamp; - - if (options.createdTimestamp) { - createdTimestamp = options.createdTimestamp + 2082844800; - } - - return new table.Table('head', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, - {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, - {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, - {name: 'flags', type: 'USHORT', value: 0}, - {name: 'unitsPerEm', type: 'USHORT', value: 1000}, - {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, - {name: 'modified', type: 'LONGDATETIME', value: timestamp}, - {name: 'xMin', type: 'SHORT', value: 0}, - {name: 'yMin', type: 'SHORT', value: 0}, - {name: 'xMax', type: 'SHORT', value: 0}, - {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: 0}, - {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, - {name: 'fontDirectionHint', type: 'SHORT', value: 2}, - {name: 'indexToLocFormat', type: 'SHORT', value: 0}, - {name: 'glyphDataFormat', type: 'SHORT', value: 0} - ], options); - } - - var head = { parse: parseHeadTable, make: makeHeadTable }; - - // The `hhea` table contains information for horizontal layout. - - // Parse the horizontal header `hhea` table - function parseHheaTable(data, start) { - var hhea = {}; - var p = new parse.Parser(data, start); - hhea.version = p.parseVersion(); - hhea.ascender = p.parseShort(); - hhea.descender = p.parseShort(); - hhea.lineGap = p.parseShort(); - hhea.advanceWidthMax = p.parseUShort(); - hhea.minLeftSideBearing = p.parseShort(); - hhea.minRightSideBearing = p.parseShort(); - hhea.xMaxExtent = p.parseShort(); - hhea.caretSlopeRise = p.parseShort(); - hhea.caretSlopeRun = p.parseShort(); - hhea.caretOffset = p.parseShort(); - p.relativeOffset += 8; - hhea.metricDataFormat = p.parseShort(); - hhea.numberOfHMetrics = p.parseUShort(); - return hhea; - } - - function makeHheaTable(options) { - return new table.Table('hhea', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'ascender', type: 'FWORD', value: 0}, - {name: 'descender', type: 'FWORD', value: 0}, - {name: 'lineGap', type: 'FWORD', value: 0}, - {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, - {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, - {name: 'minRightSideBearing', type: 'FWORD', value: 0}, - {name: 'xMaxExtent', type: 'FWORD', value: 0}, - {name: 'caretSlopeRise', type: 'SHORT', value: 1}, - {name: 'caretSlopeRun', type: 'SHORT', value: 0}, - {name: 'caretOffset', type: 'SHORT', value: 0}, - {name: 'reserved1', type: 'SHORT', value: 0}, - {name: 'reserved2', type: 'SHORT', value: 0}, - {name: 'reserved3', type: 'SHORT', value: 0}, - {name: 'reserved4', type: 'SHORT', value: 0}, - {name: 'metricDataFormat', type: 'SHORT', value: 0}, - {name: 'numberOfHMetrics', type: 'USHORT', value: 0} - ], options); - } - - var hhea = { parse: parseHheaTable, make: makeHheaTable }; - - // The `hmtx` table contains the horizontal metrics for all glyphs. - - function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } } - var glyph = glyphs.get(i); - glyph.advanceWidth = advanceWidth; - glyph.leftSideBearing = leftSideBearing; + glyph.points = points; + } else { + glyph.points = []; } - } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } - function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { - font._hmtxTableData = {}; + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); } - font._hmtxTableData[i] = { - advanceWidth: advanceWidth, - leftSideBearing: leftSideBearing - }; + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } } } +} - // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. - // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. - function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { - if (opt.lowMemory) - { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } - else - { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } +// Transform an array of points and return a new array. +function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); } - function makeHmtxTable(glyphs) { - var t = new table.Table('hmtx', []); - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var advanceWidth = glyph.advanceWidth || 0; - var leftSideBearing = glyph.leftSideBearing || 0; - t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); - t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + return newPoints; +} + +function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +} - return t; +// Convert the TrueType glyph outline to a Path. +function getPath(points) { + var p = new Path(); + if (!points) { + return p; } - var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + var contours = getContours(points); - // The `ltag` table stores IETF BCP-47 language tags. It allows supporting + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; - function makeLtagTable(tags) { - var result = new table.Table('ltag', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'numTags', type: 'ULONG', value: tags.length} - ]); + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; - var stringPool = ''; - var stringPoolOffset = 12 + tags.length * 4; - for (var i = 0; i < tags.length; ++i) { - var pos = stringPool.indexOf(tags[i]); - if (pos < 0) { - pos = stringPool.length; - stringPool += tags[i]; + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); } - - result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); } - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - return result; - } + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; - function parseLtagTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported ltag table version.'); - // The 'ltag' specification does not define any flags; skip the field. - p.skip('uLong', 1); - var numTags = p.parseULong(); + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; - var tags = []; - for (var i = 0; i < numTags; i++) { - var tag = ''; - var offset = start + p.parseUShort(); - var length = p.parseUShort(); - for (var j = offset; j < offset + length; ++j) { - tag += String.fromCharCode(data.getInt8(j)); - } + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } - tags.push(tag); + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } } - return tags; + p.closePath(); } + return p; +} - var ltag = { make: makeLtagTable, parse: parseLtagTable }; - - // The `maxp` table establishes the memory requirements for the font. - - // Parse the maximum profile `maxp` table. - function parseMaxpTable(data, start) { - var maxp = {}; - var p = new parse.Parser(data, start); - maxp.version = p.parseVersion(); - maxp.numGlyphs = p.parseUShort(); - if (maxp.version === 1.0) { - maxp.maxPoints = p.parseUShort(); - maxp.maxContours = p.parseUShort(); - maxp.maxCompositePoints = p.parseUShort(); - maxp.maxCompositeContours = p.parseUShort(); - maxp.maxZones = p.parseUShort(); - maxp.maxTwilightPoints = p.parseUShort(); - maxp.maxStorage = p.parseUShort(); - maxp.maxFunctionDefs = p.parseUShort(); - maxp.maxInstructionDefs = p.parseUShort(); - maxp.maxStackElements = p.parseUShort(); - maxp.maxSizeOfInstructions = p.parseUShort(); - maxp.maxComponentElements = p.parseUShort(); - maxp.maxComponentDepth = p.parseUShort(); +function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } } - - return maxp; } - function makeMaxpTable(numGlyphs) { - return new table.Table('maxp', [ - {name: 'version', type: 'FIXED', value: 0x00005000}, - {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} - ]); + return getPath(glyph.points); +} + +function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } } - var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; - - // The `name` naming table. - - // NameIDs for the name table. - var nameTableNames = [ - 'copyright', // 0 - 'fontFamily', // 1 - 'fontSubfamily', // 2 - 'uniqueID', // 3 - 'fullName', // 4 - 'version', // 5 - 'postScriptName', // 6 - 'trademark', // 7 - 'manufacturer', // 8 - 'designer', // 9 - 'description', // 10 - 'manufacturerURL', // 11 - 'designerURL', // 12 - 'license', // 13 - 'licenseURL', // 14 - 'reserved', // 15 - 'preferredFamily', // 16 - 'preferredSubfamily', // 17 - 'compatibleFullName', // 18 - 'sampleText', // 19 - 'postScriptFindFontName', // 20 - 'wwsFamily', // 21 - 'wwsSubfamily' // 22 - ]; + return glyphs; +} - var macLanguages = { - 0: 'en', - 1: 'fr', - 2: 'de', - 3: 'it', - 4: 'nl', - 5: 'sv', - 6: 'es', - 7: 'da', - 8: 'pt', - 9: 'no', - 10: 'he', - 11: 'ja', - 12: 'ar', - 13: 'fi', - 14: 'el', - 15: 'is', - 16: 'mt', - 17: 'tr', - 18: 'hr', - 19: 'zh-Hant', - 20: 'ur', - 21: 'hi', - 22: 'th', - 23: 'ko', - 24: 'lt', - 25: 'pl', - 26: 'hu', - 27: 'es', - 28: 'lv', - 29: 'se', - 30: 'fo', - 31: 'fa', - 32: 'ru', - 33: 'zh', - 34: 'nl-BE', - 35: 'ga', - 36: 'sq', - 37: 'ro', - 38: 'cz', - 39: 'sk', - 40: 'si', - 41: 'yi', - 42: 'sr', - 43: 'mk', - 44: 'bg', - 45: 'uk', - 46: 'be', - 47: 'uz', - 48: 'kk', - 49: 'az-Cyrl', - 50: 'az-Arab', - 51: 'hy', - 52: 'ka', - 53: 'mo', - 54: 'ky', - 55: 'tg', - 56: 'tk', - 57: 'mn-CN', - 58: 'mn', - 59: 'ps', - 60: 'ks', - 61: 'ku', - 62: 'sd', - 63: 'bo', - 64: 'ne', - 65: 'sa', - 66: 'mr', - 67: 'bn', - 68: 'as', - 69: 'gu', - 70: 'pa', - 71: 'or', - 72: 'ml', - 73: 'kn', - 74: 'ta', - 75: 'te', - 76: 'si', - 77: 'my', - 78: 'km', - 79: 'lo', - 80: 'vi', - 81: 'id', - 82: 'tl', - 83: 'ms', - 84: 'ms-Arab', - 85: 'am', - 86: 'ti', - 87: 'om', - 88: 'so', - 89: 'sw', - 90: 'rw', - 91: 'rn', - 92: 'ny', - 93: 'mg', - 94: 'eo', - 128: 'cy', - 129: 'eu', - 130: 'ca', - 131: 'la', - 132: 'qu', - 133: 'gn', - 134: 'ay', - 135: 'tt', - 136: 'ug', - 137: 'dz', - 138: 'jv', - 139: 'su', - 140: 'gl', - 141: 'af', - 142: 'br', - 143: 'iu', - 144: 'gd', - 145: 'gv', - 146: 'ga', - 147: 'to', - 148: 'el-polyton', - 149: 'kl', - 150: 'az', - 151: 'nn' - }; +function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); - // MacOS language ID → MacOS script ID - // - // Note that the script ID is not sufficient to determine what encoding - // to use in TrueType files. For some languages, MacOS used a modification - // of a mainstream script. For example, an Icelandic name would be stored - // with smRoman in the TrueType naming table, but the actual encoding - // is a special Icelandic version of the normal Macintosh Roman encoding. - // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal - // Syllables but MacOS had run out of available script codes, so this was - // done as a (pretty radical) "modification" of Ethiopic. - // - // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt - var macLanguageToScript = { - 0: 0, // langEnglish → smRoman - 1: 0, // langFrench → smRoman - 2: 0, // langGerman → smRoman - 3: 0, // langItalian → smRoman - 4: 0, // langDutch → smRoman - 5: 0, // langSwedish → smRoman - 6: 0, // langSpanish → smRoman - 7: 0, // langDanish → smRoman - 8: 0, // langPortuguese → smRoman - 9: 0, // langNorwegian → smRoman - 10: 5, // langHebrew → smHebrew - 11: 1, // langJapanese → smJapanese - 12: 4, // langArabic → smArabic - 13: 0, // langFinnish → smRoman - 14: 6, // langGreek → smGreek - 15: 0, // langIcelandic → smRoman (modified) - 16: 0, // langMaltese → smRoman - 17: 0, // langTurkish → smRoman (modified) - 18: 0, // langCroatian → smRoman (modified) - 19: 2, // langTradChinese → smTradChinese - 20: 4, // langUrdu → smArabic - 21: 9, // langHindi → smDevanagari - 22: 21, // langThai → smThai - 23: 3, // langKorean → smKorean - 24: 29, // langLithuanian → smCentralEuroRoman - 25: 29, // langPolish → smCentralEuroRoman - 26: 29, // langHungarian → smCentralEuroRoman - 27: 29, // langEstonian → smCentralEuroRoman - 28: 29, // langLatvian → smCentralEuroRoman - 29: 0, // langSami → smRoman - 30: 0, // langFaroese → smRoman (modified) - 31: 4, // langFarsi → smArabic (modified) - 32: 7, // langRussian → smCyrillic - 33: 25, // langSimpChinese → smSimpChinese - 34: 0, // langFlemish → smRoman - 35: 0, // langIrishGaelic → smRoman (modified) - 36: 0, // langAlbanian → smRoman - 37: 0, // langRomanian → smRoman (modified) - 38: 29, // langCzech → smCentralEuroRoman - 39: 29, // langSlovak → smCentralEuroRoman - 40: 0, // langSlovenian → smRoman (modified) - 41: 5, // langYiddish → smHebrew - 42: 7, // langSerbian → smCyrillic - 43: 7, // langMacedonian → smCyrillic - 44: 7, // langBulgarian → smCyrillic - 45: 7, // langUkrainian → smCyrillic (modified) - 46: 7, // langByelorussian → smCyrillic - 47: 7, // langUzbek → smCyrillic - 48: 7, // langKazakh → smCyrillic - 49: 7, // langAzerbaijani → smCyrillic - 50: 4, // langAzerbaijanAr → smArabic - 51: 24, // langArmenian → smArmenian - 52: 23, // langGeorgian → smGeorgian - 53: 7, // langMoldavian → smCyrillic - 54: 7, // langKirghiz → smCyrillic - 55: 7, // langTajiki → smCyrillic - 56: 7, // langTurkmen → smCyrillic - 57: 27, // langMongolian → smMongolian - 58: 7, // langMongolianCyr → smCyrillic - 59: 4, // langPashto → smArabic - 60: 4, // langKurdish → smArabic - 61: 4, // langKashmiri → smArabic - 62: 4, // langSindhi → smArabic - 63: 26, // langTibetan → smTibetan - 64: 9, // langNepali → smDevanagari - 65: 9, // langSanskrit → smDevanagari - 66: 9, // langMarathi → smDevanagari - 67: 13, // langBengali → smBengali - 68: 13, // langAssamese → smBengali - 69: 11, // langGujarati → smGujarati - 70: 10, // langPunjabi → smGurmukhi - 71: 12, // langOriya → smOriya - 72: 17, // langMalayalam → smMalayalam - 73: 16, // langKannada → smKannada - 74: 14, // langTamil → smTamil - 75: 15, // langTelugu → smTelugu - 76: 18, // langSinhalese → smSinhalese - 77: 19, // langBurmese → smBurmese - 78: 20, // langKhmer → smKhmer - 79: 22, // langLao → smLao - 80: 30, // langVietnamese → smVietnamese - 81: 0, // langIndonesian → smRoman - 82: 0, // langTagalog → smRoman - 83: 0, // langMalayRoman → smRoman - 84: 4, // langMalayArabic → smArabic - 85: 28, // langAmharic → smEthiopic - 86: 28, // langTigrinya → smEthiopic - 87: 28, // langOromo → smEthiopic - 88: 0, // langSomali → smRoman - 89: 0, // langSwahili → smRoman - 90: 0, // langKinyarwanda → smRoman - 91: 0, // langRundi → smRoman - 92: 0, // langNyanja → smRoman - 93: 0, // langMalagasy → smRoman - 94: 0, // langEsperanto → smRoman - 128: 0, // langWelsh → smRoman (modified) - 129: 0, // langBasque → smRoman - 130: 0, // langCatalan → smRoman - 131: 0, // langLatin → smRoman - 132: 0, // langQuechua → smRoman - 133: 0, // langGuarani → smRoman - 134: 0, // langAymara → smRoman - 135: 7, // langTatar → smCyrillic - 136: 4, // langUighur → smArabic - 137: 26, // langDzongkha → smTibetan - 138: 0, // langJavaneseRom → smRoman - 139: 0, // langSundaneseRom → smRoman - 140: 0, // langGalician → smRoman - 141: 0, // langAfrikaans → smRoman - 142: 0, // langBreton → smRoman (modified) - 143: 28, // langInuktitut → smEthiopic (modified) - 144: 0, // langScottishGaelic → smRoman (modified) - 145: 0, // langManxGaelic → smRoman (modified) - 146: 0, // langIrishGaelicScript → smRoman (modified) - 147: 0, // langTongan → smRoman - 148: 6, // langGreekAncient → smRoman - 149: 0, // langGreenlandic → smRoman - 150: 0, // langAzerbaijanRoman → smRoman - 151: 0 // langNynorsk → smRoman - }; + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; + + return glyphs; +} + +// Parse all the glyphs according to the offsets from the `loca` table. +function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } +} + +var glyf = { getPath: getPath, parse: parseGlyfTable}; + +/* A TrueType font hinting interpreter. +* +* (c) 2017 Axel Kittenberger +* +* This interpreter has been implemented according to this documentation: +* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html +* +* According to the documentation F24DOT6 values are used for pixels. +* That means calculation is 1/64 pixel accurate and uses integer operations. +* However, Javascript has floating point operations by default and only +* those are available. One could make a case to simulate the 1/64 accuracy +* exactly by truncating after every division operation +* (for example with << 0) to get pixel exactly results as other TrueType +* implementations. It may make sense since some fonts are pixel optimized +* by hand using DELTAP instructions. The current implementation doesn't +* and rather uses full floating point precision. +* +* xScale, yScale and rotation is currently ignored. +* +* A few non-trivial instructions are missing as I didn't encounter yet +* a font that used them to test a possible implementation. +* +* Some fonts seem to use undocumented features regarding the twilight zone. +* Only some of them are implemented as they were encountered. +* +* The exports.DEBUG statements are removed on the minified distribution file. +*/ + +var instructionTable; +var exec; +var execGlyph; +var execComponent; + +/* +* Creates a hinting object. +* +* There ought to be exactly one +* for each truetype font that is used for hinting. +*/ +function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; + + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; +} + +/* +* Not rounding. +*/ +function roundOff(v) { + return v; +} + +/* +* Rounding to grid. +*/ +function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); +} + +/* +* Rounding to double grid. +*/ +function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; +} + +/* +* Rounding to half grid. +*/ +function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); +} + +/* +* Rounding to up to grid. +*/ +function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); +} + +/* +* Rounding to down to grid. +*/ +function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); +} + +/* +* Super rounding. +*/ +var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + + if (v < 0) { + v = -v; + sign = -1; + } + + v += threshold - phase; + + v = Math.trunc(v / period) * period; + + v += phase; + + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } + + return v * sign; +}; - // While Microsoft indicates a region/country for all its language - // IDs, we omit the region code if it's equal to the "most likely - // region subtag" according to Unicode CLDR. For scripts, we omit - // the subtag if it is equal to the Suppress-Script entry in the - // IANA language subtag registry for IETF BCP 47. - // - // For example, Microsoft states that its language code 0x041A is - // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' - // and not 'hr-HR' because Croatia is the default country for Croatian, - // according to Unicode CLDR. As another example, Microsoft states - // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform - // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script - // for the Croatian language, according to IANA. +/* +* Unit vector of x-axis. +*/ +var xUnitVector = { + x: 1, + + y: 0, + + axis: 'x', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. // - // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html - // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry - var windowsLanguages = { - 0x0436: 'af', - 0x041C: 'sq', - 0x0484: 'gsw', - 0x045E: 'am', - 0x1401: 'ar-DZ', - 0x3C01: 'ar-BH', - 0x0C01: 'ar', - 0x0801: 'ar-IQ', - 0x2C01: 'ar-JO', - 0x3401: 'ar-KW', - 0x3001: 'ar-LB', - 0x1001: 'ar-LY', - 0x1801: 'ary', - 0x2001: 'ar-OM', - 0x4001: 'ar-QA', - 0x0401: 'ar-SA', - 0x2801: 'ar-SY', - 0x1C01: 'aeb', - 0x3801: 'ar-AE', - 0x2401: 'ar-YE', - 0x042B: 'hy', - 0x044D: 'as', - 0x082C: 'az-Cyrl', - 0x042C: 'az', - 0x046D: 'ba', - 0x042D: 'eu', - 0x0423: 'be', - 0x0845: 'bn', - 0x0445: 'bn-IN', - 0x201A: 'bs-Cyrl', - 0x141A: 'bs', - 0x047E: 'br', - 0x0402: 'bg', - 0x0403: 'ca', - 0x0C04: 'zh-HK', - 0x1404: 'zh-MO', - 0x0804: 'zh', - 0x1004: 'zh-SG', - 0x0404: 'zh-TW', - 0x0483: 'co', - 0x041A: 'hr', - 0x101A: 'hr-BA', - 0x0405: 'cs', - 0x0406: 'da', - 0x048C: 'prs', - 0x0465: 'dv', - 0x0813: 'nl-BE', - 0x0413: 'nl', - 0x0C09: 'en-AU', - 0x2809: 'en-BZ', - 0x1009: 'en-CA', - 0x2409: 'en-029', - 0x4009: 'en-IN', - 0x1809: 'en-IE', - 0x2009: 'en-JM', - 0x4409: 'en-MY', - 0x1409: 'en-NZ', - 0x3409: 'en-PH', - 0x4809: 'en-SG', - 0x1C09: 'en-ZA', - 0x2C09: 'en-TT', - 0x0809: 'en-GB', - 0x0409: 'en', - 0x3009: 'en-ZW', - 0x0425: 'et', - 0x0438: 'fo', - 0x0464: 'fil', - 0x040B: 'fi', - 0x080C: 'fr-BE', - 0x0C0C: 'fr-CA', - 0x040C: 'fr', - 0x140C: 'fr-LU', - 0x180C: 'fr-MC', - 0x100C: 'fr-CH', - 0x0462: 'fy', - 0x0456: 'gl', - 0x0437: 'ka', - 0x0C07: 'de-AT', - 0x0407: 'de', - 0x1407: 'de-LI', - 0x1007: 'de-LU', - 0x0807: 'de-CH', - 0x0408: 'el', - 0x046F: 'kl', - 0x0447: 'gu', - 0x0468: 'ha', - 0x040D: 'he', - 0x0439: 'hi', - 0x040E: 'hu', - 0x040F: 'is', - 0x0470: 'ig', - 0x0421: 'id', - 0x045D: 'iu', - 0x085D: 'iu-Latn', - 0x083C: 'ga', - 0x0434: 'xh', - 0x0435: 'zu', - 0x0410: 'it', - 0x0810: 'it-CH', - 0x0411: 'ja', - 0x044B: 'kn', - 0x043F: 'kk', - 0x0453: 'km', - 0x0486: 'quc', - 0x0487: 'rw', - 0x0441: 'sw', - 0x0457: 'kok', - 0x0412: 'ko', - 0x0440: 'ky', - 0x0454: 'lo', - 0x0426: 'lv', - 0x0427: 'lt', - 0x082E: 'dsb', - 0x046E: 'lb', - 0x042F: 'mk', - 0x083E: 'ms-BN', - 0x043E: 'ms', - 0x044C: 'ml', - 0x043A: 'mt', - 0x0481: 'mi', - 0x047A: 'arn', - 0x044E: 'mr', - 0x047C: 'moh', - 0x0450: 'mn', - 0x0850: 'mn-CN', - 0x0461: 'ne', - 0x0414: 'nb', - 0x0814: 'nn', - 0x0482: 'oc', - 0x0448: 'or', - 0x0463: 'ps', - 0x0415: 'pl', - 0x0416: 'pt', - 0x0816: 'pt-PT', - 0x0446: 'pa', - 0x046B: 'qu-BO', - 0x086B: 'qu-EC', - 0x0C6B: 'qu', - 0x0418: 'ro', - 0x0417: 'rm', - 0x0419: 'ru', - 0x243B: 'smn', - 0x103B: 'smj-NO', - 0x143B: 'smj', - 0x0C3B: 'se-FI', - 0x043B: 'se', - 0x083B: 'se-SE', - 0x203B: 'sms', - 0x183B: 'sma-NO', - 0x1C3B: 'sms', - 0x044F: 'sa', - 0x1C1A: 'sr-Cyrl-BA', - 0x0C1A: 'sr', - 0x181A: 'sr-Latn-BA', - 0x081A: 'sr-Latn', - 0x046C: 'nso', - 0x0432: 'tn', - 0x045B: 'si', - 0x041B: 'sk', - 0x0424: 'sl', - 0x2C0A: 'es-AR', - 0x400A: 'es-BO', - 0x340A: 'es-CL', - 0x240A: 'es-CO', - 0x140A: 'es-CR', - 0x1C0A: 'es-DO', - 0x300A: 'es-EC', - 0x440A: 'es-SV', - 0x100A: 'es-GT', - 0x480A: 'es-HN', - 0x080A: 'es-MX', - 0x4C0A: 'es-NI', - 0x180A: 'es-PA', - 0x3C0A: 'es-PY', - 0x280A: 'es-PE', - 0x500A: 'es-PR', - - // Microsoft has defined two different language codes for - // “Spanish with modern sorting” and “Spanish with traditional - // sorting”. This makes sense for collation APIs, and it would be - // possible to express this in BCP 47 language tags via Unicode - // extensions (eg., es-u-co-trad is Spanish with traditional - // sorting). However, for storing names in fonts, the distinction - // does not make sense, so we give “es” in both cases. - 0x0C0A: 'es', - 0x040A: 'es', - - 0x540A: 'es-US', - 0x380A: 'es-UY', - 0x200A: 'es-VE', - 0x081D: 'sv-FI', - 0x041D: 'sv', - 0x045A: 'syr', - 0x0428: 'tg', - 0x085F: 'tzm', - 0x0449: 'ta', - 0x0444: 'tt', - 0x044A: 'te', - 0x041E: 'th', - 0x0451: 'bo', - 0x041F: 'tr', - 0x0442: 'tk', - 0x0480: 'ug', - 0x0422: 'uk', - 0x042E: 'hsb', - 0x0420: 'ur', - 0x0843: 'uz-Cyrl', - 0x0443: 'uz', - 0x042A: 'vi', - 0x0452: 'cy', - 0x0488: 'wo', - 0x0485: 'sah', - 0x0478: 'ii', - 0x046A: 'yo' - }; + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; - // Returns a IETF BCP 47 language code, for example 'zh-Hant' - // for 'Chinese in the traditional script'. - function getLanguageCode(platformID, languageID, ltag) { - switch (platformID) { - case 0: // Unicode - if (languageID === 0xFFFF) { - return 'und'; - } else if (ltag) { - return ltag[languageID]; - } + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; - break; + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } - case 1: // Macintosh - return macLanguages[languageID]; + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; - case 3: // Windows - return windowsLanguages[languageID]; + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; } - return undefined; - } + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, - var utf16 = 'utf-16'; - - // MacOS script ID → encoding. This table stores the default case, - // which can be overridden by macLanguageEncodings. - var macScriptEncodings = { - 0: 'macintosh', // smRoman - 1: 'x-mac-japanese', // smJapanese - 2: 'x-mac-chinesetrad', // smTradChinese - 3: 'x-mac-korean', // smKorean - 6: 'x-mac-greek', // smGreek - 7: 'x-mac-cyrillic', // smCyrillic - 9: 'x-mac-devanagai', // smDevanagari - 10: 'x-mac-gurmukhi', // smGurmukhi - 11: 'x-mac-gujarati', // smGujarati - 12: 'x-mac-oriya', // smOriya - 13: 'x-mac-bengali', // smBengali - 14: 'x-mac-tamil', // smTamil - 15: 'x-mac-telugu', // smTelugu - 16: 'x-mac-kannada', // smKannada - 17: 'x-mac-malayalam', // smMalayalam - 18: 'x-mac-sinhalese', // smSinhalese - 19: 'x-mac-burmese', // smBurmese - 20: 'x-mac-khmer', // smKhmer - 21: 'x-mac-thai', // smThai - 22: 'x-mac-lao', // smLao - 23: 'x-mac-georgian', // smGeorgian - 24: 'x-mac-armenian', // smArmenian - 25: 'x-mac-chinesesimp', // smSimpChinese - 26: 'x-mac-tibetan', // smTibetan - 27: 'x-mac-mongolian', // smMongolian - 28: 'x-mac-ethiopic', // smEthiopic - 29: 'x-mac-ce', // smCentralEuroRoman - 30: 'x-mac-vietnamese', // smVietnamese - 31: 'x-mac-extarabic' // smExtArabic - }; + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, - // MacOS language ID → encoding. This table stores the exceptional - // cases, which override macScriptEncodings. For writing MacOS naming - // tables, we need to emit a MacOS script ID. Therefore, we cannot - // merge macScriptEncodings into macLanguageEncodings. + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. // - // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt - var macLanguageEncodings = { - 15: 'x-mac-icelandic', // langIcelandic - 17: 'x-mac-turkish', // langTurkish - 18: 'x-mac-croatian', // langCroatian - 24: 'x-mac-ce', // langLithuanian - 25: 'x-mac-ce', // langPolish - 26: 'x-mac-ce', // langHungarian - 27: 'x-mac-ce', // langEstonian - 28: 'x-mac-ce', // langLatvian - 30: 'x-mac-icelandic', // langFaroese - 37: 'x-mac-romanian', // langRomanian - 38: 'x-mac-ce', // langCzech - 39: 'x-mac-ce', // langSlovak - 40: 'x-mac-ce', // langSlovenian - 143: 'x-mac-inuit', // langInuktitut - 146: 'x-mac-gaelic' // langIrishGaelicScript - }; + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } - function getEncoding(platformID, encodingID, languageID) { - switch (platformID) { - case 0: // Unicode - return utf16; + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; - case 1: // Apple Macintosh - return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, - case 3: // Microsoft Windows - if (encodingID === 1 || encodingID === 10) { - return utf16; - } + // Slope of vector line. + slope: 0, - break; - } + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, - return undefined; + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; } +}; - // Parse the naming `name` table. - // FIXME: Format 1 additional fields are not supported yet. - // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. - function parseNameTable(data, start, ltag) { - var name = {}; - var p = new parse.Parser(data, start); - var format = p.parseUShort(); - var count = p.parseUShort(); - var stringOffset = p.offset + p.parseUShort(); - for (var i = 0; i < count; i++) { - var platformID = p.parseUShort(); - var encodingID = p.parseUShort(); - var languageID = p.parseUShort(); - var nameID = p.parseUShort(); - var property = nameTableNames[nameID] || nameID; - var byteLength = p.parseUShort(); - var offset = p.parseUShort(); - var language = getLanguageCode(platformID, languageID, ltag); - var encoding = getEncoding(platformID, encodingID, languageID); - if (encoding !== undefined && language !== undefined) { - var text = (void 0); - if (encoding === utf16) { - text = decode.UTF16(data, stringOffset + offset, byteLength); - } else { - text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); - } +/* +* Unit vector of y-axis. +*/ +var yUnitVector = { + x: 0, - if (text) { - var translations = name[property]; - if (translations === undefined) { - translations = name[property] = {}; - } + y: 1, - translations[language] = text; - } + axis: 'y', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; } + + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; } - var langTagCount = 0; - if (format === 1) { - // FIXME: Also handle Microsoft's 'name' table 1. - langTagCount = p.parseUShort(); + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; } - return name; - } + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, - // {23: 'foo'} → {'foo': 23} - // ['bar', 'baz'] → {'bar': 0, 'baz': 1} - function reverseDict(dict) { - var result = {}; - for (var key in dict) { - result[dict[key]] = parseInt(key); + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; } - return result; - } + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; - function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { - return new table.Record('NameRecord', [ - {name: 'platformID', type: 'USHORT', value: platformID}, - {name: 'encodingID', type: 'USHORT', value: encodingID}, - {name: 'languageID', type: 'USHORT', value: languageID}, - {name: 'nameID', type: 'USHORT', value: nameID}, - {name: 'length', type: 'USHORT', value: length}, - {name: 'offset', type: 'USHORT', value: offset} - ]); - } + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, - // Finds the position of needle in haystack, or -1 if not there. - // Like String.indexOf(), but for arrays. - function findSubArray(needle, haystack) { - var needleLength = needle.length; - var limit = haystack.length - needleLength + 1; + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, - loop: - for (var pos = 0; pos < limit; pos++) { - for (; pos < limit; pos++) { - for (var k = 0; k < needleLength; k++) { - if (haystack[pos + k] !== needle[k]) { - continue loop; - } - } + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, - return pos; - } - } + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, - return -1; + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; } +}; - function addStringToPool(s, pool) { - var offset = findSubArray(s, pool); - if (offset < 0) { - offset = pool.length; - var i = 0; - var len = s.length; - for (; i < len; ++i) { - pool.push(s[i]); - } +Object.freeze(xUnitVector); +Object.freeze(yUnitVector); + +/* +* Creates a unit vector that is not x- or y-axis. +*/ +function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); +} + +/* +* Gets the projected distance between two points. +* o1/o2 ... if true, respective original position is used. +*/ +UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); +}; - } +/* +* Moves point p so the moved position has the same relative +* position to the moved positions of rp1 and rp2 than the +* original positions had. +* +* See APPENDIX on INTERPOLATE at the bottom of this file. +*/ +UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); +}; - return offset; - } +/* +* Sets the point 'p' relative to point 'rp' +* by the distance 'd' +* +* See APPENDIX on SETRELATIVE at the bottom of this file. +* +* p ... point to set +* rp ... reference point +* d ... distance on projection vector +* pv ... projection vector (undefined = this) +* org ... if true, uses the original position of rp as reference. +*/ +UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + var pvns = pv.normalSlope; + var fvs = this.slope; + + var px = p.x; + var py = p.y; + + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; +}; - function makeNameTable(names, ltag) { - var nameID; - var nameIDs = []; +/* +* Touches the point p. +*/ +UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; +}; - var namesWithNumericKeys = {}; - var nameTableIds = reverseDict(nameTableNames); - for (var key in names) { - var id = nameTableIds[key]; - if (id === undefined) { - id = key; - } +/* +* Returns a unit vector with x/y coordinates. +*/ +function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); + + x /= d; + y /= d; + + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } +} + +/* +* Creates a point in the hinting engine. +*/ +function HPoint( + x, + y, + lastPointOfContour, + onCurve +) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; + + Object.preventExtensions(this); +} + +/* +* Returns the next touched point on the contour. +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } + + return p; +}; - nameID = parseInt(id); +/* +* Returns the previous touched point on the contour +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; - if (isNaN(nameID)) { - throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); - } + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } - namesWithNumericKeys[nameID] = names[key]; - nameIDs.push(nameID); - } - - var macLanguageIds = reverseDict(macLanguages); - var windowsLanguageIds = reverseDict(windowsLanguages); - - var nameRecords = []; - var stringPool = []; - - for (var i = 0; i < nameIDs.length; i++) { - nameID = nameIDs[i]; - var translations = namesWithNumericKeys[nameID]; - for (var lang in translations) { - var text = translations[lang]; - - // For MacOS, we try to emit the name in the form that was introduced - // in the initial version of the TrueType spec (in the late 1980s). - // However, this can fail for various reasons: the requested BCP 47 - // language code might not have an old-style Mac equivalent; - // we might not have a codec for the needed character encoding; - // or the name might contain characters that cannot be expressed - // in the old-style Macintosh encoding. In case of failure, we emit - // the name in a more modern fashion (Unicode encoding with BCP 47 - // language tags) that is recognized by MacOS 10.5, released in 2009. - // If fonts were only read by operating systems, we could simply - // emit all names in the modern form; this would be much easier. - // However, there are many applications and libraries that read - // 'name' tables directly, and these will usually only recognize - // the ancient form (silently skipping the unrecognized names). - var macPlatform = 1; // Macintosh - var macLanguage = macLanguageIds[lang]; - var macScript = macLanguageToScript[macLanguage]; - var macEncoding = getEncoding(macPlatform, macScript, macLanguage); - var macName = encode.MACSTRING(text, macEncoding); - if (macName === undefined) { - macPlatform = 0; // Unicode - macLanguage = ltag.indexOf(lang); - if (macLanguage < 0) { - macLanguage = ltag.length; - ltag.push(lang); - } + return p; +}; - macScript = 4; // Unicode 2.0 and later - macName = encode.UTF16(text); - } +/* +* The zero point. +*/ +var HPZero = Object.freeze(new HPoint(0, 0)); + +/* +* The default state of the interpreter. +* +* Note: Freezing the defaultState and then deriving from it +* makes the V8 Javascript engine going awkward, +* so this is avoided, albeit the defaultState shouldn't +* ever change. +*/ +var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true +}; - var macNameOffset = addStringToPool(macName, stringPool); - nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, - nameID, macName.length, macNameOffset)); +/* +* The current state of the interpreter. +* +* env ... 'fpgm' or 'prep' or 'glyf' +* prog ... the program +*/ +function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } +} + +/* +* Executes a glyph program. +* +* This does the hinting for each glyph. +* +* Returns an array of moved points. +* +* glyph: the glyph to hint +* ppem: the size the glyph is rendered for +*/ +Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } + + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } + + var font = this.font; + var prepState = this._prepState; + + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; + + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); + + fpgmState.funcs = [ ]; + fpgmState.font = font; - var winLanguage = windowsLanguageIds[lang]; - if (winLanguage !== undefined) { - var winName = encode.UTF16(text); - var winNameOffset = addStringToPool(winName, stringPool); - nameRecords.push(makeNameRecord(3, 1, winLanguage, - nameID, winName.length, winNameOffset)); - } + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } + + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; } } - nameRecords.sort(function(a, b) { - return ((a.platformID - b.platformID) || - (a.encodingID - b.encodingID) || - (a.languageID - b.languageID) || - (a.nameID - b.nameID)); - }); + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. - var t = new table.Table('name', [ - {name: 'format', type: 'USHORT', value: 0}, - {name: 'count', type: 'USHORT', value: nameRecords.length}, - {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} - ]); + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); - for (var r = 0; r < nameRecords.length; r++) { - t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); - } - - t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); - return t; - } - - var _name = { parse: parseNameTable, make: makeNameTable }; - - // The `OS/2` table contains metrics required in OpenType fonts. - - var unicodeRanges = [ - {begin: 0x0000, end: 0x007F}, // Basic Latin - {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement - {begin: 0x0100, end: 0x017F}, // Latin Extended-A - {begin: 0x0180, end: 0x024F}, // Latin Extended-B - {begin: 0x0250, end: 0x02AF}, // IPA Extensions - {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters - {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks - {begin: 0x0370, end: 0x03FF}, // Greek and Coptic - {begin: 0x2C80, end: 0x2CFF}, // Coptic - {begin: 0x0400, end: 0x04FF}, // Cyrillic - {begin: 0x0530, end: 0x058F}, // Armenian - {begin: 0x0590, end: 0x05FF}, // Hebrew - {begin: 0xA500, end: 0xA63F}, // Vai - {begin: 0x0600, end: 0x06FF}, // Arabic - {begin: 0x07C0, end: 0x07FF}, // NKo - {begin: 0x0900, end: 0x097F}, // Devanagari - {begin: 0x0980, end: 0x09FF}, // Bengali - {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi - {begin: 0x0A80, end: 0x0AFF}, // Gujarati - {begin: 0x0B00, end: 0x0B7F}, // Oriya - {begin: 0x0B80, end: 0x0BFF}, // Tamil - {begin: 0x0C00, end: 0x0C7F}, // Telugu - {begin: 0x0C80, end: 0x0CFF}, // Kannada - {begin: 0x0D00, end: 0x0D7F}, // Malayalam - {begin: 0x0E00, end: 0x0E7F}, // Thai - {begin: 0x0E80, end: 0x0EFF}, // Lao - {begin: 0x10A0, end: 0x10FF}, // Georgian - {begin: 0x1B00, end: 0x1B7F}, // Balinese - {begin: 0x1100, end: 0x11FF}, // Hangul Jamo - {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional - {begin: 0x1F00, end: 0x1FFF}, // Greek Extended - {begin: 0x2000, end: 0x206F}, // General Punctuation - {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts - {begin: 0x20A0, end: 0x20CF}, // Currency Symbol - {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols - {begin: 0x2100, end: 0x214F}, // Letterlike Symbols - {begin: 0x2150, end: 0x218F}, // Number Forms - {begin: 0x2190, end: 0x21FF}, // Arrows - {begin: 0x2200, end: 0x22FF}, // Mathematical Operators - {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical - {begin: 0x2400, end: 0x243F}, // Control Pictures - {begin: 0x2440, end: 0x245F}, // Optical Character Recognition - {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics - {begin: 0x2500, end: 0x257F}, // Box Drawing - {begin: 0x2580, end: 0x259F}, // Block Elements - {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes - {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols - {begin: 0x2700, end: 0x27BF}, // Dingbats - {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation - {begin: 0x3040, end: 0x309F}, // Hiragana - {begin: 0x30A0, end: 0x30FF}, // Katakana - {begin: 0x3100, end: 0x312F}, // Bopomofo - {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo - {begin: 0xA840, end: 0xA87F}, // Phags-pa - {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months - {begin: 0x3300, end: 0x33FF}, // CJK Compatibility - {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables - {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * - {begin: 0x10900, end: 0x1091F}, // Phoenicia - {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs - {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) - {begin: 0x31C0, end: 0x31EF}, // CJK Strokes - {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms - {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A - {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks - {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms - {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants - {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B - {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms - {begin: 0xFFF0, end: 0xFFFF}, // Specials - {begin: 0x0F00, end: 0x0FFF}, // Tibetan - {begin: 0x0700, end: 0x074F}, // Syriac - {begin: 0x0780, end: 0x07BF}, // Thaana - {begin: 0x0D80, end: 0x0DFF}, // Sinhala - {begin: 0x1000, end: 0x109F}, // Myanmar - {begin: 0x1200, end: 0x137F}, // Ethiopic - {begin: 0x13A0, end: 0x13FF}, // Cherokee - {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics - {begin: 0x1680, end: 0x169F}, // Ogham - {begin: 0x16A0, end: 0x16FF}, // Runic - {begin: 0x1780, end: 0x17FF}, // Khmer - {begin: 0x1800, end: 0x18AF}, // Mongolian - {begin: 0x2800, end: 0x28FF}, // Braille Patterns - {begin: 0xA000, end: 0xA48F}, // Yi Syllables - {begin: 0x1700, end: 0x171F}, // Tagalog - {begin: 0x10300, end: 0x1032F}, // Old Italic - {begin: 0x10330, end: 0x1034F}, // Gothic - {begin: 0x10400, end: 0x1044F}, // Deseret - {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols - {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols - {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) - {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors - {begin: 0xE0000, end: 0xE007F}, // Tags - {begin: 0x1900, end: 0x194F}, // Limbu - {begin: 0x1950, end: 0x197F}, // Tai Le - {begin: 0x1980, end: 0x19DF}, // New Tai Lue - {begin: 0x1A00, end: 0x1A1F}, // Buginese - {begin: 0x2C00, end: 0x2C5F}, // Glagolitic - {begin: 0x2D30, end: 0x2D7F}, // Tifinagh - {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols - {begin: 0xA800, end: 0xA82F}, // Syloti Nagri - {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary - {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers - {begin: 0x10380, end: 0x1039F}, // Ugaritic - {begin: 0x103A0, end: 0x103DF}, // Old Persian - {begin: 0x10450, end: 0x1047F}, // Shavian - {begin: 0x10480, end: 0x104AF}, // Osmanya - {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary - {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi - {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols - {begin: 0x12000, end: 0x123FF}, // Cuneiform - {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals - {begin: 0x1B80, end: 0x1BBF}, // Sundanese - {begin: 0x1C00, end: 0x1C4F}, // Lepcha - {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki - {begin: 0xA880, end: 0xA8DF}, // Saurashtra - {begin: 0xA900, end: 0xA92F}, // Kayah Li - {begin: 0xA930, end: 0xA95F}, // Rejang - {begin: 0xAA00, end: 0xAA5F}, // Cham - {begin: 0x10190, end: 0x101CF}, // Ancient Symbols - {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc - {begin: 0x102A0, end: 0x102DF}, // Carian - {begin: 0x1F030, end: 0x1F09F} // Domino Tiles - ]; + prepState.ppem = ppem; - function getUnicodeRange(unicode) { - for (var i = 0; i < unicodeRanges.length; i += 1) { - var range = unicodeRanges[i]; - if (unicode >= range.begin && unicode < range.end) { - return i; + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; } + } else { + prepState.cvt = []; } - return -1; - } - - // Parse the OS/2 and Windows metrics `OS/2` table - function parseOS2Table(data, start) { - var os2 = {}; - var p = new parse.Parser(data, start); - os2.version = p.parseUShort(); - os2.xAvgCharWidth = p.parseShort(); - os2.usWeightClass = p.parseUShort(); - os2.usWidthClass = p.parseUShort(); - os2.fsType = p.parseUShort(); - os2.ySubscriptXSize = p.parseShort(); - os2.ySubscriptYSize = p.parseShort(); - os2.ySubscriptXOffset = p.parseShort(); - os2.ySubscriptYOffset = p.parseShort(); - os2.ySuperscriptXSize = p.parseShort(); - os2.ySuperscriptYSize = p.parseShort(); - os2.ySuperscriptXOffset = p.parseShort(); - os2.ySuperscriptYOffset = p.parseShort(); - os2.yStrikeoutSize = p.parseShort(); - os2.yStrikeoutPosition = p.parseShort(); - os2.sFamilyClass = p.parseShort(); - os2.panose = []; - for (var i = 0; i < 10; i++) { - os2.panose[i] = p.parseByte(); - } - - os2.ulUnicodeRange1 = p.parseULong(); - os2.ulUnicodeRange2 = p.parseULong(); - os2.ulUnicodeRange3 = p.parseULong(); - os2.ulUnicodeRange4 = p.parseULong(); - os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); - os2.fsSelection = p.parseUShort(); - os2.usFirstCharIndex = p.parseUShort(); - os2.usLastCharIndex = p.parseUShort(); - os2.sTypoAscender = p.parseShort(); - os2.sTypoDescender = p.parseShort(); - os2.sTypoLineGap = p.parseShort(); - os2.usWinAscent = p.parseUShort(); - os2.usWinDescent = p.parseUShort(); - if (os2.version >= 1) { - os2.ulCodePageRange1 = p.parseULong(); - os2.ulCodePageRange2 = p.parseULong(); - } - - if (os2.version >= 2) { - os2.sxHeight = p.parseShort(); - os2.sCapHeight = p.parseShort(); - os2.usDefaultChar = p.parseUShort(); - os2.usBreakChar = p.parseUShort(); - os2.usMaxContent = p.parseUShort(); - } - - return os2; - } - - function makeOS2Table(options) { - return new table.Table('OS/2', [ - {name: 'version', type: 'USHORT', value: 0x0003}, - {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, - {name: 'usWeightClass', type: 'USHORT', value: 0}, - {name: 'usWidthClass', type: 'USHORT', value: 0}, - {name: 'fsType', type: 'USHORT', value: 0}, - {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, - {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, - {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, - {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, - {name: 'sFamilyClass', type: 'SHORT', value: 0}, - {name: 'bFamilyType', type: 'BYTE', value: 0}, - {name: 'bSerifStyle', type: 'BYTE', value: 0}, - {name: 'bWeight', type: 'BYTE', value: 0}, - {name: 'bProportion', type: 'BYTE', value: 0}, - {name: 'bContrast', type: 'BYTE', value: 0}, - {name: 'bStrokeVariation', type: 'BYTE', value: 0}, - {name: 'bArmStyle', type: 'BYTE', value: 0}, - {name: 'bLetterform', type: 'BYTE', value: 0}, - {name: 'bMidline', type: 'BYTE', value: 0}, - {name: 'bXHeight', type: 'BYTE', value: 0}, - {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, - {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, - {name: 'fsSelection', type: 'USHORT', value: 0}, - {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, - {name: 'usLastCharIndex', type: 'USHORT', value: 0}, - {name: 'sTypoAscender', type: 'SHORT', value: 0}, - {name: 'sTypoDescender', type: 'SHORT', value: 0}, - {name: 'sTypoLineGap', type: 'SHORT', value: 0}, - {name: 'usWinAscent', type: 'USHORT', value: 0}, - {name: 'usWinDescent', type: 'USHORT', value: 0}, - {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, - {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, - {name: 'sxHeight', type: 'SHORT', value: 0}, - {name: 'sCapHeight', type: 'SHORT', value: 0}, - {name: 'usDefaultChar', type: 'USHORT', value: 0}, - {name: 'usBreakChar', type: 'USHORT', value: 0}, - {name: 'usMaxContext', type: 'USHORT', value: 0} - ], options); - } - - var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; - - // The `post` table stores additional PostScript information, such as glyph names. - - // Parse the PostScript `post` table - function parsePostTable(data, start) { - var post = {}; - var p = new parse.Parser(data, start); - post.version = p.parseVersion(); - post.italicAngle = p.parseFixed(); - post.underlinePosition = p.parseShort(); - post.underlineThickness = p.parseShort(); - post.isFixedPitch = p.parseULong(); - post.minMemType42 = p.parseULong(); - post.maxMemType42 = p.parseULong(); - post.minMemType1 = p.parseULong(); - post.maxMemType1 = p.parseULong(); - switch (post.version) { - case 1: - post.names = standardNames.slice(); - break; - case 2: - post.numberOfGlyphs = p.parseUShort(); - post.glyphNameIndex = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - post.glyphNameIndex[i] = p.parseUShort(); - } + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } - post.names = []; - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - if (post.glyphNameIndex[i$1] >= standardNames.length) { - var nameLength = p.parseChar(); - post.names.push(p.parseString(nameLength)); - } - } + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } - break; - case 2.5: - post.numberOfGlyphs = p.parseUShort(); - post.offset = new Array(post.numberOfGlyphs); - for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { - post.offset[i$2] = p.parseChar(); - } + if (this._errorState > 1) { return; } - break; + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); } - return post; + this._errorState = 1; + return undefined; } +}; - function makePostTable() { - return new table.Table('post', [ - {name: 'version', type: 'FIXED', value: 0x00030000}, - {name: 'italicAngle', type: 'FIXED', value: 0}, - {name: 'underlinePosition', type: 'FWORD', value: 0}, - {name: 'underlineThickness', type: 'FWORD', value: 0}, - {name: 'isFixedPitch', type: 'ULONG', value: 0}, - {name: 'minMemType42', type: 'ULONG', value: 0}, - {name: 'maxMemType42', type: 'ULONG', value: 0}, - {name: 'minMemType1', type: 'ULONG', value: 0}, - {name: 'maxMemType1', type: 'ULONG', value: 0} - ]); - } +/* +* Executes the hinting program for a glyph. +*/ +execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); - var post = { parse: parsePostTable, make: makePostTable }; + state = new State('glyf', cg.instructions); - // The `GSUB` table contains ligatures, among other things. + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } - var subtableParsers = new Array(9); // subtableParsers[0] is unused + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS - subtableParsers[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - deltaGlyphId: this.parseUShort() - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - substitute: this.parseOffset16List() - }; + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } } - check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); - }; - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS - subtableParsers[2] = function parseLookup2() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - sequences: this.parseListOfLists() - }; - }; + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS - subtableParsers[3] = function parseLookup3() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - alternateSets: this.parseListOfLists() - }; - }; + state.gZone = state.z0 = state.z1 = state.z2 = gZone; - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS - subtableParsers[4] = function parseLookup4() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ligatureSets: this.parseListOfLists(function() { - return { - ligGlyph: this.parseUShort(), - components: this.parseUShortList(this.parseUShort() - 1) - }; - }) - }; - }; + state.contours = contours; - var lookupRecordDesc = { - sequenceIndex: Parser.uShort, - lookupListIndex: Parser.uShort - }; + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF - subtableParsers[5] = function parseLookup5() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } - if (substFormat === 1) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ruleSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - input: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - classDef: this.parsePointer(Parser.classDef), - classSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - classes: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - substFormat: substFormat, - coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); - }; + exec(state); - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC - subtableParsers[6] = function parseLookup6() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - chainRuleSets: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - backtrackClassDef: this.parsePointer(Parser.classDef), - inputClassDef: this.parsePointer(Parser.classDef), - lookaheadClassDef: this.parsePointer(Parser.classDef), - chainClassSet: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - return { - substFormat: 3, - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; + gZone.length -= 2; } - check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); - }; + } - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES - subtableParsers[7] = function parseLookup7() { - // Extension Substitution subtable - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); - var extensionLookupType = this.parseUShort(); - var extensionParser = new Parser(this.data, this.offset + this.parseULong()); - return { - substFormat: 1, - lookupType: extensionLookupType, - extension: subtableParsers[extensionLookupType].call(extensionParser) - }; - }; + return gZone; +}; - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS - subtableParsers[8] = function parseLookup8() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - substitutes: this.parseUShortList() - }; - }; +/* +* Executes the hinting program for a component of a multi-component glyph +* or of the glyph itself for a non-component glyph. +*/ +execComponent = function(glyph, state, xScale, yScale) +{ + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } - // https://www.microsoft.com/typography/OTSPEC/gsub.htm - function parseGsubTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers), - variations: p.parseFeatureVariationsList() - }; + // Chain links the contours. + var sp; // start point + var np; // next point + + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + + if (!sp) { + sp = cp; + contours.push(i$1); } + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } } - // GSUB Writing ////////////////////////////////////////////// - var subtableMakers = new Array(9); + if (state.inhibitGridFit) { return; } - subtableMakers[1] = function makeLookup1(subtable) { - if (subtable.substFormat === 1) { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, - {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} - ]); - } else { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 2}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.ushortList('substitute', subtable.substitute))); + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); } - }; + } - subtableMakers[2] = function makeLookup2(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { - return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); - }))); - }; + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); - subtableMakers[3] = function makeLookup3(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { - return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); - }))); - }; - - subtableMakers[4] = function makeLookup4(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { - return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { - return new table.Table('ligatureTable', - [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] - .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) - ); - })); - }))); - }; + exec(state); - subtableMakers[6] = function makeLookup6(subtable) { - if (subtable.substFormat === 1) { - var returnTable = new table.Table('chainContextTable', [ - {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { - return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { - var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) - .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) - .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) - .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); - - chainRule.lookupRecords.forEach(function (record, i) { - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); - }); - return new table.Table('chainRuleTable', tableData); - })); - }))); - return returnTable; - } else if (subtable.substFormat === 2) { - check.assert(false, 'lookup type 6 format 2 is not yet supported.'); - } else if (subtable.substFormat === 3) { - var tableData = [ - {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; - - tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); - subtable.backtrackCoverage.forEach(function (coverage, i) { - tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); - }); - tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); - subtable.inputCoverage.forEach(function (coverage, i) { - tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); - }); - tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); - subtable.lookaheadCoverage.forEach(function (coverage, i) { - tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); - }); - - tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); - subtable.lookupRecords.forEach(function (record, i) { - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); - }); - - var returnTable$1 = new table.Table('chainContextTable', tableData); - - return returnTable$1; - } - - check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); - }; + // Removes the extra points. + gZone.length -= 2; - function makeGsubTable(gsub) { - return new table.Table('GSUB', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} - ]); + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } } +}; - var gsub = { parse: parseGsubTable, make: makeGsubTable }; +/* +* Executes the program loaded in state. +*/ +exec = function(state) { + var prog = state.prog; - // The `GPOS` table contains kerning pairs, among other things. + if (!prog) { return; } - // Parse the metadata `meta` table. - // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html - function parseMetaTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported META table version.'); - p.parseULong(); // flags - currently unused and set to 0 - p.parseULong(); // tableOffset - var numDataMaps = p.parseULong(); + var pLen = prog.length; + var ins; - var tags = {}; - for (var i = 0; i < numDataMaps; i++) { - var tag = p.parseTag(); - var dataOffset = p.parseULong(); - var dataLength = p.parseULong(); - var text = decode.UTF8(data, start + dataOffset, dataLength); + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; - tags[tag] = text; + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); } - return tags; - } - function makeMetaTable(tags) { - var numTags = Object.keys(tags).length; - var stringPool = ''; - var stringPoolOffset = 16 + numTags * 12; + ins(state); - var result = new table.Table('meta', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'offset', type: 'ULONG', value: stringPoolOffset}, - {name: 'numTags', type: 'ULONG', value: numTags} - ]); + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } - for (var tag in tags) { - var pos = stringPool.length; - stringPool += tags[tag]; + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } - result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); - result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } } + */ + } +}; + +/* +* Initializes the twilight zone. +* +* This is only done if a SZPx instruction +* refers to the twilight zone. +*/ +function initTZone(state) +{ + var tZone = state.tZone = new Array(state.gZone.length); + + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } +} - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); +/* +* Skips the instruction pointer ahead over an IF/ELSE block. +* handleElse .. if true breaks on matching ELSE +*/ +function skip(state, handleElse) +{ + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; - return result; + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); + + state.ip = ip; +} + +/*----------------------------------------------------------* +* And then a lot of instructions... * +*----------------------------------------------------------*/ + +// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis +// 0x00-0x01 +function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + + state.fv = state.pv = state.dpv = v; +} + +// SPVTCA[a] Set Projection Vector to Coordinate Axis +// 0x02-0x03 +function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + + state.pv = state.dpv = v; +} + +// SFVTCA[a] Set Freedom Vector to Coordinate Axis +// 0x04-0x05 +function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + + state.fv = v; +} + +// SPVTL[a] Set Projection Vector To Line +// 0x06-0x07 +function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.pv = state.dpv = getUnitVector(dx, dy); +} + +// SFVTL[a] Set Freedom Vector To Line +// 0x08-0x09 +function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.fv = getUnitVector(dx, dy); +} + +// SPVFS[] Set Projection Vector From Stack +// 0x0A +function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.pv = state.dpv = getUnitVector(x, y); +} + +// SFVFS[] Set Freedom Vector From Stack +// 0x0B +function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.fv = getUnitVector(x, y); +} + +// GPV[] Get Projection Vector +// 0x0C +function GPV(state) { + var stack = state.stack; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); +} + +// GFV[] Get Freedom Vector +// 0x0C +function GFV(state) { + var stack = state.stack; + var fv = state.fv; + + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); +} + +// SFVTPV[] Set Freedom Vector To Projection Vector +// 0x0E +function SFVTPV(state) { + state.fv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } +} + +// ISECT[] moves point p to the InterSECTion of two lines +// 0x0F +function ISECT(state) +{ + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; +} + +// SRP0[] Set Reference Point 0 +// 0x10 +function SRP0(state) { + state.rp0 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } +} + +// SRP1[] Set Reference Point 1 +// 0x11 +function SRP1(state) { + state.rp1 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } +} + +// SRP1[] Set Reference Point 2 +// 0x12 +function SRP2(state) { + state.rp2 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } +} + +// SZP0[] Set Zone Pointer 0 +// 0x13 +function SZP0(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } + + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); } +} - var meta = { parse: parseMetaTable, make: makeMetaTable }; +// SZP1[] Set Zone Pointer 1 +// 0x14 +function SZP1(state) { + var n = state.stack.pop(); - // The `COLR` table adds support for multi-colored glyphs + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } - function parseColrTable(data, start) { - var p = new Parser(data, start); - var version = p.parseUShort(); - check.argument(version === 0x0000, 'Only COLRv0 supported.'); - var numBaseGlyphRecords = p.parseUShort(); - var baseGlyphRecordsOffset = p.parseOffset32(); - var layerRecordsOffset = p.parseOffset32(); - var numLayerRecords = p.parseUShort(); - p.relativeOffset = baseGlyphRecordsOffset; - var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, { - glyphID: Parser.uShort, - firstLayerIndex: Parser.uShort, - numLayers: Parser.uShort, - }); - p.relativeOffset = layerRecordsOffset; - var layerRecords = p.parseRecordList(numLayerRecords, { - glyphID: Parser.uShort, - paletteIndex: Parser.uShort - }); + state.zp1 = n; - return { - version: version, - baseGlyphRecords: baseGlyphRecords, - layerRecords: layerRecords, - }; + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); } +} + +// SZP2[] Set Zone Pointer 2 +// 0x15 +function SZP2(state) { + var n = state.stack.pop(); - function makeColrTable(ref) { - var version = ref.version; if ( version === void 0 ) version = 0x0000; - var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = []; - var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = []; + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } - check.argument(version === 0x0000, 'Only COLRv0 supported.'); - var baseGlyphRecordsOffset = 14; - var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6); - return new table.Table('COLR', [ - { name: 'version', type: 'USHORT', value: version }, - { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length }, - { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset }, - { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset }, - { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [ - { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID }, - { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex }, - { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(), - layerRecords.map(function (layer, i) { return [ - { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID }, - { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() )); + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); } +} - var colr = { parse: parseColrTable, make: makeColrTable }; +// SZPS[] Set Zone PointerS +// 0x16 +function SZPS(state) { + var n = state.stack.pop(); - // The `CPAL` define a contiguous list of colors (colorRecords) + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } - // Parse the header `head` table - function parseCpalTable(data, start) { - var p = new Parser(data, start); - var version = p.parseShort(); - var numPaletteEntries = p.parseShort(); - var numPalettes = p.parseShort(); - var numColorRecords = p.parseShort(); - var colorRecordsArrayOffset = p.parseOffset32(); - var colorRecordIndices = p.parseUShortList(numPalettes); - p.relativeOffset = colorRecordsArrayOffset; - var colorRecords = p.parseULongList(numColorRecords); - return { - version: version, - numPaletteEntries: numPaletteEntries, - colorRecords: colorRecords, - colorRecordIndices: colorRecordIndices, - }; + state.zp0 = state.zp1 = state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); } +} - function makeCpalTable(ref) { - var version = ref.version; if ( version === void 0 ) version = 0; - var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0; - var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = []; - var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0]; +// SLOOP[] Set LOOP variable +// 0x17 +function SLOOP(state) { + state.loop = state.stack.pop(); - check.argument(version === 0, 'Only CPALv0 are supported.'); - check.argument(colorRecords.length, 'No colorRecords given.'); - check.argument(colorRecordIndices.length, 'No colorRecordIndices given.'); - check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices'); - return new table.Table('CPAL', [ - { name: 'version', type: 'USHORT', value: version }, - { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length }, - { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length }, - { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length }, - { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }), - colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) )); - } + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } +} - var cpal = { parse: parseCpalTable, make: makeCpalTable }; +// RTG[] Round To Grid +// 0x18 +function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } - // The `sfnt` wrapper provides organization for the tables in the font. + state.round = roundToGrid; +} - function log2(v) { - return Math.log(v) / Math.log(2) | 0; - } +// RTHG[] Round To Half Grid +// 0x19 +function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } - function computeCheckSum(bytes) { - while (bytes.length % 4 !== 0) { - bytes.push(0); - } + state.round = roundToHalfGrid; +} - var sum = 0; - for (var i = 0; i < bytes.length; i += 4) { - sum += (bytes[i] << 24) + - (bytes[i + 1] << 16) + - (bytes[i + 2] << 8) + - (bytes[i + 3]); - } +// SMD[] Set Minimum Distance +// 0x1A +function SMD(state) { + var d = state.stack.pop(); - sum %= Math.pow(2, 32); - return sum; - } + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } - function makeTableRecord(tag, checkSum, offset, length) { - return new table.Record('Table Record', [ - {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, - {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, - {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, - {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} - ]); - } + state.minDis = d / 0x40; +} - function makeSfntTable(tables) { - var sfnt = new table.Table('sfnt', [ - {name: 'version', type: 'TAG', value: 'OTTO'}, - {name: 'numTables', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); - sfnt.tables = tables; - sfnt.numTables = tables.length; - var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); - sfnt.searchRange = 16 * highestPowerOf2; - sfnt.entrySelector = log2(highestPowerOf2); - sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; +// ELSE[] ELSE clause +// 0x1B +function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] - var recordFields = []; - var tableFields = []; + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } - var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } + skip(state, false); +} - for (var i = 0; i < tables.length; i += 1) { - var t = tables[i]; - check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); - var tableLength = t.sizeOf(); - var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); - recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); - tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); - offset += tableLength; - check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - } +// JMPR[] JuMP Relative +// 0x1C +function JMPR(state) { + var o = state.stack.pop(); - // Table records need to be sorted alphabetically. - recordFields.sort(function(r1, r2) { - if (r1.value.tag > r2.value.tag) { - return 1; - } else { - return -1; - } - }); + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } - sfnt.fields = sfnt.fields.concat(recordFields); - sfnt.fields = sfnt.fields.concat(tableFields); - return sfnt; - } + // A jump by 1 would do nothing. + state.ip += o - 1; +} - // Get the metrics for a character. If the string has more than one character - // this function returns metrics for the first available character. - // You can provide optional fallback metrics if no characters are available. - function metricsForChar(font, chars, notFoundMetrics) { - for (var i = 0; i < chars.length; i += 1) { - var glyphIndex = font.charToGlyphIndex(chars[i]); - if (glyphIndex > 0) { - var glyph = font.glyphs.get(glyphIndex); - return glyph.getMetrics(); - } - } +// SCVTCI[] Set Control Value Table Cut-In +// 0x1D +function SCVTCI(state) { + var n = state.stack.pop(); - return notFoundMetrics; - } + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } - function average(vs) { - var sum = 0; - for (var i = 0; i < vs.length; i += 1) { - sum += vs[i]; - } + state.cvCutIn = n / 0x40; +} - return sum / vs.length; - } +// DUP[] DUPlicate top stack element +// 0x20 +function DUP(state) { + var stack = state.stack; - // Convert the font object to a SFNT data structure. - // This structure contains all the necessary tables and metadata to create a binary OTF file. - function fontToSfntTable(font) { - var xMins = []; - var yMins = []; - var xMaxs = []; - var yMaxs = []; - var advanceWidths = []; - var leftSideBearings = []; - var rightSideBearings = []; - var firstCharIndex; - var lastCharIndex = 0; - var ulUnicodeRange1 = 0; - var ulUnicodeRange2 = 0; - var ulUnicodeRange3 = 0; - var ulUnicodeRange4 = 0; + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } - for (var i = 0; i < font.glyphs.length; i += 1) { - var glyph = font.glyphs.get(i); - var unicode = glyph.unicode | 0; + stack.push(stack[stack.length - 1]); +} - if (isNaN(glyph.advanceWidth)) { - throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); - } +// POP[] POP top stack element +// 0x21 +function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } - if (firstCharIndex > unicode || firstCharIndex === undefined) { - // ignore .notdef char - if (unicode > 0) { - firstCharIndex = unicode; - } - } + state.stack.pop(); +} - if (lastCharIndex < unicode) { - lastCharIndex = unicode; - } +// CLEAR[] CLEAR the stack +// 0x22 +function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } - var position = os2.getUnicodeRange(unicode); - if (position < 32) { - ulUnicodeRange1 |= 1 << position; - } else if (position < 64) { - ulUnicodeRange2 |= 1 << position - 32; - } else if (position < 96) { - ulUnicodeRange3 |= 1 << position - 64; - } else if (position < 123) { - ulUnicodeRange4 |= 1 << position - 96; - } else { - throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); - } - // Skip non-important characters. - if (glyph.name === '.notdef') { continue; } - var metrics = glyph.getMetrics(); - xMins.push(metrics.xMin); - yMins.push(metrics.yMin); - xMaxs.push(metrics.xMax); - yMaxs.push(metrics.yMax); - leftSideBearings.push(metrics.leftSideBearing); - rightSideBearings.push(metrics.rightSideBearing); - advanceWidths.push(glyph.advanceWidth); - } - - var globals = { - xMin: Math.min.apply(null, xMins), - yMin: Math.min.apply(null, yMins), - xMax: Math.max.apply(null, xMaxs), - yMax: Math.max.apply(null, yMaxs), - advanceWidthMax: Math.max.apply(null, advanceWidths), - advanceWidthAvg: average(advanceWidths), - minLeftSideBearing: Math.min.apply(null, leftSideBearings), - maxLeftSideBearing: Math.max.apply(null, leftSideBearings), - minRightSideBearing: Math.min.apply(null, rightSideBearings) - }; - globals.ascender = font.ascender; - globals.descender = font.descender; - - var headTable = head.make({ - flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) - unitsPerEm: font.unitsPerEm, - xMin: globals.xMin, - yMin: globals.yMin, - xMax: globals.xMax, - yMax: globals.yMax, - lowestRecPPEM: 3, - createdTimestamp: font.createdTimestamp - }); + state.stack.length = 0; +} - var hheaTable = hhea.make({ - ascender: globals.ascender, - descender: globals.descender, - advanceWidthMax: globals.advanceWidthMax, - minLeftSideBearing: globals.minLeftSideBearing, - minRightSideBearing: globals.minRightSideBearing, - xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length - }); +// SWAP[] SWAP the top two elements on the stack +// 0x23 +function SWAP(state) { + var stack = state.stack; - var maxpTable = maxp.make(font.glyphs.length); - - var os2Table = os2.make(Object.assign({ - xAvgCharWidth: Math.round(globals.advanceWidthAvg), - usFirstCharIndex: firstCharIndex, - usLastCharIndex: lastCharIndex, - ulUnicodeRange1: ulUnicodeRange1, - ulUnicodeRange2: ulUnicodeRange2, - ulUnicodeRange3: ulUnicodeRange3, - ulUnicodeRange4: ulUnicodeRange4, - // See http://typophile.com/node/13081 for more info on vertical metrics. - // We get metrics for typical characters (such as "x" for xHeight). - // We provide some fallback characters if characters are unavailable: their - // ordering was chosen experimentally. - sTypoAscender: globals.ascender, - sTypoDescender: globals.descender, - sTypoLineGap: 0, - usWinAscent: globals.yMax, - usWinDescent: Math.abs(globals.yMin), - ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now - sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, - sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, - usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. - usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. - }, font.tables.os2)); - - var hmtxTable = hmtx.make(font.glyphs); - var cmapTable = cmap.make(font.glyphs); - - var englishFamilyName = font.getEnglishName('fontFamily'); - var englishStyleName = font.getEnglishName('fontSubfamily'); - var englishFullName = englishFamilyName + ' ' + englishStyleName; - var postScriptName = font.getEnglishName('postScriptName'); - if (!postScriptName) { - postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; - } - - var names = {}; - for (var n in font.names) { - names[n] = font.names[n]; - } - - if (!names.uniqueID) { - names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; - } - - if (!names.postScriptName) { - names.postScriptName = {en: postScriptName}; - } - - if (!names.preferredFamily) { - names.preferredFamily = font.names.fontFamily; - } - - if (!names.preferredSubfamily) { - names.preferredSubfamily = font.names.fontSubfamily; - } - - var languageTags = []; - var nameTable = _name.make(names, languageTags); - var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); - - var postTable = post.make(); - var cffTable = cff.make(font.glyphs, { - version: font.getEnglishName('version'), - fullName: englishFullName, - familyName: englishFamilyName, - weightName: englishStyleName, - postScriptName: postScriptName, - unitsPerEm: font.unitsPerEm, - fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] - }); + var a = stack.pop(); + var b = stack.pop(); - var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } - // The order does not matter because makeSfntTable() will sort them. - var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; - if (ltagTable) { - tables.push(ltagTable); - } - // Optional tables - if (font.tables.gsub) { - tables.push(gsub.make(font.tables.gsub)); - } - if (font.tables.cpal) { - tables.push(cpal.make(font.tables.cpal)); - } - if (font.tables.colr) { - tables.push(colr.make(font.tables.colr)); - } - if (metaTable) { - tables.push(metaTable); - } + stack.push(a); + stack.push(b); +} - var sfntTable = makeSfntTable(tables); +// DEPTH[] DEPTH of the stack +// 0x24 +function DEPTH(state) { + var stack = state.stack; - // Compute the font's checkSum and store it in head.checkSumAdjustment. - var bytes = sfntTable.encode(); - var checkSum = computeCheckSum(bytes); - var tableFields = sfntTable.fields; - var checkSumAdjusted = false; - for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { - if (tableFields[i$1].name === 'head table') { - tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; - checkSumAdjusted = true; - break; - } - } + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } - if (!checkSumAdjusted) { - throw new Error('Could not find head table with checkSum to adjust.'); - } - - return sfntTable; - } - - var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; - - // The Layout object is the prototype of Substitution objects, and provides - - function searchTag(arr, tag) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid].tag; - if (val === tag) { - return imid; - } else if (val < tag) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; - } - - function binSearch(arr, value) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid]; - if (val === value) { - return imid; - } else if (val < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; - } - - // binary search in a list of ranges (coverage, class definition) - function searchRange(ranges, value) { - // jshint bitwise: false - var range; - var imin = 0; - var imax = ranges.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - range = ranges[imid]; - var start = range.start; - if (start === value) { - return range; - } else if (start < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - if (imin > 0) { - range = ranges[imin - 1]; - if (value > range.end) { return 0; } - return range; - } - } + stack.push(stack.length); +} - /** - * @exports opentype.Layout - * @class - */ - function Layout(font, tableName) { - this.font = font; - this.tableName = tableName; +// LOOPCALL[] LOOPCALL function +// 0x2A +function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } } - Layout.prototype = { + // restores the callers program + state.ip = cip; + state.prog = cprog; +} - /** - * Binary search an object by "tag" property - * @instance - * @function searchTag - * @memberof opentype.Layout - * @param {Array} arr - * @param {string} tag - * @return {number} - */ - searchTag: searchTag, +// CALL[] CALL function +// 0x2B +function CALL(state) { + var fn = state.stack.pop(); - /** - * Binary search in a list of numbers - * @instance - * @function binSearch - * @memberof opentype.Layout - * @param {Array} arr - * @param {number} value - * @return {number} - */ - binSearch: binSearch, + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } - /** - * Get or create the Layout table (GSUB, GPOS etc). - * @param {boolean} create - Whether to create a new one. - * @return {Object} The GSUB or GPOS table. - */ - getTable: function(create) { - var layout = this.font.tables[this.tableName]; - if (!layout && create) { - layout = this.font.tables[this.tableName] = this.createDefaultTable(); - } - return layout; - }, + // saves callers program + var cip = state.ip; + var cprog = state.prog; - /** - * Returns all scripts in the substitution table. - * @instance - * @return {Array} - */ - getScriptNames: function() { - var layout = this.getTable(); - if (!layout) { return []; } - return layout.scripts.map(function(script) { - return script.tag; - }); - }, + state.prog = state.funcs[fn]; - /** - * Returns the best bet for a script name. - * Returns 'DFLT' if it exists. - * If not, returns 'latn' if it exists. - * If neither exist, returns undefined. - */ - getDefaultScriptName: function() { - var layout = this.getTable(); - if (!layout) { return; } - var hasLatn = false; - for (var i = 0; i < layout.scripts.length; i++) { - var name = layout.scripts[i].tag; - if (name === 'DFLT') { return name; } - if (name === 'latn') { hasLatn = true; } - } - if (hasLatn) { return 'latn'; } - }, + // executes the function + exec(state); - /** - * Returns all LangSysRecords in the given script. - * @instance - * @param {string} [script='DFLT'] - * @param {boolean} create - forces the creation of this script table if it doesn't exist. - * @return {Object} An object with tag and script properties. - */ - getScriptTable: function(script, create) { - var layout = this.getTable(create); - if (layout) { - script = script || 'DFLT'; - var scripts = layout.scripts; - var pos = searchTag(layout.scripts, script); - if (pos >= 0) { - return scripts[pos].script; - } else if (create) { - var scr = { - tag: script, - script: { - defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, - langSysRecords: [] - } - }; - scripts.splice(-1 - pos, 0, scr); - return scr.script; - } - } - }, + // restores the callers program + state.ip = cip; + state.prog = cprog; - /** - * Returns a language system table - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. - * @return {Object} - */ - getLangSysTable: function(script, language, create) { - var scriptTable = this.getScriptTable(script, create); - if (scriptTable) { - if (!language || language === 'dflt' || language === 'DFLT') { - return scriptTable.defaultLangSys; - } - var pos = searchTag(scriptTable.langSysRecords, language); - if (pos >= 0) { - return scriptTable.langSysRecords[pos].langSys; - } else if (create) { - var langSysRecord = { - tag: language, - langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} - }; - scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); - return langSysRecord.langSys; - } - } - }, + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } +} - /** - * Get a specific feature table. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm - * @param {boolean} create - forces the creation of the feature table if it doesn't exist. - * @return {Object} - */ - getFeatureTable: function(script, language, feature, create) { - var langSysTable = this.getLangSysTable(script, language, create); - if (langSysTable) { - var featureRecord; - var featIndexes = langSysTable.featureIndexes; - var allFeatures = this.font.tables[this.tableName].features; - // The FeatureIndex array of indices is in arbitrary order, - // even if allFeatures is sorted alphabetically by feature tag. - for (var i = 0; i < featIndexes.length; i++) { - featureRecord = allFeatures[featIndexes[i]]; - if (featureRecord.tag === feature) { - return featureRecord.feature; - } - } - if (create) { - var index = allFeatures.length; - // Automatic ordering of features would require to shift feature indexes in the script list. - check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); - featureRecord = { - tag: feature, - feature: { params: 0, lookupListIndexes: [] } - }; - allFeatures.push(featureRecord); - featIndexes.push(index); - return featureRecord.feature; - } - } - }, +// CINDEX[] Copy the INDEXed element to the top of the stack +// 0x25 +function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); - /** - * Get the lookup tables of a given type for a script/language/feature. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - 4-letter feature code - * @param {number} lookupType - 1 to 9 - * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. - * @return {Object[]} - */ - getLookupTables: function(script, language, feature, lookupType, create) { - var featureTable = this.getFeatureTable(script, language, feature, create); - var tables = []; - if (featureTable) { - var lookupTable; - var lookupListIndexes = featureTable.lookupListIndexes; - var allLookups = this.font.tables[this.tableName].lookups; - // lookupListIndexes are in no particular order, so use naive search. - for (var i = 0; i < lookupListIndexes.length; i++) { - lookupTable = allLookups[lookupListIndexes[i]]; - if (lookupTable.lookupType === lookupType) { - tables.push(lookupTable); - } - } - if (tables.length === 0 && create) { - lookupTable = { - lookupType: lookupType, - lookupFlag: 0, - subtables: [], - markFilteringSet: undefined - }; - var index = allLookups.length; - allLookups.push(lookupTable); - lookupListIndexes.push(index); - return [lookupTable]; - } - } - return tables; - }, + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } - /** - * Find a glyph in a class definition table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table - * @param {object} classDefTable - an OpenType Layout class definition table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getGlyphClass: function(classDefTable, glyphIndex) { - switch (classDefTable.format) { - case 1: - if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { - return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; - } - return 0; - case 2: - var range = searchRange(classDefTable.ranges, glyphIndex); - return range ? range.classId : 0; - } - }, + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); +} - /** - * Find a glyph in a coverage table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table - * @param {object} coverageTable - an OpenType Layout coverage table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getCoverageIndex: function(coverageTable, glyphIndex) { - switch (coverageTable.format) { - case 1: - var index = binSearch(coverageTable.glyphs, glyphIndex); - return index >= 0 ? index : -1; - case 2: - var range = searchRange(coverageTable.ranges, glyphIndex); - return range ? range.index + glyphIndex - range.start : -1; - } - }, +// MINDEX[] Move the INDEXed element to the top of the stack +// 0x26 +function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); - /** - * Returns the list of glyph indexes of a coverage table. - * Format 1: the list is stored raw - * Format 2: compact list as range records. - * @instance - * @param {Object} coverageTable - * @return {Array} - */ - expandCoverage: function(coverageTable) { - if (coverageTable.format === 1) { - return coverageTable.glyphs; - } else { - var glyphs = []; - var ranges = coverageTable.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - var start = range.start; - var end = range.end; - for (var j = start; j <= end; j++) { - glyphs.push(j); - } - } - return glyphs; - } - } + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } - }; + stack.push(stack.splice(stack.length - k, 1)[0]); +} - // The Position object provides utility methods to manipulate +// FDEF[] Function DEFinition +// 0x2C +function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; - /** - * @exports opentype.Position - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ - function Position(font) { - Layout.call(this, font, 'gpos'); - } + var fn = stack.pop(); + var ipBegin = ip; - Position.prototype = Layout.prototype; + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } - /** - * Init some data for faster and easier access later. - */ - Position.prototype.init = function() { - var script = this.getDefaultScriptName(); - this.defaultKerningTables = this.getKerningTables(script); - }; + while (prog[++ip] !== 0x2D){ } - /** - * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. - * - * @param {integer} leftIndex - left glyph index - * @param {integer} rightIndex - right glyph index - * @returns {integer} - */ - Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { - for (var i = 0; i < kerningLookups.length; i++) { - var subtables = kerningLookups[i].subtables; - for (var j = 0; j < subtables.length; j++) { - var subtable = subtables[j]; - var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); - if (covIndex < 0) { continue; } - switch (subtable.posFormat) { - case 1: - // Search Pair Adjustment Positioning Format 1 - var pairSet = subtable.pairSets[covIndex]; - for (var k = 0; k < pairSet.length; k++) { - var pair = pairSet[k]; - if (pair.secondGlyph === rightIndex) { - return pair.value1 && pair.value1.xAdvance || 0; - } - } - break; // left glyph found, not right glyph - try next subtable - case 2: - // Search Pair Adjustment Positioning Format 2 - var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); - var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); - var pair$1 = subtable.classRecords[class1][class2]; - return pair$1.value1 && pair$1.value1.xAdvance || 0; - } - } - } - return 0; - }; + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); +} - /** - * List all kerning lookup tables. - * - * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value - * @param {string} [language='dflt'] - * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) - */ - Position.prototype.getKerningTables = function(script, language) { - if (this.font.tables.gpos) { - return this.getLookupTables(script, language, 'kern', 2); - } - }; +// MDAP[a] Move Direct Absolute Point +// 0x2E-0x2F +function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; - // The Substitution object provides utility methods to manipulate + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } - /** - * @exports opentype.Substitution - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ - function Substitution(font) { - Layout.call(this, font, 'gsub'); - } + var d = pv.distance(p, HPZero); + + if (round) { d = state.round(d); } + + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); - // Check if 2 arrays of primitives are equal. - function arraysEqual(ar1, ar2) { - var n = ar1.length; - if (n !== ar2.length) { return false; } - for (var i = 0; i < n; i++) { - if (ar1[i] !== ar2[i]) { return false; } + state.rp0 = state.rp1 = pi; +} + +// IUP[a] Interpolate Untouched Points through the outline +// 0x30 +function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + + // if this point has been touched go on + if (v.touched(cp)) { continue; } + + pp = cp.prevTouched(v); + + // no point on the contour has been touched? + if (pp === cp) { continue; } + + np = cp.nextTouched(v); + + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); } - return true; + + v.interpolate(cp, pp, np, v); } +} - // Find the first subtable of a lookup table in a particular format. - function getSubstFormat(lookupTable, format, defaultSubtable) { - var subtables = lookupTable.subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - if (subtable.substFormat === format) { - return subtable; - } - } - if (defaultSubtable) { - subtables.push(defaultSubtable); - return defaultSubtable; +// SHP[] SHift Point using reference point +// 0x32-0x33 +function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); } - return undefined; } - Substitution.prototype = Layout.prototype; + state.loop = 1; +} - /** - * Create a default GSUB table. - * @return {Object} gsub - The GSUB table. - */ - Substitution.prototype.createDefaultTable = function() { - // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. - return { - version: 1, - scripts: [{ - tag: 'DFLT', - script: { - defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, - langSysRecords: [] - } - }], - features: [], - lookups: [] - }; - }; +// SHC[] SHift Contour using reference point +// 0x36-0x37 +function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; - /** - * List all single substitutions (lookup type 1) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getSingle = function(feature, script, language) { - var substitutions = []; - var lookupTables = this.getLookupTables(script, language, feature, 1); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var j = (void 0); - if (subtable.substFormat === 1) { - var delta = subtable.deltaGlyphId; - for (j = 0; j < glyphs.length; j++) { - var glyph = glyphs[j]; - substitutions.push({ sub: glyph, by: glyph + delta }); - } - } else { - var substitute = subtable.substitute; - for (j = 0; j < glyphs.length; j++) { - substitutions.push({ sub: glyphs[j], by: substitute[j] }); - } - } - } - } - return substitutions; - }; + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } - /** - * List all multiple substitutions (lookup type 2) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('ccmp', 'stch') - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getMultiple = function(feature, script, language) { - var substitutions = []; - var lookupTables = this.getLookupTables(script, language, feature, 2); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var j = (void 0); + var d = pv.distance(rp, rp, false, true); - for (j = 0; j < glyphs.length; j++) { - var glyph = glyphs[j]; - var replacements = subtable.sequences[j]; - substitutions.push({ sub: glyph, by: replacements }); - } - } - } - return substitutions; - }; - - /** - * List all alternates (lookup type 3) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt'...) - * @return {Array} alternates - The list of alternates - */ - Substitution.prototype.getAlternates = function(feature, script, language) { - var alternates = []; - var lookupTables = this.getLookupTables(script, language, feature, 3); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var alternateSets = subtable.alternateSets; - for (var j = 0; j < glyphs.length; j++) { - alternates.push({ sub: glyphs[j], by: alternateSets[j] }); - } - } - } - return alternates; - }; + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); +} + +// SHZ[] SHift Zone using reference point +// 0x36-0x37 +function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } + + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } + + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } +} + +// SHPIX[] SHift point by a PIXel amount +// 0x38 +function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; - /** - * List all ligatures (lookup type 4) for a given script, language, and feature. - * The result is an array of ligature objects like { sub: [ids], by: id } - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} ligatures - The list of ligatures. - */ - Substitution.prototype.getLigatures = function(feature, script, language) { - var ligatures = []; - var lookupTables = this.getLookupTables(script, language, feature, 4); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var ligatureSets = subtable.ligatureSets; - for (var j = 0; j < glyphs.length; j++) { - var startGlyph = glyphs[j]; - var ligSet = ligatureSets[j]; - for (var k = 0; k < ligSet.length; k++) { - var lig = ligSet[k]; - ligatures.push({ - sub: [startGlyph].concat(lig.components), - by: lig.ligGlyph - }); - } - } - } + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); } - return ligatures; - }; - /** - * Add or modify a single substitution (lookup type 1) - * Format 2, more flexible, is always used. - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addSingle = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; - var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 - substFormat: 2, - coverage: {format: 1, glyphs: []}, - substitute: [] - }); - check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.substitute.splice(pos, 0, 0); - } - subtable.substitute[pos] = substitution.by; - }; + fv.setRelative(p, p, d); + fv.touch(p); + } - /** - * Add or modify a multiple substitution (lookup type 2) - * @param {string} feature - 4-letter feature name ('ccmp', 'stch') - * @param {Object} substitution - { sub: id, by: [id] } for format 2. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addMultiple = function(feature, substitution, script, language) { - check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); - var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; - var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: {format: 1, glyphs: []}, - sequences: [] - }); - check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.sequences.splice(pos, 0, 0); - } - subtable.sequences[pos] = substitution.by; - }; + state.loop = 1; +} - /** - * Add or modify an alternate substitution (lookup type 3) - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, by: [ids] } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addAlternate = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; - var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: {format: 1, glyphs: []}, - alternateSets: [] - }); - check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.alternateSets.splice(pos, 0, 0); - } - subtable.alternateSets[pos] = substitution.by; - }; +// IP[] Interpolate Point +// 0x39 +function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; - /** - * Add a ligature (lookup type 4) - * Ligatures with more components must be stored ahead of those with fewer components in order to be found - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} ligature - { sub: [ids], by: id } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addLigature = function(feature, ligature, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; - var subtable = lookupTable.subtables[0]; - if (!subtable) { - subtable = { // lookup type 4 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: { format: 1, glyphs: [] }, - ligatureSets: [] - }; - lookupTable.subtables[0] = subtable; - } - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = ligature.sub[0]; - var ligComponents = ligature.sub.slice(1); - var ligatureTable = { - ligGlyph: ligature.by, - components: ligComponents - }; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos >= 0) { - // ligatureSet already exists - var ligatureSet = subtable.ligatureSets[pos]; - for (var i = 0; i < ligatureSet.length; i++) { - // If ligature already exists, return. - if (arraysEqual(ligatureSet[i].components, ligComponents)) { - return; - } - } - // ligature does not exist: add it. - ligatureSet.push(ligatureTable); - } else { - // Create a new ligatureSet and add coverage for the first glyph. - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.ligatureSets.splice(pos, 0, [ligatureTable]); - } - }; + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; - /** - * List all feature data for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getFeature = function(feature, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.getSingle(feature, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - return this.getSingle(feature, script, language) - .concat(this.getAlternates(feature, script, language)); - case 'dlig': - case 'liga': - case 'rlig': - return this.getLigatures(feature, script, language); - case 'ccmp': - return this.getMultiple(feature, script, language) - .concat(this.getLigatures(feature, script, language)); - case 'stch': - return this.getMultiple(feature, script, language); + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); } - return undefined; - }; - /** - * Add a substitution to a feature for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.add = function(feature, sub, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.addSingle(feature, sub, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - if (typeof sub.by === 'number') { - return this.addSingle(feature, sub, script, language); - } - return this.addAlternate(feature, sub, script, language); - case 'dlig': - case 'liga': - case 'rlig': - return this.addLigature(feature, sub, script, language); - case 'ccmp': - if (sub.by instanceof Array) { - return this.addMultiple(feature, sub, script, language); - } - return this.addLigature(feature, sub, script, language); - } - return undefined; - }; + fv.interpolate(p, rp1, rp2, pv); - function isBrowser() { - return typeof window !== 'undefined'; + fv.touch(p); } - function nodeBufferToArrayBuffer(buffer) { - var ab = new ArrayBuffer(buffer.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - view[i] = buffer[i]; - } - - return ab; - } + state.loop = 1; +} + +// MSIRP[a] Move Stack Indirect Relative Point +// 0x3A-0x3B +function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + + fv.setRelative(p, rp0, d, pv); + fv.touch(p); + + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } +} + +// ALIGNRP[] Align to reference point. +// 0x3C +function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; - function arrayBufferToNodeBuffer(ab) { - var buffer = new Buffer(ab.byteLength); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - buffer[i] = view[i]; + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); } - return buffer; + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); } - function checkArgument(expression, message) { - if (!expression) { - throw message; - } + state.loop = 1; +} + +// RTG[] Round To Double Grid +// 0x3D +function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } + + state.round = roundToDoubleGrid; +} + +// MIAP[a] Move Indirect Absolute Point +// 0x3E-0x3F +function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); } - // The `glyf` table describes the glyphs in TrueType outline format. + var d = pv.distance(p, HPZero); - // Parse the coordinate data for a glyph. - function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { - var v; - if ((flag & shortVectorBitMask) > 0) { - // The coordinate is 1 byte long. - v = p.parseByte(); - // The `same` bit is re-used for short values to signify the sign of the value. - if ((flag & sameBitMask) === 0) { - v = -v; - } + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } - v = previousValue + v; - } else { - // The coordinate is 2 bytes long. - // If the `same` bit is set, the coordinate is the same as the previous coordinate. - if ((flag & sameBitMask) > 0) { - v = previousValue; - } else { - // Parse the coordinate as a signed 16-bit delta value. - v = previousValue + p.parseShort(); - } - } + d = state.round(d); + } - return v; + fv.setRelative(p, HPZero, d, pv); + + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; } - // Parse a TrueType glyph. - function parseGlyph(glyph, data, start) { - var p = new parse.Parser(data, start); - glyph.numberOfContours = p.parseShort(); - glyph._xMin = p.parseShort(); - glyph._yMin = p.parseShort(); - glyph._xMax = p.parseShort(); - glyph._yMax = p.parseShort(); - var flags; - var flag; - - if (glyph.numberOfContours > 0) { - // This glyph is not a composite. - var endPointIndices = glyph.endPointIndices = []; - for (var i = 0; i < glyph.numberOfContours; i += 1) { - endPointIndices.push(p.parseUShort()); - } + fv.touch(p); - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { - glyph.instructions.push(p.parseByte()); - } + state.rp0 = state.rp1 = pi; +} - var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; - flags = []; - for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { - flag = p.parseByte(); - flags.push(flag); - // If bit 3 is set, we repeat this flag n times, where n is the next byte. - if ((flag & 8) > 0) { - var repeatCount = p.parseByte(); - for (var j = 0; j < repeatCount; j += 1) { - flags.push(flag); - i$2 += 1; - } - } - } +// NPUSB[] PUSH N Bytes +// 0x40 +function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; - check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); - - if (endPointIndices.length > 0) { - var points = []; - var point; - // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. - if (numberOfCoordinates > 0) { - for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { - flag = flags[i$3]; - point = {}; - point.onCurve = !!(flag & 1); - point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; - points.push(point); - } + var n = prog[++ip]; - var px = 0; - for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { - flag = flags[i$4]; - point = points[i$4]; - point.x = parseGlyphCoordinate(p, flag, px, 2, 16); - px = point.x; - } + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } - var py = 0; - for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { - flag = flags[i$5]; - point = points[i$5]; - point.y = parseGlyphCoordinate(p, flag, py, 4, 32); - py = point.y; - } - } + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - glyph.points = points; - } else { - glyph.points = []; - } - } else if (glyph.numberOfContours === 0) { - glyph.points = []; - } else { - glyph.isComposite = true; - glyph.points = []; - glyph.components = []; - var moreComponents = true; - while (moreComponents) { - flags = p.parseUShort(); - var component = { - glyphIndex: p.parseUShort(), - xScale: 1, - scale01: 0, - scale10: 0, - yScale: 1, - dx: 0, - dy: 0 - }; - if ((flags & 1) > 0) { - // The arguments are words - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseShort(); - component.dy = p.parseShort(); - } else { - // values are matched points - component.matchedPoints = [p.parseUShort(), p.parseUShort()]; - } + state.ip = ip; +} - } else { - // The arguments are bytes - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseChar(); - component.dy = p.parseChar(); - } else { - // values are matched points - component.matchedPoints = [p.parseByte(), p.parseByte()]; - } - } +// NPUSHW[] PUSH N Words +// 0x41 +function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; - if ((flags & 8) > 0) { - // We have a scale - component.xScale = component.yScale = p.parseF2Dot14(); - } else if ((flags & 64) > 0) { - // We have an X / Y scale - component.xScale = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } else if ((flags & 128) > 0) { - // We have a 2x2 transformation - component.xScale = p.parseF2Dot14(); - component.scale01 = p.parseF2Dot14(); - component.scale10 = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } - glyph.components.push(component); - moreComponents = !!(flags & 32); - } - if (flags & 0x100) { - // We have instructions - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { - glyph.instructions.push(p.parseByte()); - } - } - } + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); } - // Transform an array of points and return a new array. - function transformPoints(points, transform) { - var newPoints = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - var newPt = { - x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, - y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, - onCurve: pt.onCurve, - lastPointOfContour: pt.lastPointOfContour - }; - newPoints.push(newPt); - } + state.ip = ip; +} - return newPoints; - } +// WS[] Write Store +// 0x42 +function WS(state) { + var stack = state.stack; + var store = state.store; - function getContours(points) { - var contours = []; - var currentContour = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; - } - } + if (!store) { store = state.store = []; } - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; - } + var v = stack.pop(); + var l = stack.pop(); - // Convert the TrueType glyph outline to a Path. - function getPath(points) { - var p = new Path(); - if (!points) { - return p; - } + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } - var contours = getContours(points); + store[l] = v; +} - for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { - var contour = contours[contourIndex]; +// RS[] Read Store +// 0x43 +function RS(state) { + var stack = state.stack; + var store = state.store; - var prev = null; - var curr = contour[contour.length - 1]; - var next = contour[0]; + var l = stack.pop(); - if (curr.onCurve) { - p.moveTo(curr.x, curr.y); - } else { - if (next.onCurve) { - p.moveTo(next.x, next.y); - } else { - // If both first and last points are off-curve, start at their middle. - var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; - p.moveTo(start.x, start.y); - } - } + if (exports.DEBUG) { console.log(state.step, 'RS', l); } - for (var i = 0; i < contour.length; ++i) { - prev = curr; - curr = next; - next = contour[(i + 1) % contour.length]; + var v = (store && store[l]) || 0; - if (curr.onCurve) { - // This is a straight line. - p.lineTo(curr.x, curr.y); - } else { - var prev2 = prev; - var next2 = next; + stack.push(v); +} - if (!prev.onCurve) { - prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; - } +// WCVTP[] Write Control Value Table in Pixel units +// 0x44 +function WCVTP(state) { + var stack = state.stack; - if (!next.onCurve) { - next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; - } + var v = stack.pop(); + var l = stack.pop(); - p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); - } - } + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } - p.closePath(); - } - return p; - } + state.cvt[l] = v / 0x40; +} - function buildPath(glyphs, glyph) { - if (glyph.isComposite) { - for (var j = 0; j < glyph.components.length; j += 1) { - var component = glyph.components[j]; - var componentGlyph = glyphs.get(component.glyphIndex); - // Force the ttfGlyphLoader to parse the glyph. - componentGlyph.getPath(); - if (componentGlyph.points) { - var transformedPoints = (void 0); - if (component.matchedPoints === undefined) { - // component positioned by offset - transformedPoints = transformPoints(componentGlyph.points, component); - } else { - // component positioned by matched points - if ((component.matchedPoints[0] > glyph.points.length - 1) || - (component.matchedPoints[1] > componentGlyph.points.length - 1)) { - throw Error('Matched points out of range in ' + glyph.name); - } - var firstPt = glyph.points[component.matchedPoints[0]]; - var secondPt = componentGlyph.points[component.matchedPoints[1]]; - var transform = { - xScale: component.xScale, scale01: component.scale01, - scale10: component.scale10, yScale: component.yScale, - dx: 0, dy: 0 - }; - secondPt = transformPoints([secondPt], transform)[0]; - transform.dx = firstPt.x - secondPt.x; - transform.dy = firstPt.y - secondPt.y; - transformedPoints = transformPoints(componentGlyph.points, transform); - } - glyph.points = glyph.points.concat(transformedPoints); - } - } - } +// RCVT[] Read Control Value Table entry +// 0x45 +function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); - return getPath(glyph.points); - } + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } - function parseGlyfTableAll(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); + stack.push(state.cvt[cvte] * 0x40); +} - // The last element of the loca table is invalid. - for (var i = 0; i < loca.length - 1; i += 1) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - } +// GC[] Get Coordinate projected onto the projection vector +// 0x46-0x47 +function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; - return glyphs; - } + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } - function parseGlyfTableOnLowMemory(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); +} - font._push = function(i) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - }; +// MD[a] Measure Distance +// 0x49-0x4A +function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); - return glyphs; - } - - // Parse all the glyphs according to the offsets from the `loca` table. - function parseGlyfTable(data, start, loca, font, opt) { - if (opt.lowMemory) - { return parseGlyfTableOnLowMemory(data, start, loca, font); } - else - { return parseGlyfTableAll(data, start, loca, font); } - } - - var glyf = { getPath: getPath, parse: parseGlyfTable}; - - /* A TrueType font hinting interpreter. - * - * (c) 2017 Axel Kittenberger - * - * This interpreter has been implemented according to this documentation: - * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html - * - * According to the documentation F24DOT6 values are used for pixels. - * That means calculation is 1/64 pixel accurate and uses integer operations. - * However, Javascript has floating point operations by default and only - * those are available. One could make a case to simulate the 1/64 accuracy - * exactly by truncating after every division operation - * (for example with << 0) to get pixel exactly results as other TrueType - * implementations. It may make sense since some fonts are pixel optimized - * by hand using DELTAP instructions. The current implementation doesn't - * and rather uses full floating point precision. - * - * xScale, yScale and rotation is currently ignored. - * - * A few non-trivial instructions are missing as I didn't encounter yet - * a font that used them to test a possible implementation. - * - * Some fonts seem to use undocumented features regarding the twilight zone. - * Only some of them are implemented as they were encountered. - * - * The exports.DEBUG statements are removed on the minified distribution file. - */ - - var instructionTable; - var exec; - var execGlyph; - var execComponent; - - /* - * Creates a hinting object. - * - * There ought to be exactly one - * for each truetype font that is used for hinting. - */ - function Hinting(font) { - // the font this hinting object is for - this.font = font; - - this.getCommands = function (hPoints) { - return glyf.getPath(hPoints).commands; - }; + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } - // cached states - this._fpgmState = - this._prepState = - undefined; - - // errorState - // 0 ... all okay - // 1 ... had an error in a glyf, - // continue working but stop spamming - // the console - // 2 ... error at prep, stop hinting at this ppem - // 3 ... error at fpeg, stop hinting for this font at all - this._errorState = 0; - } - - /* - * Not rounding. - */ - function roundOff(v) { - return v; - } + state.stack.push(Math.round(d * 64)); +} - /* - * Rounding to grid. - */ - function roundToGrid(v) { - //Rounding in TT is supposed to "symmetrical around zero" - return Math.sign(v) * Math.round(Math.abs(v)); - } +// MPPEM[] Measure Pixels Per EM +// 0x4B +function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); +} - /* - * Rounding to double grid. - */ - function roundToDoubleGrid(v) { - return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; - } +// FLIPON[] set the auto FLIP Boolean to ON +// 0x4D +function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; +} - /* - * Rounding to half grid. - */ - function roundToHalfGrid(v) { - return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); - } +// LT[] Less Than +// 0x50 +function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - /* - * Rounding to up to grid. - */ - function roundUpToGrid(v) { - return Math.sign(v) * Math.ceil(Math.abs(v)); - } + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } - /* - * Rounding to down to grid. - */ - function roundDownToGrid(v) { - return Math.sign(v) * Math.floor(Math.abs(v)); - } + stack.push(e1 < e2 ? 1 : 0); +} - /* - * Super rounding. - */ - var roundSuper = function (v) { - var period = this.srPeriod; - var phase = this.srPhase; - var threshold = this.srThreshold; - var sign = 1; +// LTEQ[] Less Than or EQual +// 0x53 +function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - if (v < 0) { - v = -v; - sign = -1; - } + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } - v += threshold - phase; + stack.push(e1 <= e2 ? 1 : 0); +} - v = Math.trunc(v / period) * period; +// GTEQ[] Greater Than +// 0x52 +function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - v += phase; + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } - // according to http://xgridfit.sourceforge.net/round.html - if (v < 0) { return phase * sign; } + stack.push(e1 > e2 ? 1 : 0); +} + +// GTEQ[] Greater Than or EQual +// 0x53 +function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - return v * sign; - }; + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } - /* - * Unit vector of x-axis. - */ - var xUnitVector = { - x: 1, + stack.push(e1 >= e2 ? 1 : 0); +} + +// EQ[] EQual +// 0x54 +function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - y: 0, + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); +} + +// NEQ[] Not EQual +// 0x55 +function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } + + stack.push(e2 !== e1 ? 1 : 0); +} + +// ODD[] ODD +// 0x56 +function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } + + stack.push(Math.trunc(n) % 2 ? 1 : 0); +} + +// EVEN[] EVEN +// 0x57 +function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); +} + +// IF[] IF test +// 0x58 +function IF(state) { + var test = state.stack.pop(); - axis: 'x', + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); - }, + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } +} - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; - - if (!pv || pv === this) { - do1 = p.xo - rp1.xo; - do2 = p.xo - rp2.xo; - dm1 = rp1.x - rp1.xo; - dm2 = rp2.x - rp2.xo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - p.x = p.xo + (dm1 + dm2) / 2; - return; - } +// EIF[] End IF +// 0x59 +function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it - p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; - return; - } + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } +} - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; +// AND[] logical AND +// 0x5A +function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - if (dt === 0) { - xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } - xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, + stack.push(e2 && e1 ? 1 : 0); +} - // Slope of line normal to this - normalSlope: Number.NEGATIVE_INFINITY, - - // Sets the point 'p' relative to point 'rp' - // by the distance 'd'. - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.x = (org ? rp.xo : rp.x) + d; - return; - } +// OR[] logical OR +// 0x5B +function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } - p.x = rpdx + (p.y - rpdy) / pv.normalSlope; - }, + stack.push(e2 || e1 ? 1 : 0); +} - // Slope of vector line. - slope: 0, +// NOT[] logical NOT +// 0x5C +function NOT(state) { + var stack = state.stack; + var e = stack.pop(); - // Touches the point p. - touch: function (p) { - p.xTouched = true; - }, + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } - // Tests if a point p is touched. - touched: function (p) { - return p.xTouched; - }, + stack.push(e ? 0 : 1); +} - // Untouches the point p. - untouch: function (p) { - p.xTouched = false; - } - }; +// DELTAP1[] DELTA exception P1 +// DELTAP2[] DELTA exception P2 +// DELTAP3[] DELTA exception P3 +// 0x5D, 0x71, 0x72 +function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; - /* - * Unit vector of y-axis. - */ - var yUnitVector = { - x: 0, + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } - y: 1, + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } - axis: 'y', + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); - }, + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } +} - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; - - if (!pv || pv === this) { - do1 = p.yo - rp1.yo; - do2 = p.yo - rp2.yo; - dm1 = rp1.y - rp1.yo; - dm2 = rp2.y - rp2.yo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - p.y = p.yo + (dm1 + dm2) / 2; - return; - } +// SDB[] Set Delta Base in the graphics state +// 0x5E +function SDB(state) { + var stack = state.stack; + var n = stack.pop(); - p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; - return; - } + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; + state.deltaBase = n; +} - if (dt === 0) { - yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } +// SDS[] Set Delta Shift in the graphics state +// 0x5F +function SDS(state) { + var stack = state.stack; + var n = stack.pop(); - yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } - // Slope of line normal to this. - normalSlope: 0, - - // Sets the point 'p' relative to point 'rp' - // by the distance 'd' - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.y = (org ? rp.yo : rp.y) + d; - return; - } + state.deltaShift = Math.pow(0.5, n); +} - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; +// ADD[] ADD +// 0x60 +function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); - p.y = rpdy + pv.normalSlope * (p.x - rpdx); - }, + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } - // Slope of vector line. - slope: Number.POSITIVE_INFINITY, + stack.push(n1 + n2); +} - // Touches the point p. - touch: function (p) { - p.yTouched = true; - }, +// SUB[] SUB +// 0x61 +function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); - // Tests if a point p is touched. - touched: function (p) { - return p.yTouched; - }, + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } - // Untouches the point p. - untouch: function (p) { - p.yTouched = false; - } - }; + stack.push(n1 - n2); +} - Object.freeze(xUnitVector); - Object.freeze(yUnitVector); - - /* - * Creates a unit vector that is not x- or y-axis. - */ - function UnitVector(x, y) { - this.x = x; - this.y = y; - this.axis = undefined; - this.slope = y / x; - this.normalSlope = -x / y; - Object.freeze(this); - } - - /* - * Gets the projected distance between two points. - * o1/o2 ... if true, respective original position is used. - */ - UnitVector.prototype.distance = function(p1, p2, o1, o2) { - return ( - this.x * xUnitVector.distance(p1, p2, o1, o2) + - this.y * yUnitVector.distance(p1, p2, o1, o2) - ); - }; +// DIV[] DIV +// 0x62 +function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); - /* - * Moves point p so the moved position has the same relative - * position to the moved positions of rp1 and rp2 than the - * original positions had. - * - * See APPENDIX on INTERPOLATE at the bottom of this file. - */ - UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { - var dm1; - var dm2; - var do1; - var do2; - var doa1; - var doa2; - var dt; + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; + stack.push(n1 * 64 / n2); +} - if (dt === 0) { - this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } +// MUL[] MUL +// 0x63 +function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); - this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }; + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } - /* - * Sets the point 'p' relative to point 'rp' - * by the distance 'd' - * - * See APPENDIX on SETRELATIVE at the bottom of this file. - * - * p ... point to set - * rp ... reference point - * d ... distance on projection vector - * pv ... projection vector (undefined = this) - * org ... if true, uses the original position of rp as reference. - */ - UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { - pv = pv || this; + stack.push(n1 * n2 / 64); +} - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; +// ABS[] ABSolute value +// 0x64 +function ABS(state) { + var stack = state.stack; + var n = stack.pop(); - var pvns = pv.normalSlope; - var fvs = this.slope; + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } - var px = p.x; - var py = p.y; + stack.push(Math.abs(n)); +} - p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); - p.y = fvs * (p.x - px) + py; - }; +// NEG[] NEGate +// 0x65 +function NEG(state) { + var stack = state.stack; + var n = stack.pop(); - /* - * Touches the point p. - */ - UnitVector.prototype.touch = function(p) { - p.xTouched = true; - p.yTouched = true; - }; + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } - /* - * Returns a unit vector with x/y coordinates. - */ - function getUnitVector(x, y) { - var d = Math.sqrt(x * x + y * y); + stack.push(-n); +} - x /= d; - y /= d; +// FLOOR[] FLOOR +// 0x66 +function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); - if (x === 1 && y === 0) { return xUnitVector; } - else if (x === 0 && y === 1) { return yUnitVector; } - else { return new UnitVector(x, y); } - } + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } - /* - * Creates a point in the hinting engine. - */ - function HPoint( - x, - y, - lastPointOfContour, - onCurve - ) { - this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value - this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + stack.push(Math.floor(n / 0x40) * 0x40); +} - this.lastPointOfContour = lastPointOfContour; - this.onCurve = onCurve; - this.prevPointOnContour = undefined; - this.nextPointOnContour = undefined; - this.xTouched = false; - this.yTouched = false; +// CEILING[] CEILING +// 0x67 +function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); - Object.preventExtensions(this); - } + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } - /* - * Returns the next touched point on the contour. - * - * v ... unit vector to test touch axis. - */ - HPoint.prototype.nextTouched = function(v) { - var p = this.nextPointOnContour; + stack.push(Math.ceil(n / 0x40) * 0x40); +} - while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } +// ROUND[ab] ROUND value +// 0x68-0x6B +function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); - return p; - }; + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } - /* - * Returns the previous touched point on the contour - * - * v ... unit vector to test touch axis. - */ - HPoint.prototype.prevTouched = function(v) { - var p = this.prevPointOnContour; + stack.push(state.round(n / 0x40) * 0x40); +} - while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } +// WCVTF[] Write Control Value Table in Funits +// 0x70 +function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); - return p; - }; + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } - /* - * The zero point. - */ - var HPZero = Object.freeze(new HPoint(0, 0)); - - /* - * The default state of the interpreter. - * - * Note: Freezing the defaultState and then deriving from it - * makes the V8 Javascript engine going awkward, - * so this is avoided, albeit the defaultState shouldn't - * ever change. - */ - var defaultState = { - cvCutIn: 17 / 16, // control value cut in - deltaBase: 9, - deltaShift: 0.125, - loop: 1, // loops some instructions - minDis: 1, // minimum distance - autoFlip: true - }; + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; +} - /* - * The current state of the interpreter. - * - * env ... 'fpgm' or 'prep' or 'glyf' - * prog ... the program - */ - function State(env, prog) { - this.env = env; - this.stack = []; - this.prog = prog; - - switch (env) { - case 'glyf' : - this.zp0 = this.zp1 = this.zp2 = 1; - this.rp0 = this.rp1 = this.rp2 = 0; - /* fall through */ - case 'prep' : - this.fv = this.pv = this.dpv = xUnitVector; - this.round = roundToGrid; - } - } +// DELTAC1[] DELTA exception C1 +// DELTAC2[] DELTA exception C2 +// DELTAC3[] DELTA exception C3 +// 0x73, 0x74, 0x75 +function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; - /* - * Executes a glyph program. - * - * This does the hinting for each glyph. - * - * Returns an array of moved points. - * - * glyph: the glyph to hint - * ppem: the size the glyph is rendered for - */ - Hinting.prototype.exec = function(glyph, ppem) { - if (typeof ppem !== 'number') { - throw new Error('Point size is not a number!'); - } + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } - // Received a fatal error, don't do any hinting anymore. - if (this._errorState > 2) { return; } + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } - var font = this.font; - var prepState = this._prepState; + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } - if (!prepState || prepState.ppem !== ppem) { - var fpgmState = this._fpgmState; + var delta = mag * ds; - if (!fpgmState) { - // Executes the fpgm state. - // This is used by fonts to define functions. - State.prototype = defaultState; + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } - fpgmState = - this._fpgmState = - new State('fpgm', font.tables.fpgm); + state.cvt[c] += delta; + } +} - fpgmState.funcs = [ ]; - fpgmState.font = font; +// SROUND[] Super ROUND +// 0x76 +function SROUND(state) { + var n = state.stack.pop(); - if (exports.DEBUG) { - console.log('---EXEC FPGM---'); - fpgmState.step = -1; - } + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } - try { - exec(fpgmState); - } catch (e) { - console.log('Hinting error in FPGM:' + e); - this._errorState = 3; - return; - } - } + state.round = roundSuper; - // Executes the prep program for this ppem setting. - // This is used by fonts to set cvt values - // depending on to be rendered font size. - - State.prototype = fpgmState; - prepState = - this._prepState = - new State('prep', font.tables.prep); - - prepState.ppem = ppem; - - // Creates a copy of the cvt table - // and scales it to the current ppem setting. - var oCvt = font.tables.cvt; - if (oCvt) { - var cvt = prepState.cvt = new Array(oCvt.length); - var scale = ppem / font.unitsPerEm; - for (var c = 0; c < oCvt.length; c++) { - cvt[c] = oCvt[c] * scale; - } - } else { - prepState.cvt = []; - } + var period; - if (exports.DEBUG) { - console.log('---EXEC PREP---'); - prepState.step = -1; - } + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); + } - try { - exec(prepState); - } catch (e) { - if (this._errorState < 2) { - console.log('Hinting error in PREP:' + e); - } - this._errorState = 2; - } - } + state.srPeriod = period; - if (this._errorState > 1) { return; } + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } - try { - return execGlyph(glyph, prepState); - } catch (e) { - if (this._errorState < 1) { - console.log('Hinting error:' + e); - console.log('Note: further hinting errors are silenced'); - } - this._errorState = 1; - return undefined; - } - }; + n &= 0x0F; - /* - * Executes the hinting program for a glyph. - */ - execGlyph = function(glyph, prepState) { - // original point positions - var xScale = prepState.ppem / prepState.font.unitsPerEm; - var yScale = xScale; - var components = glyph.components; - var contours; - var gZone; - var state; - - State.prototype = prepState; - if (!components) { - state = new State('glyf', glyph.instructions); - if (exports.DEBUG) { - console.log('---EXEC GLYPH---'); - state.step = -1; - } - execComponent(glyph, state, xScale, yScale); - gZone = state.gZone; - } else { - var font = prepState.font; - gZone = []; - contours = []; - for (var i = 0; i < components.length; i++) { - var c = components[i]; - var cg = font.glyphs.get(c.glyphIndex); - - state = new State('glyf', cg.instructions); - - if (exports.DEBUG) { - console.log('---EXEC COMP ' + i + '---'); - state.step = -1; - } + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} - execComponent(cg, state, xScale, yScale); - // appends the computed points to the result array - // post processes the component points - var dx = Math.round(c.dx * xScale); - var dy = Math.round(c.dy * yScale); - var gz = state.gZone; - var cc = state.contours; - for (var pi = 0; pi < gz.length; pi++) { - var p = gz[pi]; - p.xTouched = p.yTouched = false; - p.xo = p.x = p.x + dx; - p.yo = p.y = p.y + dy; - } +// S45ROUND[] Super ROUND 45 degrees +// 0x77 +function S45ROUND(state) { + var n = state.stack.pop(); - var gLen = gZone.length; - gZone.push.apply(gZone, gz); - for (var j = 0; j < cc.length; j++) { - contours.push(cc[j] + gLen); - } - } + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } - if (glyph.instructions && !state.inhibitGridFit) { - // the composite has instructions on its own - state = new State('glyf', glyph.instructions); + state.round = roundSuper; - state.gZone = state.z0 = state.z1 = state.z2 = gZone; + var period; - state.contours = contours; + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); + } - // note: HPZero cannot be used here, since - // the point might be modified - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); + state.srPeriod = period; - if (exports.DEBUG) { - console.log('---EXEC COMPOSITE---'); - state.step = -1; - } + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } - exec(state); + n &= 0x0F; - gZone.length -= 2; - } - } + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} - return gZone; - }; +// ROFF[] Round Off +// 0x7A +function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } - /* - * Executes the hinting program for a component of a multi-component glyph - * or of the glyph itself for a non-component glyph. - */ - execComponent = function(glyph, state, xScale, yScale) - { - var points = glyph.points || []; - var pLen = points.length; - var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; - var contours = state.contours = []; - - // Scales the original points and - // makes copies for the hinted points. - var cp; // current point - for (var i = 0; i < pLen; i++) { - cp = points[i]; - - gZone[i] = new HPoint( - cp.x * xScale, - cp.y * yScale, - cp.lastPointOfContour, - cp.onCurve - ); - } + state.round = roundOff; +} - // Chain links the contours. - var sp; // start point - var np; // next point +// RUTG[] Round Up To Grid +// 0x7C +function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } - for (var i$1 = 0; i$1 < pLen; i$1++) { - cp = gZone[i$1]; + state.round = roundUpToGrid; +} - if (!sp) { - sp = cp; - contours.push(i$1); - } +// RDTG[] Round Down To Grid +// 0x7D +function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } - if (cp.lastPointOfContour) { - cp.nextPointOnContour = sp; - sp.prevPointOnContour = cp; - sp = undefined; - } else { - np = gZone[i$1 + 1]; - cp.nextPointOnContour = np; - np.prevPointOnContour = cp; - } - } + state.round = roundDownToGrid; +} - if (state.inhibitGridFit) { return; } +// SCANCTRL[] SCAN conversion ConTRoL +// 0x85 +function SCANCTRL(state) { + var n = state.stack.pop(); - if (exports.DEBUG) { - console.log('PROCESSING GLYPH', state.stack); - for (var i$2 = 0; i$2 < pLen; i$2++) { - console.log(i$2, gZone[i$2].x, gZone[i$2].y); - } - } + // ignored by opentype.js - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } +} - exec(state); +// SDPVTL[a] Set Dual Projection Vector To Line +// 0x86-0x87 +function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; - // Removes the extra points. - gZone.length -= 2; + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } - if (exports.DEBUG) { - console.log('FINISHED GLYPH', state.stack); - for (var i$3 = 0; i$3 < pLen; i$3++) { - console.log(i$3, gZone[i$3].x, gZone[i$3].y); - } - } - }; + var dx; + var dy; - /* - * Executes the program loaded in state. - */ - exec = function(state) { - var prog = state.prog; + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } - if (!prog) { return; } + state.dpv = getUnitVector(dx, dy); +} - var pLen = prog.length; - var ins; +// GETINFO[] GET INFOrmation +// 0x88 +function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; - for (state.ip = 0; state.ip < pLen; state.ip++) { - if (exports.DEBUG) { state.step++; } - ins = instructionTable[prog[state.ip]]; + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } - if (!ins) { - throw new Error( - 'unknown instruction: 0x' + - Number(prog[state.ip]).toString(16) - ); - } + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } - ins(state); + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. - // very extensive debugging for each step - /* - if (exports.DEBUG) { - var da; - if (state.gZone) { - da = []; - for (let i = 0; i < state.gZone.length; i++) - { - da.push(i + ' ' + - state.gZone[i].x * 64 + ' ' + - state.gZone[i].y * 64 + ' ' + - (state.gZone[i].xTouched ? 'x' : '') + - (state.gZone[i].yTouched ? 'y' : '') - ); - } - console.log('GZ', da); - } + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } - if (state.tZone) { - da = []; - for (let i = 0; i < state.tZone.length; i++) { - da.push(i + ' ' + - state.tZone[i].x * 64 + ' ' + - state.tZone[i].y * 64 + ' ' + - (state.tZone[i].xTouched ? 'x' : '') + - (state.tZone[i].yTouched ? 'y' : '') - ); - } - console.log('TZ', da); - } + stack.push(r); +} - if (state.stack.length > 10) { - console.log( - state.stack.length, - '...', state.stack.slice(state.stack.length - 10) - ); - } else { - console.log(state.stack.length, state.stack); - } - } - */ - } - }; +// ROLL[] ROLL the top three stack elements +// 0x8A +function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); - /* - * Initializes the twilight zone. - * - * This is only done if a SZPx instruction - * refers to the twilight zone. - */ - function initTZone(state) - { - var tZone = state.tZone = new Array(state.gZone.length); + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } - // no idea if this is actually correct... - for (var i = 0; i < tZone.length; i++) - { - tZone[i] = new HPoint(0, 0); - } - } + stack.push(b); + stack.push(a); + stack.push(c); +} + +// MAX[] MAXimum of top two stack elements +// 0x8B +function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } + + stack.push(Math.max(e1, e2)); +} + +// MIN[] MINimum of top two stack elements +// 0x8C +function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); - /* - * Skips the instruction pointer ahead over an IF/ELSE block. - * handleElse .. if true breaks on matching ELSE - */ - function skip(state, handleElse) - { - var prog = state.prog; - var ip = state.ip; - var nesting = 1; - var ins; + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } - do { - ins = prog[++ip]; - if (ins === 0x58) // IF - { nesting++; } - else if (ins === 0x59) // EIF - { nesting--; } - else if (ins === 0x40) // NPUSHB - { ip += prog[ip + 1] + 1; } - else if (ins === 0x41) // NPUSHW - { ip += 2 * prog[ip + 1] + 1; } - else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB - { ip += ins - 0xB0 + 1; } - else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW - { ip += (ins - 0xB8 + 1) * 2; } - else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE - { break; } - } while (nesting > 0); + stack.push(Math.min(e1, e2)); +} - state.ip = ip; +// SCANTYPE[] SCANTYPE +// 0x8D +function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } +} + +// INSTCTRL[] INSTCTRL +// 0x8D +function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } + + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } +} + +// PUSHB[abc] PUSH Bytes +// 0xB0-0xB7 +function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// PUSHW[abc] PUSH Words +// 0xB8-0xBF +function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// MDRP[abcde] Move Direct Relative Point +// 0xD0-0xEF +// (if indirect is 0) +// +// and +// +// MIRP[abcde] Move Indirect Relative Point +// 0xE0-0xFF +// (if indirect is 1) + +function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); } - /*----------------------------------------------------------* - * And then a lot of instructions... * - *----------------------------------------------------------*/ - - // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis - // 0x00-0x01 - function SVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } +} + +/* +* The instruction table. +*/ +instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) +]; + +/***************************** + Mathematical Considerations +****************************** + +fv ... refers to freedom vector +pv ... refers to projection vector +rp ... refers to reference point +p ... refers to to point being operated on +d ... refers to distance + +SETRELATIVE: +============ + +case freedom vector == x-axis: +------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm - state.fv = state.pv = state.dpv = v; - } + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y - // SPVTCA[a] Set Projection Vector to Coordinate Axis - // 0x02-0x03 - function SPVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + equation of line b - state.pv = state.dpv = v; - } + y - rpdy = pvns * (x- rpdx) - // SFVTCA[a] Set Freedom Vector to Coordinate Axis - // 0x04-0x05 - function SFVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + y = p.y - state.fv = v; - } + x = rpdx + ( p.y - rpdy ) / pvns - // SPVTL[a] Set Projection Vector To Line - // 0x06-0x07 - function SPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } +case freedom vector == y-axis: +------------------------------ - var dx; - var dy; + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y - state.pv = state.dpv = getUnitVector(dx, dy); - } + equation of line b: + pvns ... normal slope to pv - // SFVTL[a] Set Freedom Vector To Line - // 0x08-0x09 - function SFVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; + y - rpdy = pvns * (x - rpdx) - if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + x = p.x - var dx; - var dy; + y = rpdy + pvns * (p.x - rpdx) - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - state.fv = getUnitVector(dx, dy); - } - // SPVFS[] Set Projection Vector From Stack - // 0x0A - function SPVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); +generic case: +------------- - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - state.pv = state.dpv = getUnitVector(x, y); - } + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp - // SFVFS[] Set Freedom Vector From Stack - // 0x0B - function SFVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + equation of line b: + pvns... normal slope to pv - state.fv = getUnitVector(x, y); - } + y - rpdy = pvns * (x - rpdx) - // GPV[] Get Projection Vector - // 0x0C - function GPV(state) { - var stack = state.stack; - var pv = state.pv; + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) - if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + y - py = fvs * (x - px) - stack.push(pv.x * 0x4000); - stack.push(pv.y * 0x4000); - } - // GFV[] Get Freedom Vector - // 0x0C - function GFV(state) { - var stack = state.stack; - var fv = state.fv; + on pm both equations are true for same x/y - if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + y - rpdy = pvns * (x - rpdx) - stack.push(fv.x * 0x4000); - stack.push(fv.y * 0x4000); - } + y - py = fvs * (x - px) - // SFVTPV[] Set Freedom Vector To Projection Vector - // 0x0E - function SFVTPV(state) { - state.fv = state.pv; + form to y and set equal: - if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } - } + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py - // ISECT[] moves point p to the InterSECTion of two lines - // 0x0F - function ISECT(state) - { - var stack = state.stack; - var pa0i = stack.pop(); - var pa1i = stack.pop(); - var pb0i = stack.pop(); - var pb1i = stack.pop(); - var pi = stack.pop(); - var z0 = state.z0; - var z1 = state.z1; - var pa0 = z0[pa0i]; - var pa1 = z0[pa1i]; - var pb0 = z1[pb0i]; - var pb1 = z1[pb1i]; - var p = state.z2[pi]; + expand: - if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py - // math from - // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + switch: - var x1 = pa0.x; - var y1 = pa0.y; - var x2 = pa1.x; - var y2 = pa1.y; - var x3 = pb0.x; - var y3 = pb0.y; - var x4 = pb1.x; - var y4 = pb1.y; + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy - var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - var f1 = x1 * y2 - y1 * x2; - var f2 = x3 * y4 - y3 * x4; + solve for x: - p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; - p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; - } + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy - // SRP0[] Set Reference Point 0 - // 0x10 - function SRP0(state) { - state.rp0 = state.stack.pop(); - if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } - } - // SRP1[] Set Reference Point 1 - // 0x11 - function SRP1(state) { - state.rp1 = state.stack.pop(); + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns - if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } - } + and: - // SRP1[] Set Reference Point 2 - // 0x12 - function SRP2(state) { - state.rp2 = state.stack.pop(); + y = fvs * (x - px) + py - if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } - } - // SZP0[] Set Zone Pointer 0 - // 0x13 - function SZP0(state) { - var n = state.stack.pop(); - if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } +INTERPOLATE: +============ - state.zp0 = n; +Examples of point interpolation. - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.tZone; - break; - case 1 : - state.z0 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } +The weight of the movement of the reference point gets bigger +the further the other reference point is away, thus the safest +option (that is avoiding 0/0 divisions) is to weight the +original distance of the other point by the sum of both distances. - // SZP1[] Set Zone Pointer 1 - // 0x14 - function SZP1(state) { - var n = state.stack.pop(); +If the sum of both distances is 0, then move the point by the +arithmetic average of the movement of both reference points. - if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } - state.zp1 = n; - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z1 = state.tZone; - break; - case 1 : - state.z1 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - // SZP2[] Set Zone Pointer 2 - // 0x15 - function SZP2(state) { - var n = state.stack.pop(); + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 - if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } - state.zp2 = n; +------- - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z2 = state.tZone; - break; - case 1 : - state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - // SZPS[] Set Zone PointerS - // 0x16 - function SZPS(state) { - var n = state.stack.pop(); - if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 - state.zp0 = state.zp1 = state.zp2 = n; - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.z1 = state.z2 = state.tZone; - break; - case 1 : - state.z0 = state.z1 = state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } +------- - // SLOOP[] Set LOOP variable - // 0x17 - function SLOOP(state) { - state.loop = state.stack.pop(); - if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } - } + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 - // RTG[] Round To Grid - // 0x18 - function RTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } - state.round = roundToGrid; - } + (+10) + po *-------->* p - // RTHG[] Round To Half Grid - // 0x19 - function RTHG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } +------- - state.round = roundToHalfGrid; - } - // SMD[] Set Minimum Distance - // 0x1A - function SMD(state) { - var d = state.stack.pop(); + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 - if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } - state.minDis = d / 0x40; - } + (+25) + po *----------------------->* p - // ELSE[] ELSE clause - // 0x1B - function ELSE(state) { - // This instruction has been reached by executing a then branch - // so it just skips ahead until matching EIF. - // - // In case the IF was negative the IF[] instruction already - // skipped forward over the ELSE[] - if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } - skip(state, false); - } +vim: set ts=4 sw=4 expandtab: +*****/ - // JMPR[] JuMP Relative - // 0x1C - function JMPR(state) { - var o = state.stack.pop(); +/** + * Converts a string into a list of tokens. + */ - if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } +/** + * Create a new token + * @param {string} char a single char + */ +function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; +} - // A jump by 1 would do nothing. - state.ip += o - 1; - } +/** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ +function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; +} - // SCVTCI[] Set Control Value Table Cut-In - // 0x1D - function SCVTCI(state) { - var n = state.stack.pop(); +/** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ +function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; +} - if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } +/** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ - state.cvCutIn = n / 0x40; - } +/** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ +function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); +} - // DUP[] DUPlicate top stack element - // 0x20 - function DUP(state) { - var stack = state.stack; +/** + * Create an event instance + * @param {string} eventId event unique id + */ +function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; +} - if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } +/** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ +function initializeCoreEvents(events) { + var this$1 = this; - stack.push(stack[stack.length - 1]); - } + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; - // POP[] POP top stack element - // 0x21 - function POP(state) { - if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); - state.stack.pop(); + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); +} - // CLEAR[] CLEAR the stack - // 0x22 - function CLEAR(state) { - if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } +/** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ +function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; - state.stack.length = 0; - } + initializeCoreEvents.call(this, events); +} - // SWAP[] SWAP the top two elements on the stack - // 0x23 - function SWAP(state) { - var stack = state.stack; +/** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ +Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; +}; - var a = stack.pop(); - var b = stack.pop(); +Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; +}; - if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } +/** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ +Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; +}; - stack.push(a); - stack.push(b); +/** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ +Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); +}; - // DEPTH[] DEPTH of the stack - // 0x24 - function DEPTH(state) { - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } - - stack.push(stack.length); +/** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; } +}; - // LOOPCALL[] LOOPCALL function - // 0x2A - function LOOPCALL(state) { - var stack = state.stack; - var fn = stack.pop(); - var c = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } +/** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } +}; - // saves callers program - var cip = state.ip; - var cprog = state.prog; +/** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; +}; - state.prog = state.funcs[fn]; +/** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } +}; - // executes the function - for (var i = 0; i < c; i++) { - exec(state); +/** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } +}; - if (exports.DEBUG) { console.log( - ++state.step, - i + 1 < c ? 'next loopcall' : 'done loopcall', - i - ); } +/** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ +Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); } + }); + this.registeredModifiers.push(modifierId); +}; - // restores the callers program - state.ip = cip; - state.prog = cprog; +/** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ +Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; } +}; - // CALL[] CALL function - // 0x2B - function CALL(state) { - var fn = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } - - // saves callers program - var cip = state.ip; - var cprog = state.prog; - - state.prog = state.funcs[fn]; +/** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ +Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); +}; - // executes the function - exec(state); +/** + * Sets context params current value index + * @param {number} index context params current value index + */ +ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); +}; - // restores the callers program - state.ip = cip; - state.prog = cprog; +/** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ +ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } +}; - if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } +/** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ +Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); } +}; - // CINDEX[] Copy the INDEXed element to the top of the stack - // 0x25 - function CINDEX(state) { - var stack = state.stack; - var k = stack.pop(); +/** + * Converts all tokens into a string + */ +Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); +}; - if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } +/** + * Get a context by name + * @param {string} contextName context name to get + */ +Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; +}; - // In case of k == 1, it copies the last element after popping - // thus stack.length - k. - stack.push(stack[stack.length - k]); +/** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ +Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; } +}; - // MINDEX[] Move the INDEXed element to the top of the stack - // 0x26 - function MINDEX(state) { - var stack = state.stack; - var k = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } +/** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ +Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; - stack.push(stack.splice(stack.length - k, 1)[0]); + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); } +}; - // FDEF[] Function DEFinition - // 0x2C - function FDEF(state) { - if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; +/** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ +Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; +}; - var fn = stack.pop(); - var ipBegin = ip; - - if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } +/** + * Gets a context range tokens + * @param {contextRange} range a context range + */ +Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); +}; - while (prog[++ip] !== 0x2D){ } +/** + * Gets the ranges of a context + * @param {string} contextName context name + */ +Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } +}; - state.ip = ip; - state.funcs[fn] = prog.slice(ipBegin + 1, ip); +/** + * Resets context ranges to run context update + */ +Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } } +}; - // MDAP[a] Move Direct Absolute Point - // 0x2E-0x2F - function MDAP(round, state) { - var pi = state.stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; +/** + * Updates context ranges + */ +Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); +}; - if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } +/** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ +Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; +}; - var d = pv.distance(p, HPZero); +/** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ +Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); +}; - if (round) { d = state.round(d); } +/** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ +Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; +}; - fv.setRelative(p, HPZero, d, pv); - fv.touch(p); +// ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ +// ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ +// ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ +// jscs:disable maximumLineLength +/** + * Check if a char is Arabic + * @param {string} c a single char + */ +function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); +} - state.rp0 = state.rp1 = pi; - } +/** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ +function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); +} - // IUP[a] Interpolate Untouched Points through the outline - // 0x30 - function IUP(v, state) { - var z2 = state.z2; - var pLen = z2.length - 2; - var cp; - var pp; - var np; +/** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ +function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); +} - if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } +/** + * Check if a char is Latin + * @param {string} c a single char + */ +function isLatinChar(c) { + return /[A-z]/.test(c); +} - for (var i = 0; i < pLen; i++) { - cp = z2[i]; // current point +/** + * Check if a char is whitespace char + * @param {string} c a single char + */ +function isWhiteSpace(c) { + return /\s/.test(c); +} - // if this point has been touched go on - if (v.touched(cp)) { continue; } +/** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ - pp = cp.prevTouched(v); +/** + * Create feature query instance + * @param {Font} font opentype font instance + */ +function FeatureQuery(font) { + this.font = font; + this.features = {}; +} - // no point on the contour has been touched? - if (pp === cp) { continue; } +/** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ - np = cp.nextTouched(v); +/** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ +function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; +} - if (pp === np) { - // only one point on the contour has been touched - // so simply moves the point like that +/** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ +function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); - v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } } - - v.interpolate(cp, pp, np, v); - } + break; + default: + return -1; // not found } + return -1; +} - // SHP[] SHift Point using reference point - // 0x32-0x33 - function SHP(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var loop = state.loop; - var z2 = state.z2; +/** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; +} - while (loop--) - { - var pi = stack.pop(); - var p = z2[pi]; +/** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; +} - var d = pv.distance(rp, rp, false, true); - fv.setRelative(p, p, d, pv); - fv.touch(p); +/** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ +function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; +} - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? - 'loop ' + (state.loop - loop) + ': ' : - '' - ) + - 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi - ); +/** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ +function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } } } - - state.loop = 1; } + return substitutions; +} - // SHC[] SHift Contour using reference point - // 0x36-0x37 - function SHC(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var ci = stack.pop(); - var sp = state.z2[state.contours[ci]]; - var p = sp; - - if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } +/** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; +} - var d = pv.distance(rp, rp, false, true); +/** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ +function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; +} - do { - if (p !== rp) { fv.setRelative(p, p, d, pv); } - p = p.nextPointOnContour; - } while (p !== sp); +/** + * Get default script features indexes + */ +FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } } + return []; +}; - // SHZ[] SHift Zone using reference point - // 0x36-0x37 - function SHZ(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; +/** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); +}; - var e = stack.pop(); +/** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; +}; - if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } +/** + * Get features of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; +}; - var z; - switch (e) { - case 0 : z = state.tZone; break; - case 1 : z = state.gZone; break; - default : throw new Error('Invalid zone'); - } +/** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; +}; - var p; - var d = pv.distance(rp, rp, false, true); - var pLen = z.length - 2; - for (var i = 0; i < pLen; i++) - { - p = z[i]; - fv.setRelative(p, p, d, pv); - //if (p !== rp) fv.setRelative(p, p, d, pv); - } +/** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); } +}; - // SHPIX[] SHift point by a PIXel amount - // 0x38 - function SHPIX(state) { - var stack = state.stack; - var loop = state.loop; - var fv = state.fv; - var d = stack.pop() / 0x40; - var z2 = state.z2; +/** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; +/** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'SHPIX[]', pi, d - ); +/** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ +FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; } - - fv.setRelative(p, p, d); - fv.touch(p); + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; } - - state.loop = 1; } + return substitutions.length ? substitutions : null; +}; - // IP[] Interpolate Point - // 0x39 - function IP(state) { - var stack = state.stack; - var rp1i = state.rp1; - var rp2i = state.rp2; - var loop = state.loop; - var rp1 = state.z0[rp1i]; - var rp2 = state.z1[rp2i]; - var fv = state.fv; - var pv = state.dpv; - var z2 = state.z2; - - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; +/** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ +FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; +}; - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'IP[]', pi, rp1i, '<->', rp2i - ); - } +/** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ +FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; +}; - fv.interpolate(p, rp1, rp2, pv); +/** + * Get lookup table by index + * @param {number} index lookup table index + */ +FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; +}; - fv.touch(p); - } +/** + * Get lookup tables for a feature + * @param {string} feature + */ +FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); +}; - state.loop = 1; +/** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ +FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; +}; - // MSIRP[a] Move Stack Indirect Relative Point - // 0x3A-0x3B - function MSIRP(a, state) { - var stack = state.stack; - var d = stack.pop() / 64; - var pi = stack.pop(); - var p = state.z1[pi]; - var rp0 = state.z0[state.rp0]; - var fv = state.fv; - var pv = state.pv; +/** + * Arabic word context checkers + */ - fv.setRelative(p, rp0, d, pv); - fv.touch(p); +function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); +} + +function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); +} + +var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck +}; - if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } +/** + * Arabic sentence context checkers + */ - state.rp1 = state.rp0; - state.rp2 = pi; - if (a) { state.rp0 = pi; } +function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); +} + +function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; } +} - // ALIGNRP[] Align to reference point. - // 0x3C - function ALIGNRP(state) { - var stack = state.stack; - var rp0i = state.rp0; - var rp0 = state.z0[rp0i]; - var loop = state.loop; - var fv = state.fv; - var pv = state.pv; - var z1 = state.z1; +var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck +}; - while (loop--) { - var pi = stack.pop(); - var p = z1[pi]; +/** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'ALIGNRP[]', pi - ); - } +/** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} - fv.setRelative(p, rp0, 0, pv); - fv.touch(p); - } +/** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); +} - state.loop = 1; +/** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); } +} - // RTG[] Round To Double Grid - // 0x3D - function RTDG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } +/** + * Supported substitutions + */ +var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 +}; - state.round = roundToDoubleGrid; +/** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); } +} - // MIAP[a] Move Indirect Absolute Point - // 0x3E-0x3F - function MIAP(round, state) { - var stack = state.stack; - var n = stack.pop(); - var pi = stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; - var cv = state.cvt[n]; +/** + * Apply Arabic presentation forms to a range of tokens + */ - if (exports.DEBUG) { - console.log( - state.step, - 'MIAP[' + round + ']', - n, '(', cv, ')', pi - ); - } +/** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; +} - var d = pv.distance(p, HPZero); +/** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; +} - if (round) { - if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } +/** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ +function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); +} - d = state.round(d); - } +/** + * Apply Arabic required ligatures feature to a range of tokens + */ - fv.setRelative(p, HPZero, d, pv); +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} - if (state.zp0 === 0) { - p.xo = p.x; - p.yo = p.y; +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); } + }); +} - fv.touch(p); - - state.rp0 = state.rp1 = pi; - } - - // NPUSB[] PUSH N Bytes - // 0x40 - function NPUSHB(state) { - var prog = state.prog; - var ip = state.ip; - var stack = state.stack; - - var n = prog[++ip]; - - if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } +/** + * Latin word context checkers + */ - state.ip = ip; - } +function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); +} + +function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); +} + +var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck +}; - // NPUSHW[] PUSH N Words - // 0x41 - function NPUSHW(state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - var n = prog[++ip]; +/** + * Apply Latin ligature feature to a range of tokens + */ - if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); } + }); +} - state.ip = ip; - } - - // WS[] Write Store - // 0x42 - function WS(state) { - var stack = state.stack; - var store = state.store; +/** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ - if (!store) { store = state.store = []; } +/** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ +function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; +} - var v = stack.pop(); - var l = stack.pop(); +/** + * Sets Bidi text + * @param {string} text a text input + */ +Bidi.prototype.setText = function (text) { + this.text = text; +}; - if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } +/** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ +Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck +}); - store[l] = v; - } +/** + * Register arabic word check + */ +function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); +} - // RS[] Read Store - // 0x43 - function RS(state) { - var stack = state.stack; - var store = state.store; +/** + * Perform pre tokenization procedure then + * tokenize text input + */ +function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); +} - var l = stack.pop(); +/** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ +function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); +} - if (exports.DEBUG) { console.log(state.step, 'RS', l); } - - var v = (store && store[l]) || 0; - - stack.push(v); - } - - // WCVTP[] Write Control Value Table in Pixel units - // 0x44 - function WCVTP(state) { - var stack = state.stack; - - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } - - state.cvt[l] = v / 0x40; - } - - // RCVT[] Read Control Value Table entry - // 0x45 - function RCVT(state) { - var stack = state.stack; - var cvte = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } - - stack.push(state.cvt[cvte] * 0x40); - } - - // GC[] Get Coordinate projected onto the projection vector - // 0x46-0x47 - function GC(a, state) { - var stack = state.stack; - var pi = stack.pop(); - var p = state.z2[pi]; - - if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } - - stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); - } - - // MD[a] Measure Distance - // 0x49-0x4A - function MD(a, state) { - var stack = state.stack; - var pi2 = stack.pop(); - var pi1 = stack.pop(); - var p2 = state.z1[pi2]; - var p1 = state.z0[pi1]; - var d = state.dpv.distance(p1, p2, a, a); - - if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } - - state.stack.push(Math.round(d * 64)); - } - - // MPPEM[] Measure Pixels Per EM - // 0x4B - function MPPEM(state) { - if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } - state.stack.push(state.ppem); +/** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ +Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); } +}; - // FLIPON[] set the auto FLIP Boolean to ON - // 0x4D - function FLIPON(state) { - if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } - state.autoFlip = true; +/** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ +Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); } +}; - // LT[] Less Than - // 0x50 - function LT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } +/** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ +Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); +}; - stack.push(e1 < e2 ? 1 : 0); +/** + * Check if 'glyphIndex' is registered + */ +function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); } +} - // LTEQ[] Less Than or EQual - // 0x53 - function LTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } +/** + * Apply arabic presentation forms features + */ +function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); +} - stack.push(e1 <= e2 ? 1 : 0); - } +/** + * Apply required arabic ligatures + */ +function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); +} - // GTEQ[] Greater Than - // 0x52 - function GT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); +/** + * Apply required arabic ligatures + */ +function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); +} - if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } +/** + * Check if a context is registered + * @param {string} contextId context id + */ +Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); +}; - stack.push(e1 > e2 ? 1 : 0); +/** + * Apply features to registered contexts + */ +Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); } - - // GTEQ[] Greater Than or EQual - // 0x53 - function GTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } - - stack.push(e1 >= e2 ? 1 : 0); + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); } - - // EQ[] EQual - // 0x54 - function EQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } - - stack.push(e2 === e1 ? 1 : 0); + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); } +}; - // NEQ[] Not EQual - // 0x55 - function NEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } - - stack.push(e2 !== e1 ? 1 : 0); +/** + * process text input + * @param {string} text an input text + */ +Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); } +}; - // ODD[] ODD - // 0x56 - function ODD(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } +/** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ +Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); +}; - stack.push(Math.trunc(n) % 2 ? 1 : 0); - } +/** + * Get the current state index of each token + * @param {text} text an input text + */ +Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; +}; - // EVEN[] EVEN - // 0x57 - function EVEN(state) { - var stack = state.stack; - var n = stack.pop(); +// The Font object - if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } +/** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ - stack.push(Math.trunc(n) % 2 ? 0 : 1); +/** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ +function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); } - // IF[] IF test - // 0x58 - function IF(state) { - var test = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; - // if test is true it just continues - // if not the ip is skipped until matching ELSE or EIF - if (!test) { - skip(state, true); + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } } - } - - // EIF[] End IF - // 0x59 - function EIF(state) { - // this can be reached normally when - // executing an else branch. - // -> just ignore it - - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } - } - - // AND[] logical AND - // 0x5A - function AND(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } - - stack.push(e2 && e1 ? 1 : 0); - } - - // OR[] logical OR - // 0x5B - function OR(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } - - stack.push(e2 || e1 ? 1 : 0); - } + }); +} - // NOT[] logical NOT - // 0x5C - function NOT(state) { - var stack = state.stack; - var e = stack.pop(); +/** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ +Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; +}; - if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } +/** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ +Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); +}; - stack.push(e ? 0 : 1); +/** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); } - // DELTAP1[] DELTA exception P1 - // DELTAP2[] DELTA exception P2 - // DELTAP3[] DELTA exception P3 - // 0x5D, 0x71, 0x72 - function DELTAP123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var fv = state.fv; - var pv = state.pv; - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - var z0 = state.z0; - - if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } - - for (var i = 0; i < n; i++) { - var pi = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } - - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + return glyph; +}; - var p = z0[pi]; - fv.setRelative(p, p, mag * ds, pv); +/** + * Update features + * @param {any} options features options + */ +Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; } - } - - // SDB[] Set Delta Base in the graphics state - // 0x5E - function SDB(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } - - state.deltaBase = n; - } - - // SDS[] Set Delta Shift in the graphics state - // 0x5F - function SDS(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } - - state.deltaShift = Math.pow(0.5, n); - } - - // ADD[] ADD - // 0x60 - function ADD(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } - - stack.push(n1 + n2); - } - - // SUB[] SUB - // 0x61 - function SUB(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } - - stack.push(n1 - n2); - } - - // DIV[] DIV - // 0x62 - function DIV(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } - - stack.push(n1 * 64 / n2); - } - - // MUL[] MUL - // 0x63 - function MUL(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } - - stack.push(n1 * n2 / 64); - } - - // ABS[] ABSolute value - // 0x64 - function ABS(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } - - stack.push(Math.abs(n)); - } - - // NEG[] NEGate - // 0x65 - function NEG(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } - - stack.push(-n); - } + }); +}; - // FLOOR[] FLOOR - // 0x66 - function FLOOR(state) { - var stack = state.stack; - var n = stack.pop(); +/** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ +Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; - if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } - stack.push(Math.floor(n / 0x40) * 0x40); - } + var bidi = new Bidi(); - // CEILING[] CEILING - // 0x67 - function CEILING(state) { - var stack = state.stack; - var n = stack.pop(); + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); - if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; - stack.push(Math.ceil(n / 0x40) * 0x40); - } + bidi.applyFeatures(this, features); - // ROUND[ab] ROUND value - // 0x68-0x6B - function ROUND(dt, state) { - var stack = state.stack; - var n = stack.pop(); + var indexes = bidi.getTextGlyphs(s); - if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + var length = indexes.length; - stack.push(state.round(n / 0x40) * 0x40); + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; } + return glyphs; +}; - // WCVTF[] Write Control Value Table in Funits - // 0x70 - function WCVTF(state) { - var stack = state.stack; - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } +/** + * @param {string} + * @return {Number} + */ +Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); +}; - state.cvt[l] = v * state.ppem / state.font.unitsPerEm; +/** + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); } - // DELTAC1[] DELTA exception C1 - // DELTAC2[] DELTA exception C2 - // DELTAC3[] DELTA exception C3 - // 0x73, 0x74, 0x75 - function DELTAC123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - - if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } - - for (var i = 0; i < n; i++) { - var c = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } - - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - - var delta = mag * ds; - - if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + return glyph; +}; - state.cvt[c] += delta; - } +/** + * @param {Number} + * @return {String} + */ +Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; } - // SROUND[] Super ROUND - // 0x76 - function SROUND(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } - - state.round = roundSuper; - - var period; - - switch (n & 0xC0) { - case 0x00: - period = 0.5; - break; - case 0x40: - period = 1; - break; - case 0x80: - period = 2; - break; - default: - throw new Error('invalid SROUND value'); - } - - state.srPeriod = period; - - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: throw new Error('invalid SROUND value'); - } - - n &= 0x0F; - - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } - } + return this.glyphNames.glyphIndexToName(gid); +}; - // S45ROUND[] Super ROUND 45 degrees - // 0x77 - function S45ROUND(state) { - var n = state.stack.pop(); +/** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ +Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; +}; - if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } +/** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ +Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] +}; - state.round = roundSuper; +/** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ +Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; +}; - var period; +/** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ +Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; +}; - switch (n & 0xC0) { - case 0x00: - period = Math.sqrt(2) / 2; - break; - case 0x40: - period = Math.sqrt(2); - break; - case 0x80: - period = 2 * Math.sqrt(2); - break; - default: - throw new Error('invalid S45ROUND value'); - } +/** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ +Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); - state.srPeriod = period; + return glyphPaths; +}; - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: - throw new Error('invalid S45ROUND value'); - } +/** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ +Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); +}; - n &= 0x0F; +/** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); +}; - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } - } +/** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); +}; - // ROFF[] Round Off - // 0x7A - function ROFF(state) { - if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } +/** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); +}; - state.round = roundOff; +/** + * @param {string} + * @return {string} + */ +Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; } +}; - // RUTG[] Round Up To Grid - // 0x7C - function RUTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } +/** + * Validate + */ +Font.prototype.validate = function() { + var _this = this; - state.round = roundUpToGrid; + function assert(predicate, message) { } - // RDTG[] Round Down To Grid - // 0x7D - function RDTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } - - state.round = roundDownToGrid; + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); } - // SCANCTRL[] SCAN conversion ConTRoL - // 0x85 - function SCANCTRL(state) { - var n = state.stack.pop(); + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); - // ignored by opentype.js + // Dimension information + assert(this.unitsPerEm > 0); +}; - if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } +/** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ +Font.prototype.toTables = function() { + return sfnt.fontToTable(this); +}; +/** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ +Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); +}; +/** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ +Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; } - // SDPVTL[a] Set Dual Projection Vector To Line - // 0x86-0x87 - function SDPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; + return buffer; +}; - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; +/** + * Initiate a download of the OpenType font. + */ +Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; + console.warn('Font file could not be downloaded. Try using a different browser.'); } - - state.dpv = getUnitVector(dx, dy); + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); } +}; +/** + * @private + */ +Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 +}; - // GETINFO[] GET INFOrmation - // 0x88 - function GETINFO(state) { - var stack = state.stack; - var sel = stack.pop(); - var r = 0; - - if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } - - // v35 as in no subpixel hinting - if (sel & 0x01) { r = 35; } - - // TODO rotation and stretch currently not supported - // and thus those GETINFO are always 0. - - // opentype.js is always gray scaling - if (sel & 0x20) { r |= 0x1000; } - - stack.push(r); - } - - // ROLL[] ROLL the top three stack elements - // 0x8A - function ROLL(state) { - var stack = state.stack; - var a = stack.pop(); - var b = stack.pop(); - var c = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } - - stack.push(b); - stack.push(a); - stack.push(c); - } - - // MAX[] MAXimum of top two stack elements - // 0x8B - function MAX(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } - - stack.push(Math.max(e1, e2)); - } - - // MIN[] MINimum of top two stack elements - // 0x8C - function MIN(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } - - stack.push(Math.min(e1, e2)); - } - - // SCANTYPE[] SCANTYPE - // 0x8D - function SCANTYPE(state) { - var n = state.stack.pop(); - // ignored by opentype.js - if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } - } - - // INSTCTRL[] INSTCTRL - // 0x8D - function INSTCTRL(state) { - var s = state.stack.pop(); - var v = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } - - switch (s) { - case 1 : state.inhibitGridFit = !!v; return; - case 2 : state.ignoreCvt = !!v; return; - default: throw new Error('invalid INSTCTRL[] selector'); - } - } - - // PUSHB[abc] PUSH Bytes - // 0xB0-0xB7 - function PUSHB(n, state) { - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; - - if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - - state.ip = ip; - } - - // PUSHW[abc] PUSH Words - // 0xB8-0xBF - function PUSHW(n, state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } - - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } - - state.ip = ip; - } - - // MDRP[abcde] Move Direct Relative Point - // 0xD0-0xEF - // (if indirect is 0) - // - // and - // - // MIRP[abcde] Move Indirect Relative Point - // 0xE0-0xFF - // (if indirect is 1) - - function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { - var stack = state.stack; - var cvte = indirect && stack.pop(); - var pi = stack.pop(); - var rp0i = state.rp0; - var rp = state.z0[rp0i]; - var p = state.z1[pi]; - - var md = state.minDis; - var fv = state.fv; - var pv = state.dpv; - var od; // original distance - var d; // moving distance - var sign; // sign of distance - var cv; - - d = od = pv.distance(p, rp, true, true); - sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 - - // TODO consider autoFlip - d = Math.abs(d); - - if (indirect) { - cv = state.cvt[cvte]; - - if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } - } - - if (keepD && d < md) { d = md; } - - if (ro) { d = state.round(d); } - - fv.setRelative(p, rp, sign * d, pv); - fv.touch(p); - - if (exports.DEBUG) { - console.log( - state.step, - (indirect ? 'MIRP[' : 'MDRP[') + - (setRp0 ? 'M' : 'm') + - (keepD ? '>' : '_') + - (ro ? 'R' : '_') + - (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + - ']', - indirect ? - cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : - '', - pi, - '(d =', od, '->', sign * d, ')' - ); - } - - state.rp1 = state.rp0; - state.rp2 = pi; - if (setRp0) { state.rp0 = pi; } - } - - /* - * The instruction table. - */ - instructionTable = [ - /* 0x00 */ SVTCA.bind(undefined, yUnitVector), - /* 0x01 */ SVTCA.bind(undefined, xUnitVector), - /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), - /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), - /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), - /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), - /* 0x06 */ SPVTL.bind(undefined, 0), - /* 0x07 */ SPVTL.bind(undefined, 1), - /* 0x08 */ SFVTL.bind(undefined, 0), - /* 0x09 */ SFVTL.bind(undefined, 1), - /* 0x0A */ SPVFS, - /* 0x0B */ SFVFS, - /* 0x0C */ GPV, - /* 0x0D */ GFV, - /* 0x0E */ SFVTPV, - /* 0x0F */ ISECT, - /* 0x10 */ SRP0, - /* 0x11 */ SRP1, - /* 0x12 */ SRP2, - /* 0x13 */ SZP0, - /* 0x14 */ SZP1, - /* 0x15 */ SZP2, - /* 0x16 */ SZPS, - /* 0x17 */ SLOOP, - /* 0x18 */ RTG, - /* 0x19 */ RTHG, - /* 0x1A */ SMD, - /* 0x1B */ ELSE, - /* 0x1C */ JMPR, - /* 0x1D */ SCVTCI, - /* 0x1E */ undefined, // TODO SSWCI - /* 0x1F */ undefined, // TODO SSW - /* 0x20 */ DUP, - /* 0x21 */ POP, - /* 0x22 */ CLEAR, - /* 0x23 */ SWAP, - /* 0x24 */ DEPTH, - /* 0x25 */ CINDEX, - /* 0x26 */ MINDEX, - /* 0x27 */ undefined, // TODO ALIGNPTS - /* 0x28 */ undefined, - /* 0x29 */ undefined, // TODO UTP - /* 0x2A */ LOOPCALL, - /* 0x2B */ CALL, - /* 0x2C */ FDEF, - /* 0x2D */ undefined, // ENDF (eaten by FDEF) - /* 0x2E */ MDAP.bind(undefined, 0), - /* 0x2F */ MDAP.bind(undefined, 1), - /* 0x30 */ IUP.bind(undefined, yUnitVector), - /* 0x31 */ IUP.bind(undefined, xUnitVector), - /* 0x32 */ SHP.bind(undefined, 0), - /* 0x33 */ SHP.bind(undefined, 1), - /* 0x34 */ SHC.bind(undefined, 0), - /* 0x35 */ SHC.bind(undefined, 1), - /* 0x36 */ SHZ.bind(undefined, 0), - /* 0x37 */ SHZ.bind(undefined, 1), - /* 0x38 */ SHPIX, - /* 0x39 */ IP, - /* 0x3A */ MSIRP.bind(undefined, 0), - /* 0x3B */ MSIRP.bind(undefined, 1), - /* 0x3C */ ALIGNRP, - /* 0x3D */ RTDG, - /* 0x3E */ MIAP.bind(undefined, 0), - /* 0x3F */ MIAP.bind(undefined, 1), - /* 0x40 */ NPUSHB, - /* 0x41 */ NPUSHW, - /* 0x42 */ WS, - /* 0x43 */ RS, - /* 0x44 */ WCVTP, - /* 0x45 */ RCVT, - /* 0x46 */ GC.bind(undefined, 0), - /* 0x47 */ GC.bind(undefined, 1), - /* 0x48 */ undefined, // TODO SCFS - /* 0x49 */ MD.bind(undefined, 0), - /* 0x4A */ MD.bind(undefined, 1), - /* 0x4B */ MPPEM, - /* 0x4C */ undefined, // TODO MPS - /* 0x4D */ FLIPON, - /* 0x4E */ undefined, // TODO FLIPOFF - /* 0x4F */ undefined, // TODO DEBUG - /* 0x50 */ LT, - /* 0x51 */ LTEQ, - /* 0x52 */ GT, - /* 0x53 */ GTEQ, - /* 0x54 */ EQ, - /* 0x55 */ NEQ, - /* 0x56 */ ODD, - /* 0x57 */ EVEN, - /* 0x58 */ IF, - /* 0x59 */ EIF, - /* 0x5A */ AND, - /* 0x5B */ OR, - /* 0x5C */ NOT, - /* 0x5D */ DELTAP123.bind(undefined, 1), - /* 0x5E */ SDB, - /* 0x5F */ SDS, - /* 0x60 */ ADD, - /* 0x61 */ SUB, - /* 0x62 */ DIV, - /* 0x63 */ MUL, - /* 0x64 */ ABS, - /* 0x65 */ NEG, - /* 0x66 */ FLOOR, - /* 0x67 */ CEILING, - /* 0x68 */ ROUND.bind(undefined, 0), - /* 0x69 */ ROUND.bind(undefined, 1), - /* 0x6A */ ROUND.bind(undefined, 2), - /* 0x6B */ ROUND.bind(undefined, 3), - /* 0x6C */ undefined, // TODO NROUND[ab] - /* 0x6D */ undefined, // TODO NROUND[ab] - /* 0x6E */ undefined, // TODO NROUND[ab] - /* 0x6F */ undefined, // TODO NROUND[ab] - /* 0x70 */ WCVTF, - /* 0x71 */ DELTAP123.bind(undefined, 2), - /* 0x72 */ DELTAP123.bind(undefined, 3), - /* 0x73 */ DELTAC123.bind(undefined, 1), - /* 0x74 */ DELTAC123.bind(undefined, 2), - /* 0x75 */ DELTAC123.bind(undefined, 3), - /* 0x76 */ SROUND, - /* 0x77 */ S45ROUND, - /* 0x78 */ undefined, // TODO JROT[] - /* 0x79 */ undefined, // TODO JROF[] - /* 0x7A */ ROFF, - /* 0x7B */ undefined, - /* 0x7C */ RUTG, - /* 0x7D */ RDTG, - /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though - /* 0x7F */ POP, // actually AA, supposed to do only a pop though - /* 0x80 */ undefined, // TODO FLIPPT - /* 0x81 */ undefined, // TODO FLIPRGON - /* 0x82 */ undefined, // TODO FLIPRGOFF - /* 0x83 */ undefined, - /* 0x84 */ undefined, - /* 0x85 */ SCANCTRL, - /* 0x86 */ SDPVTL.bind(undefined, 0), - /* 0x87 */ SDPVTL.bind(undefined, 1), - /* 0x88 */ GETINFO, - /* 0x89 */ undefined, // TODO IDEF - /* 0x8A */ ROLL, - /* 0x8B */ MAX, - /* 0x8C */ MIN, - /* 0x8D */ SCANTYPE, - /* 0x8E */ INSTCTRL, - /* 0x8F */ undefined, - /* 0x90 */ undefined, - /* 0x91 */ undefined, - /* 0x92 */ undefined, - /* 0x93 */ undefined, - /* 0x94 */ undefined, - /* 0x95 */ undefined, - /* 0x96 */ undefined, - /* 0x97 */ undefined, - /* 0x98 */ undefined, - /* 0x99 */ undefined, - /* 0x9A */ undefined, - /* 0x9B */ undefined, - /* 0x9C */ undefined, - /* 0x9D */ undefined, - /* 0x9E */ undefined, - /* 0x9F */ undefined, - /* 0xA0 */ undefined, - /* 0xA1 */ undefined, - /* 0xA2 */ undefined, - /* 0xA3 */ undefined, - /* 0xA4 */ undefined, - /* 0xA5 */ undefined, - /* 0xA6 */ undefined, - /* 0xA7 */ undefined, - /* 0xA8 */ undefined, - /* 0xA9 */ undefined, - /* 0xAA */ undefined, - /* 0xAB */ undefined, - /* 0xAC */ undefined, - /* 0xAD */ undefined, - /* 0xAE */ undefined, - /* 0xAF */ undefined, - /* 0xB0 */ PUSHB.bind(undefined, 1), - /* 0xB1 */ PUSHB.bind(undefined, 2), - /* 0xB2 */ PUSHB.bind(undefined, 3), - /* 0xB3 */ PUSHB.bind(undefined, 4), - /* 0xB4 */ PUSHB.bind(undefined, 5), - /* 0xB5 */ PUSHB.bind(undefined, 6), - /* 0xB6 */ PUSHB.bind(undefined, 7), - /* 0xB7 */ PUSHB.bind(undefined, 8), - /* 0xB8 */ PUSHW.bind(undefined, 1), - /* 0xB9 */ PUSHW.bind(undefined, 2), - /* 0xBA */ PUSHW.bind(undefined, 3), - /* 0xBB */ PUSHW.bind(undefined, 4), - /* 0xBC */ PUSHW.bind(undefined, 5), - /* 0xBD */ PUSHW.bind(undefined, 6), - /* 0xBE */ PUSHW.bind(undefined, 7), - /* 0xBF */ PUSHW.bind(undefined, 8), - /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), - /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), - /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), - /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), - /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), - /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), - /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), - /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), - /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), - /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), - /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), - /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), - /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), - /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), - /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), - /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), - /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), - /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), - /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), - /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), - /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), - /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), - /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), - /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), - /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), - /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), - /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), - /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), - /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), - /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), - /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), - /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), - /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), - /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), - /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), - /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), - /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), - /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), - /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), - /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), - /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), - /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), - /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), - /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), - /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), - /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), - /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), - /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), - /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), - /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), - /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), - /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), - /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), - /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), - /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), - /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), - /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), - /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), - /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), - /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), - /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), - /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), - /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), - /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) - ]; - - /***************************** - Mathematical Considerations - ****************************** - - fv ... refers to freedom vector - pv ... refers to projection vector - rp ... refers to reference point - p ... refers to to point being operated on - d ... refers to distance - - SETRELATIVE: - ============ - - case freedom vector == x-axis: - ------------------------------ - - (pv) - .-' - rpd .-' - .-* - d .-'90°' - .-' ' - .-' ' - *-' ' b - rp ' - ' - ' - p *----------*-------------- (fv) - pm - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b - - y - rpdy = pvns * (x- rpdx) - - y = p.y - - x = rpdx + ( p.y - rpdy ) / pvns - - - case freedom vector == y-axis: - ------------------------------ - - * pm - |\ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ b - | \ - | \ - | \ .-' (pv) - | 90° \.-' - | .-'* rpd - | .-' - * *-' d - p rp - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b: - pvns ... normal slope to pv - - y - rpdy = pvns * (x - rpdx) - - x = p.x - - y = rpdy + pvns * (p.x - rpdx) - - - - generic case: - ------------- - - - .'(fv) - .' - .* pm - .' ! - .' . - .' ! - .' . b - .' ! - * . - p ! - 90° . ... (pv) - ...-*-''' - ...---''' rpd - ...---''' d - *--''' - rp - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b: - pvns... normal slope to pv - - y - rpdy = pvns * (x - rpdx) - - equation of freedom vector line: - fvs ... slope of freedom vector (=fy/fx) - - y - py = fvs * (x - px) - - - on pm both equations are true for same x/y - - y - rpdy = pvns * (x - rpdx) - - y - py = fvs * (x - px) - - form to y and set equal: - - pvns * (x - rpdx) + rpdy = fvs * (x - px) + py - - expand: - - pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py - - switch: - - fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy - - solve for x: - - fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy - - - - fvs * px - pvns * rpdx + rpdy - py - x = ----------------------------------- - fvs - pvns - - and: - - y = fvs * (x - px) + py - - - - INTERPOLATE: - ============ - - Examples of point interpolation. - - The weight of the movement of the reference point gets bigger - the further the other reference point is away, thus the safest - option (that is avoiding 0/0 divisions) is to weight the - original distance of the other point by the sum of both distances. - - If the sum of both distances is 0, then move the point by the - arithmetic average of the movement of both reference points. - - - - - (+6) - rp1o *---->*rp1 - . . (+12) - . . rp2o *---------->* rp2 - . . . . - . . . . - . 10 20 . . - |.........|...................| . - . . . - . . (+8) . - po *------>*p . - . . . - . 12 . 24 . - |...........|.......................| - 36 - - - ------- - - - - (+10) - rp1o *-------->*rp1 - . . (-10) - . . rp2 *<---------* rpo2 - . . . . - . . . . - . 10 . 30 . . - |.........|.............................| - . . - . (+5) . - po *--->* p . - . . . - . . 20 . - |....|..............| - 5 15 - - - ------- - - - (+10) - rp1o *-------->*rp1 - . . - . . - rp2o *-------->*rp2 - - - (+10) - po *-------->* p - - ------- - - - (+10) - rp1o *-------->*rp1 - . . - . .(+30) - rp2o *---------------------------->*rp2 - - - (+25) - po *----------------------->* p - - - - vim: set ts=4 sw=4 expandtab: - *****/ - - /** - * Converts a string into a list of tokens. - */ - - /** - * Create a new token - * @param {string} char a single char - */ - function Token(char) { - this.char = char; - this.state = {}; - this.activeState = null; - } - - /** - * Create a new context range - * @param {number} startIndex range start index - * @param {number} endOffset range end index offset - * @param {string} contextName owner context name - */ - function ContextRange(startIndex, endOffset, contextName) { - this.contextName = contextName; - this.startIndex = startIndex; - this.endOffset = endOffset; - } - - /** - * Check context start and end - * @param {string} contextName a unique context name - * @param {function} checkStart a predicate function the indicates a context's start - * @param {function} checkEnd a predicate function the indicates a context's end - */ - function ContextChecker(contextName, checkStart, checkEnd) { - this.contextName = contextName; - this.openRange = null; - this.ranges = []; - this.checkStart = checkStart; - this.checkEnd = checkEnd; - } - - /** - * @typedef ContextParams - * @type Object - * @property {array} context context items - * @property {number} currentIndex current item index - */ - - /** - * Create a context params - * @param {array} context a list of items - * @param {number} currentIndex current item index - */ - function ContextParams(context, currentIndex) { - this.context = context; - this.index = currentIndex; - this.length = context.length; - this.current = context[currentIndex]; - this.backtrack = context.slice(0, currentIndex); - this.lookahead = context.slice(currentIndex + 1); - } - - /** - * Create an event instance - * @param {string} eventId event unique id - */ - function Event(eventId) { - this.eventId = eventId; - this.subscribers = []; - } - - /** - * Initialize a core events and auto subscribe required event handlers - * @param {any} events an object that enlists core events handlers - */ - function initializeCoreEvents(events) { - var this$1 = this; - - var coreEvents = [ - 'start', 'end', 'next', 'newToken', 'contextStart', - 'contextEnd', 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' - ]; - - coreEvents.forEach(function (eventId) { - Object.defineProperty(this$1.events, eventId, { - value: new Event(eventId) - }); - }); - - if (!!events) { - coreEvents.forEach(function (eventId) { - var event = events[eventId]; - if (typeof event === 'function') { - this$1.events[eventId].subscribe(event); - } - }); - } - var requiresContextUpdate = [ - 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD' - ]; - requiresContextUpdate.forEach(function (eventId) { - this$1.events[eventId].subscribe( - this$1.updateContextsRanges - ); - }); - } - - /** - * Converts a string into a list of tokens - * @param {any} events tokenizer core events - */ - function Tokenizer(events) { - this.tokens = []; - this.registeredContexts = {}; - this.contextCheckers = []; - this.events = {}; - this.registeredModifiers = []; - - initializeCoreEvents.call(this, events); - } - - /** - * Sets the state of a token, usually called by a state modifier. - * @param {string} key state item key - * @param {any} value state item value - */ - Token.prototype.setState = function(key, value) { - this.state[key] = value; - this.activeState = { key: key, value: this.state[key] }; - return this.activeState; - }; - - Token.prototype.getState = function (stateId) { - return this.state[stateId] || null; - }; - - /** - * Checks if an index exists in the tokens list. - * @param {number} index token index - */ - Tokenizer.prototype.inboundIndex = function(index) { - return index >= 0 && index < this.tokens.length; - }; - - /** - * Compose and apply a list of operations (replace, update, delete) - * @param {array} RUDs replace, update and delete operations - * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) - */ - Tokenizer.prototype.composeRUD = function (RUDs) { - var this$1 = this; - - var silent = true; - var state = RUDs.map(function (RUD) { return ( - this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) - ); }); - var hasFAILObject = function (obj) { return ( - typeof obj === 'object' && - obj.hasOwnProperty('FAIL') - ); }; - if (state.every(hasFAILObject)) { - return { - FAIL: "composeRUD: one or more operations hasn't completed successfully", - report: state.filter(hasFAILObject) - }; - } - this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); - }; - - /** - * Replace a range of tokens with a list of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {token} tokens a list of tokens to replace - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { - offset = offset !== null ? offset : this.tokens.length; - var isTokenType = tokens.every(function (token) { return token instanceof Token; }); - if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { - var replaced = this.tokens.splice.apply( - this.tokens, [startIndex, offset].concat(tokens) - ); - if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } - return [replaced, tokens]; - } else { - return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; - } - }; - - /** - * Replace a token with another token - * @param {number} index token index - * @param {token} token a token to replace - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.replaceToken = function (index, token, silent) { - if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { - var replaced = this.tokens.splice(index, 1, token); - if (!silent) { this.dispatch('replaceToken', [index, token]); } - return [replaced[0], token]; - } else { - return { FAIL: 'replaceToken: invalid token or index.' }; - } - }; - - /** - * Removes a range of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { - offset = !isNaN(offset) ? offset : this.tokens.length; - var tokens = this.tokens.splice(startIndex, offset); - if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } - return tokens; - }; - - /** - * Remove a token at a certain index - * @param {number} index token index - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.removeToken = function(index, silent) { - if (!isNaN(index) && this.inboundIndex(index)) { - var token = this.tokens.splice(index, 1); - if (!silent) { this.dispatch('removeToken', [token, index]); } - return token; - } else { - return { FAIL: 'removeToken: invalid token index.' }; - } - }; - - /** - * Insert a list of tokens at a certain index - * @param {array} tokens a list of tokens to insert - * @param {number} index insert the list of tokens at index - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.insertToken = function (tokens, index, silent) { - var tokenType = tokens.every( - function (token) { return token instanceof Token; } - ); - if (tokenType) { - this.tokens.splice.apply( - this.tokens, [index, 0].concat(tokens) - ); - if (!silent) { this.dispatch('insertToken', [tokens, index]); } - return tokens; - } else { - return { FAIL: 'insertToken: invalid token(s).' }; - } - }; - - /** - * A state modifier that is called on 'newToken' event - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a function to update token state - */ - Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { - this.events.newToken.subscribe(function(token, contextParams) { - var conditionParams = [token, contextParams]; - var canApplyModifier = ( - condition === null || - condition.apply(this, conditionParams) === true - ); - var modifierParams = [token, contextParams]; - if (canApplyModifier) { - var newStateValue = modifier.apply(this, modifierParams); - token.setState(modifierId, newStateValue); - } - }); - this.registeredModifiers.push(modifierId); - }; - - /** - * Subscribe a handler to an event - * @param {function} eventHandler an event handler function - */ - Event.prototype.subscribe = function (eventHandler) { - if (typeof eventHandler === 'function') { - return ((this.subscribers.push(eventHandler)) - 1); - } else { - return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; - } - }; - - /** - * Unsubscribe an event handler - * @param {string} subsId subscription id - */ - Event.prototype.unsubscribe = function (subsId) { - this.subscribers.splice(subsId, 1); - }; - - /** - * Sets context params current value index - * @param {number} index context params current value index - */ - ContextParams.prototype.setCurrentIndex = function(index) { - this.index = index; - this.current = this.context[index]; - this.backtrack = this.context.slice(0, index); - this.lookahead = this.context.slice(index + 1); - }; - - /** - * Get an item at an offset from the current value - * example (current value is 3): - * 1 2 [3] 4 5 | items values - * -2 -1 0 1 2 | offset values - * @param {number} offset an offset from current value index - */ - ContextParams.prototype.get = function (offset) { - switch (true) { - case (offset === 0): - return this.current; - case (offset < 0 && Math.abs(offset) <= this.backtrack.length): - return this.backtrack.slice(offset)[0]; - case (offset > 0 && offset <= this.lookahead.length): - return this.lookahead[offset - 1]; - default: - return null; - } - }; - - /** - * Converts a context range into a string value - * @param {contextRange} range a context range - */ - Tokenizer.prototype.rangeToText = function (range) { - if (range instanceof ContextRange) { - return ( - this.getRangeTokens(range) - .map(function (token) { return token.char; }).join('') - ); - } - }; - - /** - * Converts all tokens into a string - */ - Tokenizer.prototype.getText = function () { - return this.tokens.map(function (token) { return token.char; }).join(''); - }; - - /** - * Get a context by name - * @param {string} contextName context name to get - */ - Tokenizer.prototype.getContext = function (contextName) { - var context = this.registeredContexts[contextName]; - return !!context ? context : null; - }; - - /** - * Subscribes a new event handler to an event - * @param {string} eventName event name to subscribe to - * @param {function} eventHandler a function to be invoked on event - */ - Tokenizer.prototype.on = function(eventName, eventHandler) { - var event = this.events[eventName]; - if (!!event) { - return event.subscribe(eventHandler); - } else { - return null; - } - }; - - /** - * Dispatches an event - * @param {string} eventName event name - * @param {any} args event handler arguments - */ - Tokenizer.prototype.dispatch = function(eventName, args) { - var this$1 = this; - - var event = this.events[eventName]; - if (event instanceof Event) { - event.subscribers.forEach(function (subscriber) { - subscriber.apply(this$1, args || []); - }); - } - }; - - /** - * Register a new context checker - * @param {string} contextName a unique context name - * @param {function} contextStartCheck a predicate function that returns true on context start - * @param {function} contextEndCheck a predicate function that returns true on context end - * TODO: call tokenize on registration to update context ranges with the new context. - */ - Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { - if (!!this.getContext(contextName)) { return { - FAIL: - ("context name '" + contextName + "' is already registered.") - }; } - if (typeof contextStartCheck !== 'function') { return { - FAIL: - "missing context start check." - }; } - if (typeof contextEndCheck !== 'function') { return { - FAIL: - "missing context end check." - }; } - var contextCheckers = new ContextChecker( - contextName, contextStartCheck, contextEndCheck - ); - this.registeredContexts[contextName] = contextCheckers; - this.contextCheckers.push(contextCheckers); - return contextCheckers; - }; - - /** - * Gets a context range tokens - * @param {contextRange} range a context range - */ - Tokenizer.prototype.getRangeTokens = function(range) { - var endIndex = range.startIndex + range.endOffset; - return [].concat( - this.tokens - .slice(range.startIndex, endIndex) - ); - }; - - /** - * Gets the ranges of a context - * @param {string} contextName context name - */ - Tokenizer.prototype.getContextRanges = function(contextName) { - var context = this.getContext(contextName); - if (!!context) { - return context.ranges; - } else { - return { FAIL: ("context checker '" + contextName + "' is not registered.") }; - } - }; - - /** - * Resets context ranges to run context update - */ - Tokenizer.prototype.resetContextsRanges = function () { - var registeredContexts = this.registeredContexts; - for (var contextName in registeredContexts) { - if (registeredContexts.hasOwnProperty(contextName)) { - var context = registeredContexts[contextName]; - context.ranges = []; - } - } - }; - - /** - * Updates context ranges - */ - Tokenizer.prototype.updateContextsRanges = function () { - this.resetContextsRanges(); - var chars = this.tokens.map(function (token) { return token.char; }); - for (var i = 0; i < chars.length; i++) { - var contextParams = new ContextParams(chars, i); - this.runContextCheck(contextParams); - } - this.dispatch('updateContextsRanges', [this.registeredContexts]); - }; - - /** - * Sets the end offset of an open range - * @param {number} offset range end offset - * @param {string} contextName context name - */ - Tokenizer.prototype.setEndOffset = function (offset, contextName) { - var startIndex = this.getContext(contextName).openRange.startIndex; - var range = new ContextRange(startIndex, offset, contextName); - var ranges = this.getContext(contextName).ranges; - range.rangeId = contextName + "." + (ranges.length); - ranges.push(range); - this.getContext(contextName).openRange = null; - return range; - }; - - /** - * Runs a context check on the current context - * @param {contextParams} contextParams current context params - */ - Tokenizer.prototype.runContextCheck = function(contextParams) { - var this$1 = this; - - var index = contextParams.index; - this.contextCheckers.forEach(function (contextChecker) { - var contextName = contextChecker.contextName; - var openRange = this$1.getContext(contextName).openRange; - if (!openRange && contextChecker.checkStart(contextParams)) { - openRange = new ContextRange(index, null, contextName); - this$1.getContext(contextName).openRange = openRange; - this$1.dispatch('contextStart', [contextName, index]); - } - if (!!openRange && contextChecker.checkEnd(contextParams)) { - var offset = (index - openRange.startIndex) + 1; - var range = this$1.setEndOffset(offset, contextName); - this$1.dispatch('contextEnd', [contextName, range]); - } - }); - }; - - /** - * Converts a text into a list of tokens - * @param {string} text a text to tokenize - */ - Tokenizer.prototype.tokenize = function (text) { - this.tokens = []; - this.resetContextsRanges(); - var chars = Array.from(text); - this.dispatch('start'); - for (var i = 0; i < chars.length; i++) { - var char = chars[i]; - var contextParams = new ContextParams(chars, i); - this.dispatch('next', [contextParams]); - this.runContextCheck(contextParams); - var token = new Token(char); - this.tokens.push(token); - this.dispatch('newToken', [token, contextParams]); - } - this.dispatch('end', [this.tokens]); - return this.tokens; - }; - - // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ - // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ - // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ - // jscs:disable maximumLineLength - /** - * Check if a char is Arabic - * @param {string} c a single char - */ - function isArabicChar(c) { - return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); - } - - /** - * Check if a char is an isolated arabic char - * @param {string} c a single char - */ - function isIsolatedArabicChar(char) { - return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); - } - - /** - * Check if a char is an Arabic Tashkeel char - * @param {string} c a single char - */ - function isTashkeelArabicChar(char) { - return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); - } - - /** - * Check if a char is Latin - * @param {string} c a single char - */ - function isLatinChar(c) { - return /[A-z]/.test(c); - } - - /** - * Check if a char is whitespace char - * @param {string} c a single char - */ - function isWhiteSpace(c) { - return /\s/.test(c); - } - - /** - * Query a feature by some of it's properties to lookup a glyph substitution. - */ - - /** - * Create feature query instance - * @param {Font} font opentype font instance - */ - function FeatureQuery(font) { - this.font = font; - this.features = {}; - } - - /** - * @typedef SubstitutionAction - * @type Object - * @property {number} id substitution type - * @property {string} tag feature tag - * @property {any} substitution substitution value(s) - */ - - /** - * Create a substitution action instance - * @param {SubstitutionAction} action - */ - function SubstitutionAction(action) { - this.id = action.id; - this.tag = action.tag; - this.substitution = action.substitution; - } - - /** - * Lookup a coverage table - * @param {number} glyphIndex glyph index - * @param {CoverageTable} coverage coverage table - */ - function lookupCoverage(glyphIndex, coverage) { - if (!glyphIndex) { return -1; } - switch (coverage.format) { - case 1: - return coverage.glyphs.indexOf(glyphIndex); - - case 2: - var ranges = coverage.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (glyphIndex >= range.start && glyphIndex <= range.end) { - var offset = glyphIndex - range.start; - return range.index + offset; - } - } - break; - default: - return -1; // not found - } - return -1; - } - - /** - * Handle a single substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ - function singleSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return glyphIndex + subtable.deltaGlyphId; - } - - /** - * Handle a single substitution - format 2 - * @param {ContextParams} contextParams context params to lookup - */ - function singleSubstitutionFormat2(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.substitute[substituteIndex]; - } - - /** - * Lookup a list of coverage tables - * @param {any} coverageList a list of coverage tables - * @param {ContextParams} contextParams context params to lookup - */ - function lookupCoverageList(coverageList, contextParams) { - var lookupList = []; - for (var i = 0; i < coverageList.length; i++) { - var coverage = coverageList[i]; - var glyphIndex = contextParams.current; - glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; - var lookupIndex = lookupCoverage(glyphIndex, coverage); - if (lookupIndex !== -1) { - lookupList.push(lookupIndex); - } - } - if (lookupList.length !== coverageList.length) { return -1; } - return lookupList; - } - - /** - * Handle chaining context substitution - format 3 - * @param {ContextParams} contextParams context params to lookup - */ - function chainingSubstitutionFormat3(contextParams, subtable) { - var lookupsCount = ( - subtable.inputCoverage.length + - subtable.lookaheadCoverage.length + - subtable.backtrackCoverage.length - ); - if (contextParams.context.length < lookupsCount) { return []; } - // INPUT LOOKUP // - var inputLookups = lookupCoverageList( - subtable.inputCoverage, contextParams - ); - if (inputLookups === -1) { return []; } - // LOOKAHEAD LOOKUP // - var lookaheadOffset = subtable.inputCoverage.length - 1; - if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } - var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); - while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { - lookaheadContext.shift(); - } - var lookaheadParams = new ContextParams(lookaheadContext, 0); - var lookaheadLookups = lookupCoverageList( - subtable.lookaheadCoverage, lookaheadParams - ); - // BACKTRACK LOOKUP // - var backtrackContext = [].concat(contextParams.backtrack); - backtrackContext.reverse(); - while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { - backtrackContext.shift(); - } - if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } - var backtrackParams = new ContextParams(backtrackContext, 0); - var backtrackLookups = lookupCoverageList( - subtable.backtrackCoverage, backtrackParams - ); - var contextRulesMatch = ( - inputLookups.length === subtable.inputCoverage.length && - lookaheadLookups.length === subtable.lookaheadCoverage.length && - backtrackLookups.length === subtable.backtrackCoverage.length - ); - var substitutions = []; - if (contextRulesMatch) { - for (var i = 0; i < subtable.lookupRecords.length; i++) { - var lookupRecord = subtable.lookupRecords[i]; - var lookupListIndex = lookupRecord.lookupListIndex; - var lookupTable = this.getLookupByIndex(lookupListIndex); - for (var s = 0; s < lookupTable.subtables.length; s++) { - var subtable$1 = lookupTable.subtables[s]; - var lookup = this.getLookupMethod(lookupTable, subtable$1); - var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); - if (substitutionType === '12') { - for (var n = 0; n < inputLookups.length; n++) { - var glyphIndex = contextParams.get(n); - var substitution = lookup(glyphIndex); - if (substitution) { substitutions.push(substitution); } - } - } - } - } - } - return substitutions; - } - - /** - * Handle ligature substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ - function ligatureSubstitutionFormat1(contextParams, subtable) { - // COVERAGE LOOKUP // - var glyphIndex = contextParams.current; - var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (ligSetIndex === -1) { return null; } - // COMPONENTS LOOKUP - // (!) note, components are ordered in the written direction. - var ligature; - var ligatureSet = subtable.ligatureSets[ligSetIndex]; - for (var s = 0; s < ligatureSet.length; s++) { - ligature = ligatureSet[s]; - for (var l = 0; l < ligature.components.length; l++) { - var lookaheadItem = contextParams.lookahead[l]; - var component = ligature.components[l]; - if (lookaheadItem !== component) { break; } - if (l === ligature.components.length - 1) { return ligature; } - } - } - return null; - } - - /** - * Handle decomposition substitution - format 1 - * @param {number} glyphIndex glyph index - * @param {any} subtable subtable - */ - function decompositionSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.sequences[substituteIndex]; - } - - /** - * Get default script features indexes - */ - FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { - var scripts = this.font.tables.gsub.scripts; - for (var s = 0; s < scripts.length; s++) { - var script = scripts[s]; - if (script.tag === 'DFLT') { return ( - script.script.defaultLangSys.featureIndexes - ); } - } - return []; - }; - - /** - * Get feature indexes of a specific script - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { - var tables = this.font.tables; - if (!tables.gsub) { return []; } - if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } - var scripts = this.font.tables.gsub.scripts; - for (var i = 0; i < scripts.length; i++) { - var script = scripts[i]; - if (script.tag === scriptTag && script.script.defaultLangSys) { - return script.script.defaultLangSys.featureIndexes; - } else { - var langSysRecords = script.langSysRecords; - if (!!langSysRecords) { - for (var j = 0; j < langSysRecords.length; j++) { - var langSysRecord = langSysRecords[j]; - if (langSysRecord.tag === scriptTag) { - var langSys = langSysRecord.langSys; - return langSys.featureIndexes; - } - } - } - } - } - return this.getDefaultScriptFeaturesIndexes(); - }; - - /** - * Map a feature tag to a gsub feature - * @param {any} features gsub features - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { - var tags = {}; - for (var i = 0; i < features.length; i++) { - var tag = features[i].tag; - var feature = features[i].feature; - tags[tag] = feature; - } - this.features[scriptTag].tags = tags; - }; - - /** - * Get features of a specific script - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { - var features = this.features[scriptTag]; - if (this.features.hasOwnProperty(scriptTag)) { return features; } - var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); - if (!featuresIndexes) { return null; } - var gsub = this.font.tables.gsub; - features = featuresIndexes.map(function (index) { return gsub.features[index]; }); - this.features[scriptTag] = features; - this.mapTagsToFeatures(features, scriptTag); - return features; - }; - - /** - * Get substitution type - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ - FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { - var lookupType = lookupTable.lookupType.toString(); - var substFormat = subtable.substFormat.toString(); - return lookupType + substFormat; - }; - - /** - * Get lookup method - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ - FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { - var this$1 = this; - - var substitutionType = this.getSubstitutionType(lookupTable, subtable); - switch (substitutionType) { - case '11': - return function (glyphIndex) { return singleSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - case '12': - return function (glyphIndex) { return singleSubstitutionFormat2.apply( - this$1, [glyphIndex, subtable] - ); }; - case '63': - return function (contextParams) { return chainingSubstitutionFormat3.apply( - this$1, [contextParams, subtable] - ); }; - case '41': - return function (contextParams) { return ligatureSubstitutionFormat1.apply( - this$1, [contextParams, subtable] - ); }; - case '21': - return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - default: - throw new Error( - "lookupType: " + (lookupTable.lookupType) + " - " + - "substFormat: " + (subtable.substFormat) + " " + - "is not yet supported" - ); - } - }; - - /** - * [ LOOKUP TYPES ] - * ------------------------------- - * Single 1; - * Multiple 2; - * Alternate 3; - * Ligature 4; - * Context 5; - * ChainingContext 6; - * ExtensionSubstitution 7; - * ReverseChainingContext 8; - * ------------------------------- - * - */ - - /** - * @typedef FQuery - * @type Object - * @param {string} tag feature tag - * @param {string} script feature script - * @param {ContextParams} contextParams context params - */ - - /** - * Lookup a feature using a query parameters - * @param {FQuery} query feature query - */ - FeatureQuery.prototype.lookupFeature = function (query) { - var contextParams = query.contextParams; - var currentIndex = contextParams.index; - var feature = this.getFeature({ - tag: query.tag, script: query.script - }); - if (!feature) { return new Error( - "font '" + (this.font.names.fullName.en) + "' " + - "doesn't support feature '" + (query.tag) + "' " + - "for script '" + (query.script) + "'." - ); } - var lookups = this.getFeatureLookups(feature); - var substitutions = [].concat(contextParams.context); - for (var l = 0; l < lookups.length; l++) { - var lookupTable = lookups[l]; - var subtables = this.getLookupSubtables(lookupTable); - for (var s = 0; s < subtables.length; s++) { - var subtable = subtables[s]; - var substType = this.getSubstitutionType(lookupTable, subtable); - var lookup = this.getLookupMethod(lookupTable, subtable); - var substitution = (void 0); - switch (substType) { - case '11': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 11, tag: query.tag, substitution: substitution - })); - } - break; - case '12': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 12, tag: query.tag, substitution: substitution - })); - } - break; - case '63': - substitution = lookup(contextParams); - if (Array.isArray(substitution) && substitution.length) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 63, tag: query.tag, substitution: substitution - })); - } - break; - case '41': - substitution = lookup(contextParams); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 41, tag: query.tag, substitution: substitution - })); - } - break; - case '21': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 21, tag: query.tag, substitution: substitution - })); - } - break; - } - contextParams = new ContextParams(substitutions, currentIndex); - if (Array.isArray(substitution) && !substitution.length) { continue; } - substitution = null; - } - } - return substitutions.length ? substitutions : null; - }; - - /** - * Checks if a font supports a specific features - * @param {FQuery} query feature query object - */ - FeatureQuery.prototype.supports = function (query) { - if (!query.script) { return false; } - this.getScriptFeatures(query.script); - var supportedScript = this.features.hasOwnProperty(query.script); - if (!query.tag) { return supportedScript; } - var supportedFeature = ( - this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) - ); - return supportedScript && supportedFeature; - }; - - /** - * Get lookup table subtables - * @param {any} lookupTable lookup table - */ - FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { - return lookupTable.subtables || null; - }; - - /** - * Get lookup table by index - * @param {number} index lookup table index - */ - FeatureQuery.prototype.getLookupByIndex = function (index) { - var lookups = this.font.tables.gsub.lookups; - return lookups[index] || null; - }; - - /** - * Get lookup tables for a feature - * @param {string} feature - */ - FeatureQuery.prototype.getFeatureLookups = function (feature) { - // TODO: memoize - return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); - }; - - /** - * Query a feature by it's properties - * @param {any} query an object that describes the properties of a query - */ - FeatureQuery.prototype.getFeature = function getFeature(query) { - if (!this.font) { return { FAIL: "No font was found"}; } - if (!this.features.hasOwnProperty(query.script)) { - this.getScriptFeatures(query.script); - } - var scriptFeatures = this.features[query.script]; - if (!scriptFeatures) { return ( - { FAIL: ("No feature for script " + (query.script))} - ); } - if (!scriptFeatures.tags[query.tag]) { return null; } - return this.features[query.script].tags[query.tag]; - }; - - /** - * Arabic word context checkers - */ - - function arabicWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? arabic first char - (prevChar === null && isArabicChar(char)) || - // ? arabic char preceded with a non arabic char - (!isArabicChar(prevChar) && isArabicChar(char)) - ); - } - - function arabicWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last arabic char - (nextChar === null) || - // ? next char is not arabic - (!isArabicChar(nextChar)) - ); - } - - var arabicWordCheck = { - startCheck: arabicWordStartCheck, - endCheck: arabicWordEndCheck - }; - - /** - * Arabic sentence context checkers - */ - - function arabicSentenceStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? an arabic char preceded with a non arabic char - (isArabicChar(char) || isTashkeelArabicChar(char)) && - !isArabicChar(prevChar) - ); - } - - function arabicSentenceEndCheck(contextParams) { - var nextChar = contextParams.get(1); - switch (true) { - case nextChar === null: - return true; - case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): - var nextIsWhitespace = isWhiteSpace(nextChar); - if (!nextIsWhitespace) { return true; } - if (nextIsWhitespace) { - var arabicCharAhead = false; - arabicCharAhead = ( - contextParams.lookahead.some( - function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } - ) - ); - if (!arabicCharAhead) { return true; } - } - break; - default: - return false; - } - } - - var arabicSentenceCheck = { - startCheck: arabicSentenceStartCheck, - endCheck: arabicSentenceEndCheck - }; - - /** - * Apply single substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function singleSubstitutionFormat1$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); - } - - /** - * Apply single substitution format 2 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function singleSubstitutionFormat2$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); - } - - /** - * Apply chaining context substitution format 3 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function chainingSubstitutionFormat3$1(action, tokens, index) { - action.substitution.forEach(function (subst, offset) { - var token = tokens[index + offset]; - token.setState(action.tag, subst); - }); - } - - /** - * Apply ligature substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function ligatureSubstitutionFormat1$1(action, tokens, index) { - var token = tokens[index]; - token.setState(action.tag, action.substitution.ligGlyph); - var compsCount = action.substitution.components.length; - for (var i = 0; i < compsCount; i++) { - token = tokens[index + i + 1]; - token.setState('deleted', true); - } - } - - /** - * Supported substitutions - */ - var SUBSTITUTIONS = { - 11: singleSubstitutionFormat1$1, - 12: singleSubstitutionFormat2$1, - 63: chainingSubstitutionFormat3$1, - 41: ligatureSubstitutionFormat1$1 - }; - - /** - * Apply substitutions to a list of tokens - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function applySubstitution(action, tokens, index) { - if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { - SUBSTITUTIONS[action.id](action, tokens, index); - } - } - - /** - * Apply Arabic presentation forms to a range of tokens - */ - - /** - * Check if a char can be connected to it's preceding char - * @param {ContextParams} charContextParams context params of a char - */ - function willConnectPrev(charContextParams) { - var backtrack = [].concat(charContextParams.backtrack); - for (var i = backtrack.length - 1; i >= 0; i--) { - var prevChar = backtrack[i]; - var isolated = isIsolatedArabicChar(prevChar); - var tashkeel = isTashkeelArabicChar(prevChar); - if (!isolated && !tashkeel) { return true; } - if (isolated) { return false; } - } - return false; - } - - /** - * Check if a char can be connected to it's proceeding char - * @param {ContextParams} charContextParams context params of a char - */ - function willConnectNext(charContextParams) { - if (isIsolatedArabicChar(charContextParams.current)) { return false; } - for (var i = 0; i < charContextParams.lookahead.length; i++) { - var nextChar = charContextParams.lookahead[i]; - var tashkeel = isTashkeelArabicChar(nextChar); - if (!tashkeel) { return true; } - } - return false; - } - - /** - * Apply arabic presentation forms to a list of tokens - * @param {ContextRange} range a range of tokens - */ - function arabicPresentationForms(range) { - var this$1 = this; - - var script = 'arab'; - var tags = this.featuresTags[script]; - var tokens = this.tokenizer.getRangeTokens(range); - if (tokens.length === 1) { return; } - var contextParams = new ContextParams( - tokens.map(function (token) { return token.getState('glyphIndex'); } - ), 0); - var charContextParams = new ContextParams( - tokens.map(function (token) { return token.char; } - ), 0); - tokens.forEach(function (token, index) { - if (isTashkeelArabicChar(token.char)) { return; } - contextParams.setCurrentIndex(index); - charContextParams.setCurrentIndex(index); - var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) - if (willConnectPrev(charContextParams)) { CONNECT |= 1; } - if (willConnectNext(charContextParams)) { CONNECT |= 2; } - var tag; - switch (CONNECT) { - case 1: (tag = 'fina'); break; - case 2: (tag = 'init'); break; - case 3: (tag = 'medi'); break; - } - if (tags.indexOf(tag) === -1) { return; } - var substitutions = this$1.query.lookupFeature({ - tag: tag, script: script, contextParams: contextParams - }); - if (substitutions instanceof Error) { return console.info(substitutions.message); } - substitutions.forEach(function (action, index) { - if (action instanceof SubstitutionAction) { - applySubstitution(action, tokens, index); - contextParams.context[index] = action.substitution; - } - }); - }); - } - - /** - * Apply Arabic required ligatures feature to a range of tokens - */ - - /** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ - function getContextParams(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); - } - - /** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ - function arabicRequiredLigatures(range) { - var this$1 = this; - - var script = 'arab'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'rlig', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams(tokens); - } - }); - } - - /** - * Latin word context checkers - */ - - function latinWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? latin first char - (prevChar === null && isLatinChar(char)) || - // ? latin char preceded with a non latin char - (!isLatinChar(prevChar) && isLatinChar(char)) - ); - } - - function latinWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last latin char - (nextChar === null) || - // ? next char is not latin - (!isLatinChar(nextChar)) - ); - } - - var latinWordCheck = { - startCheck: latinWordStartCheck, - endCheck: latinWordEndCheck - }; - - /** - * Apply Latin ligature feature to a range of tokens - */ - - /** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ - function getContextParams$1(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); - } - - /** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ - function latinLigature(range) { - var this$1 = this; - - var script = 'latn'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams$1(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'liga', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams$1(tokens); - } - }); - } - - /** - * Infer bidirectional properties for a given text and apply - * the corresponding layout rules. - */ - - /** - * Create Bidi. features - * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' - */ - function Bidi(baseDir) { - this.baseDir = baseDir || 'ltr'; - this.tokenizer = new Tokenizer(); - this.featuresTags = {}; - } - - /** - * Sets Bidi text - * @param {string} text a text input - */ - Bidi.prototype.setText = function (text) { - this.text = text; - }; - - /** - * Store essential context checks: - * arabic word check for applying gsub features - * arabic sentence check for adjusting arabic layout - */ - Bidi.prototype.contextChecks = ({ - latinWordCheck: latinWordCheck, - arabicWordCheck: arabicWordCheck, - arabicSentenceCheck: arabicSentenceCheck - }); - - /** - * Register arabic word check - */ - function registerContextChecker(checkId) { - var check = this.contextChecks[(checkId + "Check")]; - return this.tokenizer.registerContextChecker( - checkId, check.startCheck, check.endCheck - ); - } - - /** - * Perform pre tokenization procedure then - * tokenize text input - */ - function tokenizeText() { - registerContextChecker.call(this, 'latinWord'); - registerContextChecker.call(this, 'arabicWord'); - registerContextChecker.call(this, 'arabicSentence'); - return this.tokenizer.tokenize(this.text); - } - - /** - * Reverse arabic sentence layout - * TODO: check base dir before applying adjustments - priority low - */ - function reverseArabicSentences() { - var this$1 = this; - - var ranges = this.tokenizer.getContextRanges('arabicSentence'); - ranges.forEach(function (range) { - var rangeTokens = this$1.tokenizer.getRangeTokens(range); - this$1.tokenizer.replaceRange( - range.startIndex, - range.endOffset, - rangeTokens.reverse() - ); - }); - } - - /** - * Register supported features tags - * @param {script} script script tag - * @param {Array} tags features tags list - */ - Bidi.prototype.registerFeatures = function (script, tags) { - var this$1 = this; - - var supportedTags = tags.filter( - function (tag) { return this$1.query.supports({script: script, tag: tag}); } - ); - if (!this.featuresTags.hasOwnProperty(script)) { - this.featuresTags[script] = supportedTags; - } else { - this.featuresTags[script] = - this.featuresTags[script].concat(supportedTags); - } - }; - - /** - * Apply GSUB features - * @param {Array} tagsList a list of features tags - * @param {string} script a script tag - * @param {Font} font opentype font instance - */ - Bidi.prototype.applyFeatures = function (font, features) { - if (!font) { throw new Error( - 'No valid font was provided to apply features' - ); } - if (!this.query) { this.query = new FeatureQuery(font); } - for (var f = 0; f < features.length; f++) { - var feature = features[f]; - if (!this.query.supports({script: feature.script})) { continue; } - this.registerFeatures(feature.script, feature.tags); - } - }; - - /** - * Register a state modifier - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a modifier function to set token state - */ - Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { - this.tokenizer.registerModifier(modifierId, condition, modifier); - }; - - /** - * Check if 'glyphIndex' is registered - */ - function checkGlyphIndexStatus() { - if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { - throw new Error( - 'glyphIndex modifier is required to apply ' + - 'arabic presentation features.' - ); - } - } - - /** - * Apply arabic presentation forms features - */ - function applyArabicPresentationForms() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicPresentationForms.call(this$1, range); - }); - } - - /** - * Apply required arabic ligatures - */ - function applyArabicRequireLigatures() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('rlig') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicRequiredLigatures.call(this$1, range); - }); - } - - /** - * Apply required arabic ligatures - */ - function applyLatinLigatures() { - var this$1 = this; - - var script = 'latn'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('liga') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('latinWord'); - ranges.forEach(function (range) { - latinLigature.call(this$1, range); - }); - } - - /** - * Check if a context is registered - * @param {string} contextId context id - */ - Bidi.prototype.checkContextReady = function (contextId) { - return !!this.tokenizer.getContext(contextId); - }; - - /** - * Apply features to registered contexts - */ - Bidi.prototype.applyFeaturesToContexts = function () { - if (this.checkContextReady('arabicWord')) { - applyArabicPresentationForms.call(this); - applyArabicRequireLigatures.call(this); - } - if (this.checkContextReady('latinWord')) { - applyLatinLigatures.call(this); - } - if (this.checkContextReady('arabicSentence')) { - reverseArabicSentences.call(this); - } - }; - - /** - * process text input - * @param {string} text an input text - */ - Bidi.prototype.processText = function(text) { - if (!this.text || this.text !== text) { - this.setText(text); - tokenizeText.call(this); - this.applyFeaturesToContexts(); - } - }; - - /** - * Process a string of text to identify and adjust - * bidirectional text entities. - * @param {string} text input text - */ - Bidi.prototype.getBidiText = function (text) { - this.processText(text); - return this.tokenizer.getText(); - }; - - /** - * Get the current state index of each token - * @param {text} text an input text - */ - Bidi.prototype.getTextGlyphs = function (text) { - this.processText(text); - var indexes = []; - for (var i = 0; i < this.tokenizer.tokens.length; i++) { - var token = this.tokenizer.tokens[i]; - if (token.state.deleted) { continue; } - var index = token.activeState.value; - indexes.push(Array.isArray(index) ? index[0] : index); - } - return indexes; - }; - - // The Font object - - /** - * @typedef FontOptions - * @type Object - * @property {Boolean} empty - whether to create a new empty font - * @property {string} familyName - * @property {string} styleName - * @property {string=} fullName - * @property {string=} postScriptName - * @property {string=} designer - * @property {string=} designerURL - * @property {string=} manufacturer - * @property {string=} manufacturerURL - * @property {string=} license - * @property {string=} licenseURL - * @property {string=} version - * @property {string=} description - * @property {string=} copyright - * @property {string=} trademark - * @property {Number} unitsPerEm - * @property {Number} ascender - * @property {Number} descender - * @property {Number} createdTimestamp - * @property {string=} weightClass - * @property {string=} widthClass - * @property {string=} fsSelection - */ - - /** - * A Font represents a loaded OpenType font file. - * It contains a set of glyphs and methods to draw text on a drawing context, - * or to get a path representing the text. - * @exports opentype.Font - * @class - * @param {FontOptions} - * @constructor - */ - function Font(options) { - options = options || {}; - options.tables = options.tables || {}; - - if (!options.empty) { - // Check that we've provided the minimum set of names. - checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); - checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); - checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); - checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); - checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); - - // OS X will complain if the names are empty, so we put a single space everywhere by default. - this.names = { - fontFamily: {en: options.familyName || ' '}, - fontSubfamily: {en: options.styleName || ' '}, - fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, - // postScriptName may not contain any whitespace - postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, - designer: {en: options.designer || ' '}, - designerURL: {en: options.designerURL || ' '}, - manufacturer: {en: options.manufacturer || ' '}, - manufacturerURL: {en: options.manufacturerURL || ' '}, - license: {en: options.license || ' '}, - licenseURL: {en: options.licenseURL || ' '}, - version: {en: options.version || 'Version 0.1'}, - description: {en: options.description || ' '}, - copyright: {en: options.copyright || ' '}, - trademark: {en: options.trademark || ' '} - }; - this.unitsPerEm = options.unitsPerEm || 1000; - this.ascender = options.ascender; - this.descender = options.descender; - this.createdTimestamp = options.createdTimestamp; - this.tables = Object.assign(options.tables, { - os2: Object.assign({ - usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, - usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, - fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, - }, options.tables.os2) - }); - } - - this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. - this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); - this.encoding = new DefaultEncoding(this); - this.position = new Position(this); - this.substitution = new Substitution(this); - this.tables = this.tables || {}; - - // needed for low memory mode only. - this._push = null; - this._hmtxTableData = {}; - - Object.defineProperty(this, 'hinting', { - get: function() { - if (this._hinting) { return this._hinting; } - if (this.outlinesFormat === 'truetype') { - return (this._hinting = new Hinting(this)); - } - } - }); - } - - /** - * Check if the font has a glyph for the given character. - * @param {string} - * @return {Boolean} - */ - Font.prototype.hasChar = function(c) { - return this.encoding.charToGlyphIndex(c) !== null; - }; - - /** - * Convert the given character to a single glyph index. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {Number} - */ - Font.prototype.charToGlyphIndex = function(s) { - return this.encoding.charToGlyphIndex(s); - }; - - /** - * Convert the given character to a single Glyph object. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {opentype.Glyph} - */ - Font.prototype.charToGlyph = function(c) { - var glyphIndex = this.charToGlyphIndex(c); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); - } - - return glyph; - }; - - /** - * Update features - * @param {any} options features options - */ - Font.prototype.updateFeatures = function (options) { - // TODO: update all features options not only 'latn'. - return this.defaultRenderOptions.features.map(function (feature) { - if (feature.script === 'latn') { - return { - script: 'latn', - tags: feature.tags.filter(function (tag) { return options[tag]; }) - }; - } else { - return feature; - } - }); - }; - - /** - * Convert the given text to a list of Glyph objects. - * Note that there is no strict one-to-one mapping between characters and - * glyphs, so the list of returned glyphs can be larger or smaller than the - * length of the given string. - * @param {string} - * @param {GlyphRenderOptions} [options] - * @return {opentype.Glyph[]} - */ - Font.prototype.stringToGlyphs = function(s, options) { - var this$1 = this; - - - var bidi = new Bidi(); - - // Create and register 'glyphIndex' state modifier - var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; - bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); - - // roll-back to default features - var features = options ? - this.updateFeatures(options.features) : - this.defaultRenderOptions.features; - - bidi.applyFeatures(this, features); - - var indexes = bidi.getTextGlyphs(s); - - var length = indexes.length; - - // convert glyph indexes to glyph objects - var glyphs = new Array(length); - var notdef = this.glyphs.get(0); - for (var i = 0; i < length; i += 1) { - glyphs[i] = this.glyphs.get(indexes[i]) || notdef; - } - return glyphs; - }; - - /** - * @param {string} - * @return {Number} - */ - Font.prototype.nameToGlyphIndex = function(name) { - return this.glyphNames.nameToGlyphIndex(name); - }; - - /** - * @param {string} - * @return {opentype.Glyph} - */ - Font.prototype.nameToGlyph = function(name) { - var glyphIndex = this.nameToGlyphIndex(name); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); - } - - return glyph; - }; - - /** - * @param {Number} - * @return {String} - */ - Font.prototype.glyphIndexToName = function(gid) { - if (!this.glyphNames.glyphIndexToName) { - return ''; - } - - return this.glyphNames.glyphIndexToName(gid); - }; - - /** - * Retrieve the value of the kerning pair between the left glyph (or its index) - * and the right glyph (or its index). If no kerning pair is found, return 0. - * The kerning value gets added to the advance width when calculating the spacing - * between glyphs. - * For GPOS kerning, this method uses the default script and language, which covers - * most use cases. To have greater control, use font.position.getKerningValue . - * @param {opentype.Glyph} leftGlyph - * @param {opentype.Glyph} rightGlyph - * @return {Number} - */ - Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { - leftGlyph = leftGlyph.index || leftGlyph; - rightGlyph = rightGlyph.index || rightGlyph; - var gposKerning = this.position.defaultKerningTables; - if (gposKerning) { - return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); - } - // "kern" table - return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; - }; - - /** - * @typedef GlyphRenderOptions - * @type Object - * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. - * See https://www.microsoft.com/typography/otspec/scripttags.htm - * @property {string} [language='dflt'] - language system used to determine which features to apply. - * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx - * @property {boolean} [kerning=true] - whether to include kerning values - * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. - * See https://www.microsoft.com/typography/otspec/featuretags.htm - */ - Font.prototype.defaultRenderOptions = { - kerning: true, - features: [ - /** - * these 4 features are required to render Arabic text properly - * and shouldn't be turned off when rendering arabic text. - */ - { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, - { script: 'latn', tags: ['liga', 'rlig'] } - ] - }; - - /** - * Helper function that invokes the given callback for each glyph in the given text. - * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text - * @param {string} text - The text to apply. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @param {Function} callback - */ - Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - options = Object.assign({}, this.defaultRenderOptions, options); - var fontScale = 1 / this.unitsPerEm * fontSize; - var glyphs = this.stringToGlyphs(text, options); - var kerningLookups; - if (options.kerning) { - var script = options.script || this.position.getDefaultScriptName(); - kerningLookups = this.position.getKerningTables(script, options.language); - } - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs[i]; - callback.call(this, glyph, x, y, fontSize, options); - if (glyph.advanceWidth) { - x += glyph.advanceWidth * fontScale; - } - - if (options.kerning && i < glyphs.length - 1) { - // We should apply position adjustment lookups in a more generic way. - // Here we only use the xAdvance value. - var kerningValue = kerningLookups ? - this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : - this.getKerningValue(glyph, glyphs[i + 1]); - x += kerningValue * fontScale; - } - - if (options.letterSpacing) { - x += options.letterSpacing * fontSize; - } else if (options.tracking) { - x += (options.tracking / 1000) * fontSize; - } - } - return x; - }; - - /** - * Create a Path object that represents the given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path} - */ - Font.prototype.getPath = function(text, x, y, fontSize, options) { - var fullPath = new Path(); - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - fullPath.extend(glyphPath); - }); - return fullPath; - }; - - /** - * Create an array of Path objects that represent the glyphs of a given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path[]} - */ - Font.prototype.getPaths = function(text, x, y, fontSize, options) { - var glyphPaths = []; - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - glyphPaths.push(glyphPath); - }); - - return glyphPaths; - }; - - /** - * Returns the advance width of a text. - * - * This is something different than Path.getBoundingBox() as for example a - * suffixed whitespace increases the advanceWidth but not the bounding box - * or an overhanging letter like a calligraphic 'f' might have a quite larger - * bounding box than its advance width. - * - * This corresponds to canvas2dContext.measureText(text).width - * - * @param {string} text - The text to create. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return advance width - */ - Font.prototype.getAdvanceWidth = function(text, fontSize, options) { - return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); - }; - - /** - * Draw the text on the given drawing context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { - this.getPath(text, x, y, fontSize, options).draw(ctx); - }; - - /** - * Draw the points of all glyphs in the text. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawPoints(ctx, gX, gY, gFontSize); - }); - }; - - /** - * Draw lines indicating important font measurements for all glyphs in the text. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawMetrics(ctx, gX, gY, gFontSize); - }); - }; - - /** - * @param {string} - * @return {string} - */ - Font.prototype.getEnglishName = function(name) { - var translations = this.names[name]; - if (translations) { - return translations.en; - } - }; - - /** - * Validate - */ - Font.prototype.validate = function() { - var _this = this; - - function assert(predicate, message) { - } - - function assertNamePresent(name) { - var englishName = _this.getEnglishName(name); - assert(englishName && englishName.trim().length > 0); - } - - // Identification information - assertNamePresent('fontFamily'); - assertNamePresent('weightName'); - assertNamePresent('manufacturer'); - assertNamePresent('copyright'); - assertNamePresent('version'); - - // Dimension information - assert(this.unitsPerEm > 0); - }; - - /** - * Convert the font object to a SFNT data structure. - * This structure contains all the necessary tables and metadata to create a binary OTF file. - * @return {opentype.Table} - */ - Font.prototype.toTables = function() { - return sfnt.fontToTable(this); - }; - /** - * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. - */ - Font.prototype.toBuffer = function() { - console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); - return this.toArrayBuffer(); - }; - /** - * Converts a `opentype.Font` into an `ArrayBuffer` - * @return {ArrayBuffer} - */ - Font.prototype.toArrayBuffer = function() { - var sfntTable = this.toTables(); - var bytes = sfntTable.encode(); - var buffer = new ArrayBuffer(bytes.length); - var intArray = new Uint8Array(buffer); - for (var i = 0; i < bytes.length; i++) { - intArray[i] = bytes[i]; - } - - return buffer; - }; - - /** - * Initiate a download of the OpenType font. - */ - Font.prototype.download = function(fileName) { - var familyName = this.getEnglishName('fontFamily'); - var styleName = this.getEnglishName('fontSubfamily'); - fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; - var arrayBuffer = this.toArrayBuffer(); - - if (isBrowser()) { - window.URL = window.URL || window.webkitURL; - - if (window.URL) { - var dataView = new DataView(arrayBuffer); - var blob = new Blob([dataView], {type: 'font/opentype'}); - - var link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = fileName; - - var event = document.createEvent('MouseEvents'); - event.initEvent('click', true, false); - link.dispatchEvent(event); - } else { - console.warn('Font file could not be downloaded. Try using a different browser.'); - } - } else { - var fs = require('fs'); - var buffer = arrayBufferToNodeBuffer(arrayBuffer); - fs.writeFileSync(fileName, buffer); - } - }; - /** - * @private - */ - Font.prototype.fsSelectionValues = { - ITALIC: 0x001, //1 - UNDERSCORE: 0x002, //2 - NEGATIVE: 0x004, //4 - OUTLINED: 0x008, //8 - STRIKEOUT: 0x010, //16 - BOLD: 0x020, //32 - REGULAR: 0x040, //64 - USER_TYPO_METRICS: 0x080, //128 - WWS: 0x100, //256 - OBLIQUE: 0x200 //512 - }; - - /** - * @private - */ - Font.prototype.usWidthClasses = { - ULTRA_CONDENSED: 1, - EXTRA_CONDENSED: 2, - CONDENSED: 3, - SEMI_CONDENSED: 4, - MEDIUM: 5, - SEMI_EXPANDED: 6, - EXPANDED: 7, - EXTRA_EXPANDED: 8, - ULTRA_EXPANDED: 9 - }; - - /** - * @private - */ - Font.prototype.usWeightClasses = { - THIN: 100, - EXTRA_LIGHT: 200, - LIGHT: 300, - NORMAL: 400, - MEDIUM: 500, - SEMI_BOLD: 600, - BOLD: 700, - EXTRA_BOLD: 800, - BLACK: 900 - }; +/** + * @private + */ +Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 +}; - // The `fvar` table stores font variation axes and instances. +/** + * @private + */ +Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 +}; - function addName(name, names) { - var nameString = JSON.stringify(name); - var nameID = 256; - for (var nameKey in names) { - var n = parseInt(nameKey); - if (!n || n < 256) { - continue; - } +// The `fvar` table stores font variation axes and instances. - if (JSON.stringify(names[nameKey]) === nameString) { - return n; - } +function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } - if (nameID <= n) { - nameID = n + 1; - } + if (JSON.stringify(names[nameKey]) === nameString) { + return n; } - names[nameID] = name; - return nameID; + if (nameID <= n) { + nameID = n + 1; + } } - function makeFvarAxis(n, axis, names) { - var nameID = addName(axis.name, names); - return [ - {name: 'tag_' + n, type: 'TAG', value: axis.tag}, - {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, - {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, - {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, - {name: 'flags_' + n, type: 'USHORT', value: 0}, - {name: 'nameID_' + n, type: 'USHORT', value: nameID} - ]; - } + names[nameID] = name; + return nameID; +} + +function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; +} + +function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; +} + +function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; - function parseFvarAxis(data, start, names) { - var axis = {}; - var p = new parse.Parser(data, start); - axis.tag = p.parseTag(); - axis.minValue = p.parseFixed(); - axis.defaultValue = p.parseFixed(); - axis.maxValue = p.parseFixed(); - p.skip('uShort', 1); // reserved for flags; no values defined - axis.name = names[p.parseUShort()] || {}; - return axis; + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); } - function makeFvarInstance(n, inst, axes, names) { - var nameID = addName(inst.name, names); - var fields = [ - {name: 'nameID_' + n, type: 'USHORT', value: nameID}, - {name: 'flags_' + n, type: 'USHORT', value: 0} - ]; + return fields; +} - for (var i = 0; i < axes.length; ++i) { - var axisTag = axes[i].tag; - fields.push({ - name: 'axis_' + n + ' ' + axisTag, - type: 'FIXED', - value: inst.coordinates[axisTag] << 16 - }); - } +function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined - return fields; + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); } - function parseFvarInstance(data, start, axes, names) { - var inst = {}; - var p = new parse.Parser(data, start); - inst.name = names[p.parseUShort()] || {}; - p.skip('uShort', 1); // reserved for flags; no values defined + return inst; +} - inst.coordinates = {}; - for (var i = 0; i < axes.length; ++i) { - inst.coordinates[axes[i].tag] = p.parseFixed(); - } +function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); - return inst; + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); } - function makeFvarTable(fvar, names) { - var result = new table.Table('fvar', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'offsetToData', type: 'USHORT', value: 0}, - {name: 'countSizePairs', type: 'USHORT', value: 2}, - {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, - {name: 'axisSize', type: 'USHORT', value: 20}, - {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, - {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} - ]); - result.offsetToData = result.sizeOf(); + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } - for (var i = 0; i < fvar.axes.length; i++) { - result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); - } + return result; +} - for (var j = 0; j < fvar.instances.length; j++) { - result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); - } +function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); - return result; + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); } - function parseFvarTable(data, start, names) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); - var offsetToData = p.parseOffset16(); - // Skip countSizePairs. - p.skip('uShort', 1); - var axisCount = p.parseUShort(); - var axisSize = p.parseUShort(); - var instanceCount = p.parseUShort(); - var instanceSize = p.parseUShort(); + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } - var axes = []; - for (var i = 0; i < axisCount; i++) { - axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); - } + return {axes: axes, instances: instances}; +} - var instances = []; - var instanceStart = start + offsetToData + axisCount * axisSize; - for (var j = 0; j < instanceCount; j++) { - instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); - } +var fvar = { make: makeFvarTable, parse: parseFvarTable }; - return {axes: axes, instances: instances}; - } +// The `GDEF` table contains various glyph properties - var fvar = { make: makeFvarTable, parse: parseFvarTable }; +var attachList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) + }; +}; - // The `GDEF` table contains various glyph properties +var caretValue = function() { + var format = this.parseUShort(); + check.argument(format === 1 || format === 2 || format === 3, + 'Unsupported CaretValue table version.'); + if (format === 1) { + return { coordinate: this.parseShort() }; + } else if (format === 2) { + return { pointindex: this.parseShort() }; + } else if (format === 3) { + // Device / Variation Index tables unsupported + return { coordinate: this.parseShort() }; + } +}; - var attachList = function() { - return { - coverage: this.parsePointer(Parser.coverage), - attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) - }; - }; +var ligGlyph = function() { + return this.parseList(Parser.pointer(caretValue)); +}; - var caretValue = function() { - var format = this.parseUShort(); - check.argument(format === 1 || format === 2 || format === 3, - 'Unsupported CaretValue table version.'); - if (format === 1) { - return { coordinate: this.parseShort() }; - } else if (format === 2) { - return { pointindex: this.parseShort() }; - } else if (format === 3) { - // Device / Variation Index tables unsupported - return { coordinate: this.parseShort() }; - } +var ligCaretList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) }; +}; - var ligGlyph = function() { - return this.parseList(Parser.pointer(caretValue)); - }; +var markGlyphSets = function() { + this.parseUShort(); // Version + return this.parseList(Parser.pointer(Parser.coverage)); +}; - var ligCaretList = function() { +function parseGDEFTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, + 'Unsupported GDEF table version.'); + var gdef = { + version: tableVersion, + classDef: p.parsePointer(Parser.classDef), + attachList: p.parsePointer(attachList), + ligCaretList: p.parsePointer(ligCaretList), + markAttachClassDef: p.parsePointer(Parser.classDef) + }; + if (tableVersion >= 1.2) { + gdef.markGlyphSets = p.parsePointer(markGlyphSets); + } + return gdef; +} +var gdef = { parse: parseGDEFTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable +// this = Parser instance +subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { return { + posFormat: 1, coverage: this.parsePointer(Parser.coverage), - ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) + value: this.parseValueRecord() }; - }; - - var markGlyphSets = function() { - this.parseUShort(); // Version - return this.parseList(Parser.pointer(Parser.coverage)); - }; - - function parseGDEFTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, - 'Unsupported GDEF table version.'); - var gdef = { - version: tableVersion, - classDef: p.parsePointer(Parser.classDef), - attachList: p.parsePointer(attachList), - ligCaretList: p.parsePointer(ligCaretList), - markAttachClassDef: p.parsePointer(Parser.classDef) + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() }; - if (tableVersion >= 1.2) { - gdef.markGlyphSets = p.parsePointer(markGlyphSets); - } - return gdef; } - var gdef = { parse: parseGDEFTable }; - - // The `GPOS` table contains kerning pairs, among other things. - - var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable - // this = Parser instance - subtableParsers$1[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var posformat = this.parseUShort(); - if (posformat === 1) { - return { - posFormat: 1, - coverage: this.parsePointer(Parser.coverage), - value: this.parseValueRecord() - }; - } else if (posformat === 2) { - return { - posFormat: 2, - coverage: this.parsePointer(Parser.coverage), - values: this.parseValueRecordList() - }; - } - check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); - }; - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable - subtableParsers$1[2] = function parseLookup2() { - var start = this.offset + this.relativeOffset; - var posFormat = this.parseUShort(); - check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); - var coverage = this.parsePointer(Parser.coverage); - var valueFormat1 = this.parseUShort(); - var valueFormat2 = this.parseUShort(); - if (posFormat === 1) { - // Adjustments for Glyph Pairs - return { - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - pairSets: this.parseList(Parser.pointer(Parser.list(function() { - return { // pairValueRecord - secondGlyph: this.parseUShort(), - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - }))) - }; - } else if (posFormat === 2) { - var classDef1 = this.parsePointer(Parser.classDef); - var classDef2 = this.parsePointer(Parser.classDef); - var class1Count = this.parseUShort(); - var class2Count = this.parseUShort(); - return { - // Class Pair Adjustment - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - classDef1: classDef1, - classDef2: classDef2, - class1Count: class1Count, - class2Count: class2Count, - classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { - return { - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - })) - }; - } - }; - - subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; - subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; - subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; - subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; - subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; - subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; - subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos - function parseGposTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); - - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1), - variations: p.parseFeatureVariationsList() - }; - } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); +}; +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable +subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; } +}; - // GPOS Writing ////////////////////////////////////////////// - // NOT SUPPORTED - var subtableMakers$1 = new Array(10); +subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; +subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; +subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; +subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; +subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; +subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; +subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos +function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); - function makeGposTable(gpos) { - return new table.Table('GPOS', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} - ]); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; } - var gpos = { parse: parseGposTable, make: makeGposTable }; +} - // The `kern` table contains kerning pairs. +// GPOS Writing ////////////////////////////////////////////// +// NOT SUPPORTED +var subtableMakers$1 = new Array(10); - function parseWindowsKernTable(p) { - var pairs = {}; - // Skip nTables. - p.skip('uShort'); - var subtableVersion = p.parseUShort(); - check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); - // Skip subtableLength, subtableCoverage - p.skip('uShort', 2); +function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); +} + +var gpos = { parse: parseGposTable, make: makeGposTable }; + +// The `kern` table contains kerning pairs. + +function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + return pairs; +} + +function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { var nPairs = p.parseUShort(); // Skip searchRange, entrySelector, rangeShift. p.skip('uShort', 3); @@ -14074,491 +14101,460 @@ const { BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer } = / var value = p.parseShort(); pairs[leftIndex + ',' + rightIndex] = value; } - return pairs; - } - - function parseMacKernTable(p) { - var pairs = {}; - // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. - // Skip the rest. - p.skip('uShort'); - var nTables = p.parseULong(); - //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); - if (nTables > 1) { - console.warn('Only the first kern subtable is supported.'); - } - p.skip('uLong'); - var coverage = p.parseUShort(); - var subtableVersion = coverage & 0xFF; - p.skip('uShort'); - if (subtableVersion === 0) { - var nPairs = p.parseUShort(); - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - for (var i = 0; i < nPairs; i += 1) { - var leftIndex = p.parseUShort(); - var rightIndex = p.parseUShort(); - var value = p.parseShort(); - pairs[leftIndex + ',' + rightIndex] = value; - } - } - return pairs; } + return pairs; +} - // Parse the `kern` table which contains kerning pairs. - function parseKernTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseUShort(); - if (tableVersion === 0) { - return parseWindowsKernTable(p); - } else if (tableVersion === 1) { - return parseMacKernTable(p); - } else { - throw new Error('Unsupported kern table version (' + tableVersion + ').'); - } - } - - var kern = { parse: parseKernTable }; - - // The `loca` table stores the offsets to the locations of the glyphs in the font. - - // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, - // relative to the beginning of the glyphData table. - // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) - // The loca table has two versions: a short version where offsets are stored as uShorts, and a long - // version where offsets are stored as uLongs. The `head` table specifies which version to use - // (under indexToLocFormat). - function parseLocaTable(data, start, numGlyphs, shortVersion) { - var p = new parse.Parser(data, start); - var parseFn = shortVersion ? p.parseUShort : p.parseULong; - // There is an extra entry after the last index element to compute the length of the last glyph. - // That's why we use numGlyphs + 1. - var glyphOffsets = []; - for (var i = 0; i < numGlyphs + 1; i += 1) { - var glyphOffset = parseFn.call(p); - if (shortVersion) { - // The short table version stores the actual offset divided by 2. - glyphOffset *= 2; - } +// Parse the `kern` table which contains kerning pairs. +function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } +} + +var kern = { parse: parseKernTable }; + +// The `loca` table stores the offsets to the locations of the glyphs in the font. - glyphOffsets.push(glyphOffset); +// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, +// relative to the beginning of the glyphData table. +// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) +// The loca table has two versions: a short version where offsets are stored as uShorts, and a long +// version where offsets are stored as uLongs. The `head` table specifies which version to use +// (under indexToLocFormat). +function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; } - return glyphOffsets; + glyphOffsets.push(glyphOffset); } - var loca = { parse: parseLocaTable }; + return glyphOffsets; +} - // opentype.js +var loca = { parse: parseLocaTable }; - /** - * The opentype library. - * @namespace opentype - */ +// opentype.js - // File loaders ///////////////////////////////////////////////////////// - /** - * Loads a font from a file. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} path - The path of the file - * @param {Function} callback - The function to call when the font load completes - */ - function loadFromFile(path, callback) { - var fs = require('fs'); - fs.readFile(path, function(err, buffer) { - if (err) { - return callback(err.message); - } +/** + * The opentype library. + * @namespace opentype + */ - callback(null, nodeBufferToArrayBuffer(buffer)); - }); - } - /** - * Loads a font from a URL. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} url - The URL of the font file. - * @param {Function} callback - The function to call when the font load completes - */ - function loadFromUrl(url, callback) { - var request = new XMLHttpRequest(); - request.open('get', url, true); - request.responseType = 'arraybuffer'; - request.onload = function() { - if (request.response) { - return callback(null, request.response); - } else { - return callback('Font could not be loaded: ' + request.statusText); - } - }; +// File loaders ///////////////////////////////////////////////////////// +/** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } - request.onerror = function () { - callback('Font could not be loaded'); - }; + callback(null, nodeBufferToArrayBuffer(buffer)); + }); +} +/** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; - request.send(); - } + request.onerror = function () { + callback('Font could not be loaded'); + }; - // Table Directory Entries ////////////////////////////////////////////// - /** - * Parses OpenType table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ - function parseOpenTypeTableEntries(data, numTables) { - var tableEntries = []; - var p = 12; - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var checksum = parse.getULong(data, p + 4); - var offset = parse.getULong(data, p + 8); - var length = parse.getULong(data, p + 12); - tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); - p += 16; - } + request.send(); +} - return tableEntries; +// Table Directory Entries ////////////////////////////////////////////// +/** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; } - /** - * Parses WOFF table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ - function parseWOFFTableEntries(data, numTables) { - var tableEntries = []; - var p = 44; // offset to the first table directory entry. - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var offset = parse.getULong(data, p + 4); - var compLength = parse.getULong(data, p + 8); - var origLength = parse.getULong(data, p + 12); - var compression = (void 0); - if (compLength < origLength) { - compression = 'WOFF'; - } else { - compression = false; - } + return tableEntries; +} - tableEntries.push({tag: tag, offset: offset, compression: compression, - compressedLength: compLength, length: origLength}); - p += 20; +/** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; } - return tableEntries; + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; } - /** - * @typedef TableData - * @type Object - * @property {DataView} data - The DataView - * @property {number} offset - The data offset. - */ + return tableEntries; +} - /** - * @param {DataView} - * @param {Object} - * @return {TableData} - */ - function uncompressTable(data, tableEntry) { - if (tableEntry.compression === 'WOFF') { - var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); - var outBuffer = new Uint8Array(tableEntry.length); - tinyInflate(inBuffer, outBuffer); - if (outBuffer.byteLength !== tableEntry.length) { - throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); - } +/** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ - var view = new DataView(outBuffer.buffer, 0); - return {data: view, offset: 0}; - } else { - return {data: data, offset: tableEntry.offset}; +/** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ +function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); } + + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; } +} - // Public API /////////////////////////////////////////////////////////// +// Public API /////////////////////////////////////////////////////////// - /** - * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. - * Throws an error if the font could not be parsed. - * @param {ArrayBuffer} - * @param {Object} opt - options for parsing - * @return {opentype.Font} - */ - function parseBuffer(buffer, opt) { - opt = (opt === undefined || opt === null) ? {} : opt; - - var indexToLocFormat; - var ltagTable; - - // Since the constructor can also be called to create new fonts from scratch, we indicate this - // should be an empty font that we'll fill with our own data. - var font = new Font({empty: true}); - - // OpenType fonts use big endian byte ordering. - // We can't rely on typed array view types, because they operate with the endianness of the host computer. - // Instead we use DataViews where we can specify endianness. - var data = new DataView(buffer, 0); - var numTables; - var tableEntries = []; - var signature = parse.getTag(data, 0); - if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { +/** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ +function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { + font.outlinesFormat = 'truetype'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { + font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { font.outlinesFormat = 'truetype'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'OTTO') { + } else if (flavor === 'OTTO') { font.outlinesFormat = 'cff'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'wOFF') { - var flavor = parse.getTag(data, 4); - if (flavor === String.fromCharCode(0, 1, 0, 0)) { - font.outlinesFormat = 'truetype'; - } else if (flavor === 'OTTO') { - font.outlinesFormat = 'cff'; - } else { - throw new Error('Unsupported OpenType flavor ' + signature); - } - - numTables = parse.getUShort(data, 12); - tableEntries = parseWOFFTableEntries(data, numTables); } else { - throw new Error('Unsupported OpenType signature ' + signature); - } - - var cffTableEntry; - var fvarTableEntry; - var glyfTableEntry; - var gdefTableEntry; - var gposTableEntry; - var gsubTableEntry; - var hmtxTableEntry; - var kernTableEntry; - var locaTableEntry; - var nameTableEntry; - var metaTableEntry; - var p; - - for (var i = 0; i < numTables; i += 1) { - var tableEntry = tableEntries[i]; - var table = (void 0); - switch (tableEntry.tag) { - case 'cmap': - table = uncompressTable(data, tableEntry); - font.tables.cmap = cmap.parse(table.data, table.offset); - font.encoding = new CmapEncoding(font.tables.cmap); - break; - case 'cvt ' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.cvt = p.parseShortList(tableEntry.length / 2); - break; - case 'fvar': - fvarTableEntry = tableEntry; - break; - case 'fpgm' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.fpgm = p.parseByteList(tableEntry.length); - break; - case 'head': - table = uncompressTable(data, tableEntry); - font.tables.head = head.parse(table.data, table.offset); - font.unitsPerEm = font.tables.head.unitsPerEm; - indexToLocFormat = font.tables.head.indexToLocFormat; - break; - case 'hhea': - table = uncompressTable(data, tableEntry); - font.tables.hhea = hhea.parse(table.data, table.offset); - font.ascender = font.tables.hhea.ascender; - font.descender = font.tables.hhea.descender; - font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; - break; - case 'hmtx': - hmtxTableEntry = tableEntry; - break; - case 'ltag': - table = uncompressTable(data, tableEntry); - ltagTable = ltag.parse(table.data, table.offset); - break; - case 'COLR': - table = uncompressTable(data, tableEntry); - font.tables.colr = colr.parse(table.data, table.offset); - break; - case 'CPAL': - table = uncompressTable(data, tableEntry); - font.tables.cpal = cpal.parse(table.data, table.offset); - break; - case 'maxp': - table = uncompressTable(data, tableEntry); - font.tables.maxp = maxp.parse(table.data, table.offset); - font.numGlyphs = font.tables.maxp.numGlyphs; - break; - case 'name': - nameTableEntry = tableEntry; - break; - case 'OS/2': - table = uncompressTable(data, tableEntry); - font.tables.os2 = os2.parse(table.data, table.offset); - break; - case 'post': - table = uncompressTable(data, tableEntry); - font.tables.post = post.parse(table.data, table.offset); - font.glyphNames = new GlyphNames(font.tables.post); - break; - case 'prep' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.prep = p.parseByteList(tableEntry.length); - break; - case 'glyf': - glyfTableEntry = tableEntry; - break; - case 'loca': - locaTableEntry = tableEntry; - break; - case 'CFF ': - cffTableEntry = tableEntry; - break; - case 'kern': - kernTableEntry = tableEntry; - break; - case 'GDEF': - gdefTableEntry = tableEntry; - break; - case 'GPOS': - gposTableEntry = tableEntry; - break; - case 'GSUB': - gsubTableEntry = tableEntry; - break; - case 'meta': - metaTableEntry = tableEntry; - break; - } + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gdefTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'COLR': + table = uncompressTable(data, tableEntry); + font.tables.colr = colr.parse(table.data, table.offset); + break; + case 'CPAL': + table = uncompressTable(data, tableEntry); + font.tables.cpal = cpal.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GDEF': + gdefTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; } + } - var nameTable = uncompressTable(data, nameTableEntry); - font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); - font.names = font.tables.name; - - if (glyfTableEntry && locaTableEntry) { - var shortVersion = indexToLocFormat === 0; - var locaTable = uncompressTable(data, locaTableEntry); - var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); - var glyfTable = uncompressTable(data, glyfTableEntry); - font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); - } else if (cffTableEntry) { - var cffTable = uncompressTable(data, cffTableEntry); - cff.parse(cffTable.data, cffTable.offset, font, opt); - } else { - throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); - } + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; - var hmtxTable = uncompressTable(data, hmtxTableEntry); - hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); - addGlyphNames(font, opt); + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } - if (kernTableEntry) { - var kernTable = uncompressTable(data, kernTableEntry); - font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); - } else { - font.kerningPairs = {}; - } + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); - if (gdefTableEntry) { - var gdefTable = uncompressTable(data, gdefTableEntry); - font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); - } + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } - if (gposTableEntry) { - var gposTable = uncompressTable(data, gposTableEntry); - font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); - font.position.init(); - } + if (gdefTableEntry) { + var gdefTable = uncompressTable(data, gdefTableEntry); + font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); + } - if (gsubTableEntry) { - var gsubTable = uncompressTable(data, gsubTableEntry); - font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); - } + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } - if (fvarTableEntry) { - var fvarTable = uncompressTable(data, fvarTableEntry); - font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); - } + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } - if (metaTableEntry) { - var metaTable = uncompressTable(data, metaTableEntry); - font.tables.meta = meta.parse(metaTable.data, metaTable.offset); - font.metas = font.tables.meta; - } + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } - return font; + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; } - /** - * Asynchronously load the font from a URL or a filesystem. When done, call the callback - * with two arguments `(err, font)`. The `err` will be null on success, - * the `font` is a Font object. - * We use the node.js callback convention so that - * opentype.js can integrate with frameworks like async.js. - * @alias opentype.load - * @param {string} url - The URL of the font to load. - * @param {Function} callback - The callback. - */ - function load(url, callback, opt) { - opt = (opt === undefined || opt === null) ? {} : opt; - var isNode = typeof window === 'undefined'; - var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; - - return new Promise(function (resolve, reject) { - loadFn(url, function(err, arrayBuffer) { - if (err) { - if (callback) { - return callback(err); - } else { - reject(err); - } - } - var font; - try { - font = parseBuffer(arrayBuffer, opt); - } catch (e) { - if (callback) { - return callback(e, null); - } else { - reject(e); - } + return font; +} + +/** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ +function load(url, callback, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + var isNode = typeof window === 'undefined'; + var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { if (callback) { - return callback(null, font); + return callback(e, null); } else { - resolve(font); + reject(e); } - }); + } + if (callback) { + return callback(null, font); + } else { + resolve(font); + } }); - } - - /** - * Synchronously load the font from a URL or file. - * When done, returns the font object or throws an error. - * @alias opentype.loadSync - * @param {string} url - The URL of the font to load. - * @param {Object} opt - opt.lowMemory - * @return {opentype.Font} - */ - function loadSync(url, opt) { - var fs = require('fs'); - var buffer = fs.readFileSync(url); - return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); - } - - return { BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer }; + }); +} -} )(); +/** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ +function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); +} -const opentype = { +var opentype = /*#__PURE__*/Object.freeze({ __proto__: null, Font: Font, Glyph: Glyph, @@ -14568,7 +14564,11 @@ const opentype = { parse: parseBuffer, load: load, loadSync: loadSync -}; +}); + +return { opentype, BoundingBox, Font, Glyph, Path, parse, load, loadSync, parseBuffer }; + +} )(); export default opentype; export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse }; diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index 924160f68ce190..66cf4f057e27a3 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -1,4 +1,4 @@ -const UTIF = {}; +var UTIF = {}; /* @__PURE__ */ ( () => { From 6df1cd4fcf34cae5b317bc29ebd3ed5e7e84dc86 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 07:46:52 -0500 Subject: [PATCH 31/45] Cleanup --- examples/jsm/controls/OrbitControls.js | 2 +- examples/jsm/exporters/DRACOExporter.js | 46 +- examples/jsm/exporters/GLTFExporter.js | 232 +++-- examples/jsm/loaders/KTX2Loader.js | 524 +++++------ examples/jsm/loaders/LogLuvLoader.js | 698 ++++++++------- examples/jsm/loaders/RGBMLoader.js | 1068 +++++++++++------------ examples/jsm/loaders/lwo/IFFParser.js | 8 +- 7 files changed, 1289 insertions(+), 1289 deletions(-) diff --git a/examples/jsm/controls/OrbitControls.js b/examples/jsm/controls/OrbitControls.js index 6839795d0c0fa1..0e987b6d4661ef 100644 --- a/examples/jsm/controls/OrbitControls.js +++ b/examples/jsm/controls/OrbitControls.js @@ -8,7 +8,7 @@ import { Vector3, Plane, Ray, - MathUtils, + MathUtils } from 'three'; // OrbitControls performs orbiting, dollying (zooming), and panning. diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 9de867ac1b76eb..147f58e19fbb53 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -212,29 +212,6 @@ class DRACOExporter { } -/* @__PURE__ */ Object.assign( DRACOExporter, { - - // Encoder methods - - MESH_EDGEBREAKER_ENCODING: 1, - MESH_SEQUENTIAL_ENCODING: 0, - - // Geometry type - - POINT_CLOUD: 0, - TRIANGULAR_MESH: 1, - - // Attribute type - - INVALID: - 1, - POSITION: 0, - NORMAL: 1, - COLOR: 2, - TEX_COORD: 3, - GENERIC: 4 - -} ); - function createVertexColorSRGBArray( attribute ) { // While .drc files do not specify colorspace, the only 'official' tooling @@ -268,4 +245,27 @@ function createVertexColorSRGBArray( attribute ) { } +/* @__PURE__ */ Object.assign( DRACOExporter, { + + // Encoder methods + + MESH_EDGEBREAKER_ENCODING: 1, + MESH_SEQUENTIAL_ENCODING: 0, + + // Geometry type + + POINT_CLOUD: 0, + TRIANGULAR_MESH: 1, + + // Attribute type + + INVALID: - 1, + POSITION: 0, + NORMAL: 1, + COLOR: 2, + TEX_COORD: 3, + GENERIC: 4 + +} ); + export { DRACOExporter }; diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 3493156e9de76d..4c02c2bd95e15c 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -3032,204 +3032,202 @@ class GLTFMeshGpuInstancing { } -/* @__PURE__ */ Object.assign( GLTFExporter, { - - /** - * Static utility functions - */ - Utils: { - - insertKeyframe: function ( track, time ) { +/** + * Static utility functions + */ +const Utils = { - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); + insertKeyframe: function ( track, time ) { - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); - let index; + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - if ( track.times.length === 0 ) { + let index; - times[ 0 ] = time; + if ( track.times.length === 0 ) { - for ( let i = 0; i < valueSize; i ++ ) { + times[ 0 ] = time; - values[ i ] = 0; + for ( let i = 0; i < valueSize; i ++ ) { - } + values[ i ] = 0; - index = 0; + } - } else if ( time < track.times[ 0 ] ) { + index = 0; - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + } else if ( time < track.times[ 0 ] ) { - times[ 0 ] = time; - times.set( track.times, 1 ); + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); + times[ 0 ] = time; + times.set( track.times, 1 ); - index = 0; + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); - } else if ( time > track.times[ track.times.length - 1 ] ) { + index = 0; - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + } else if ( time > track.times[ track.times.length - 1 ] ) { - return track.times.length - 1; + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - } + return track.times.length - 1; - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); + } - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); - index = times.length - 1; + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); - } else { + index = times.length - 1; - for ( let i = 0; i < track.times.length; i ++ ) { + } else { - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + for ( let i = 0; i < track.times.length; i ++ ) { - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); - index = i + 1; + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - break; + index = i + 1; - } + break; } } - track.times = times; - track.values = values; - - return index; - - }, + } - mergeMorphTargetTracks: function ( clip, root ) { + track.times = times; + track.values = values; - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; + return index; - for ( let i = 0; i < sourceTracks.length; ++ i ) { + }, - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + mergeMorphTargetTracks: function ( clip, root ) { - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + for ( let i = 0; i < sourceTracks.length; ++ i ) { - } + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + } - } + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); } - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - - if ( targetIndex === undefined ) { + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); - } + } - let mergedTrack; + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + if ( targetIndex === undefined ) { - mergedTrack = sourceTrack.clone(); + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + } - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + let mergedTrack; - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - } + mergedTrack = sourceTrack.clone(); - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - continue; + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; } - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + continue; - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + } - } + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - } + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); } - clip.tracks = tracks; + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - return clip; + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + + } } + clip.tracks = tracks; + + return clip; + } -} ); +}; + +/* @__PURE__ */ Object.assign( GLTFExporter, { Utils } ); export { GLTFExporter }; diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index 6a2c88939ace86..9fb6f7ec9089ff 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -338,378 +338,384 @@ class KTX2Loader extends Loader { } -/* @__PURE__ */ Object.assign( KTX2Loader, { - - /* CONSTANTS */ - BasisFormat: { - ETC1S: 0, - UASTC_4x4: 1, - }, +/* CONSTANTS */ - TranscoderFormat: { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, - }, - - EngineFormat: { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, - }, +const BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, +}; +const TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, +}; - /* WEB WORKER */ +const EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, +}; - BasisWorker: function () { - let config; - let transcoderPending; - let BasisModule; +/* WEB WORKER */ - const EngineFormat = _EngineFormat; // eslint-disable-line no-undef - const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef - const BasisFormat = _BasisFormat; // eslint-disable-line no-undef +const BasisWorker = function () { - self.addEventListener( 'message', function ( e ) { + let config; + let transcoderPending; + let BasisModule; - const message = e.data; + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef - switch ( message.type ) { + self.addEventListener( 'message', function ( e ) { - case 'init': - config = message.config; - init( message.transcoderBinary ); - break; + const message = e.data; - case 'transcode': - transcoderPending.then( () => { + switch ( message.type ) { - try { + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; - const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); + case 'transcode': + transcoderPending.then( () => { - self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); + try { - } catch ( error ) { + const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); - console.error( error ); + self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + } catch ( error ) { - } + console.error( error ); - } ); - break; + self.postMessage( { type: 'error', id: message.id, error: error.message } ); - } + } - } ); + } ); + break; - function init( wasmBinary ) { + } - transcoderPending = new Promise( ( resolve ) => { + } ); - BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; - BASIS( BasisModule ); // eslint-disable-line no-undef + function init( wasmBinary ) { - } ).then( () => { + transcoderPending = new Promise( ( resolve ) => { - BasisModule.initializeBasis(); + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef - if ( BasisModule.KTX2File === undefined ) { + } ).then( () => { - console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); + BasisModule.initializeBasis(); - } + if ( BasisModule.KTX2File === undefined ) { - } ); + console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); - } + } - function transcode( buffer ) { + } ); - const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + } - function cleanup() { + function transcode( buffer ) { - ktx2File.close(); - ktx2File.delete(); + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); - } + function cleanup() { - if ( ! ktx2File.isValid() ) { + ktx2File.close(); + ktx2File.delete(); - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); + } - } + if ( ! ktx2File.isValid() ) { - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; - const width = ktx2File.getWidth(); - const height = ktx2File.getHeight(); - const layerCount = ktx2File.getLayers() || 1; - const levelCount = ktx2File.getLevels(); - const faceCount = ktx2File.getFaces(); - const hasAlpha = ktx2File.getHasAlpha(); - const dfdFlags = ktx2File.getDFDFlags(); + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); - const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + } - if ( ! width || ! height || ! levelCount ) { + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const layerCount = ktx2File.getLayers() || 1; + const levelCount = ktx2File.getLevels(); + const faceCount = ktx2File.getFaces(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdFlags = ktx2File.getDFDFlags(); - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid texture' ); + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); - } + if ( ! width || ! height || ! levelCount ) { - if ( ! ktx2File.startTranscoding() ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid texture' ); - cleanup(); - throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); + } - } + if ( ! ktx2File.startTranscoding() ) { - const faces = []; - const buffers = []; + cleanup(); + throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); - for ( let face = 0; face < faceCount; face ++ ) { + } - const mipmaps = []; + const faces = []; + const buffers = []; - for ( let mip = 0; mip < levelCount; mip ++ ) { + for ( let face = 0; face < faceCount; face ++ ) { - const layerMips = []; + const mipmaps = []; - let mipWidth, mipHeight; + for ( let mip = 0; mip < levelCount; mip ++ ) { - for ( let layer = 0; layer < layerCount; layer ++ ) { + const layerMips = []; - const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); + let mipWidth, mipHeight; - if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + for ( let layer = 0; layer < layerCount; layer ++ ) { - console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); - } + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { - if ( levelCount > 1 ) { + console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); - mipWidth = levelInfo.origWidth; - mipHeight = levelInfo.origHeight; + } - } else { + if ( levelCount > 1 ) { - // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with - // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. - // See mrdoob/three.js#25908. - mipWidth = levelInfo.width; - mipHeight = levelInfo.height; + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; - } + } else { - const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); - const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; - if ( ! status ) { + } - cleanup(); - throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); - } + if ( ! status ) { - layerMips.push( dst ); + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); } - const mipData = concat( layerMips ); - - mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); - buffers.push( mipData.buffer ); + layerMips.push( dst ); } - faces.push( { mipmaps, width, height, format: engineFormat } ); + const mipData = concat( layerMips ); - } + mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); + buffers.push( mipData.buffer ); - cleanup(); + } - return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; + faces.push( { mipmaps, width, height, format: engineFormat } ); } - // - - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [ BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], - engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], - engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], - engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], - engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1 ], - engineFormat: [ EngineFormat.RGB_ETC1_Format ], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], - engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ]; - - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityETC1S - b.priorityETC1S; + cleanup(); - } ); - const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; - return a.priorityUASTC - b.priorityUASTC; + } - } ); + // - function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityETC1S - b.priorityETC1S; + + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityUASTC - b.priorityUASTC; + + } ); + + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + + let transcoderFormat; + let engineFormat; + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + + for ( let i = 0; i < options.length; i ++ ) { + + const opt = options[ i ]; + + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; - let transcoderFormat; - let engineFormat; + return { transcoderFormat, engineFormat }; - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + } - for ( let i = 0; i < options.length; i ++ ) { + console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); - const opt = options[ i ]; + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; - if ( ! config[ opt.if ] ) continue; - if ( ! opt.basisFormat.includes( basisFormat ) ) continue; - if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; - if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + return { transcoderFormat, engineFormat }; - transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; - engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + } - return { transcoderFormat, engineFormat }; + function isPowerOfTwo( value ) { - } + if ( value <= 2 ) return true; - console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); + return ( value & ( value - 1 ) ) === 0 && value !== 0; - transcoderFormat = TranscoderFormat.RGBA32; - engineFormat = EngineFormat.RGBAFormat; + } - return { transcoderFormat, engineFormat }; + /** Concatenates N byte arrays. */ + function concat( arrays ) { - } + if ( arrays.length === 1 ) return arrays[ 0 ]; - function isPowerOfTwo( value ) { + let totalByteLength = 0; - if ( value <= 2 ) return true; + for ( let i = 0; i < arrays.length; i ++ ) { - return ( value & ( value - 1 ) ) === 0 && value !== 0; + const array = arrays[ i ]; + totalByteLength += array.byteLength; } - /** Concatenates N byte arrays. */ - function concat( arrays ) { - - if ( arrays.length === 1 ) return arrays[ 0 ]; + const result = new Uint8Array( totalByteLength ); - let totalByteLength = 0; - - for ( let i = 0; i < arrays.length; i ++ ) { - - const array = arrays[ i ]; - totalByteLength += array.byteLength; - - } + let byteOffset = 0; - const result = new Uint8Array( totalByteLength ); + for ( let i = 0; i < arrays.length; i ++ ) { - let byteOffset = 0; + const array = arrays[ i ]; + result.set( array, byteOffset ); - for ( let i = 0; i < arrays.length; i ++ ) { + byteOffset += array.byteLength; - const array = arrays[ i ]; - result.set( array, byteOffset ); + } - byteOffset += array.byteLength; + return result; - } + } - return result; +}; - } +/* @__PURE__ */ Object.assign( KTX2Loader, { - } + BasisFormat, + TranscoderFormat, + EngineFormat, + BasisWorker, } ); diff --git a/examples/jsm/loaders/LogLuvLoader.js b/examples/jsm/loaders/LogLuvLoader.js index ddf6d3d737669f..60b6ae400e75e7 100644 --- a/examples/jsm/loaders/LogLuvLoader.js +++ b/examples/jsm/loaders/LogLuvLoader.js @@ -44,568 +44,566 @@ class LogLuvLoader extends DataTextureLoader { // from https://github.com/photopea/UTIF.js (MIT License) -const UTIF = /* @__PURE__ */ ( () => { +const UTIF = {}; - const UTIF = {}; +/* @__PURE__ */ ( () => { - UTIF.decode = function ( buff, prm ) { +UTIF.decode = function ( buff, prm ) { - if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug - var data = new Uint8Array( buff ), offset = 0; + if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug + var data = new Uint8Array( buff ), offset = 0; - var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; - var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; - bin.readUshort( data, offset ); offset += 2; + var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; + var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; + bin.readUshort( data, offset ); offset += 2; - var ifdo = bin.readUint( data, offset ); - var ifds = []; - while ( true ) { + var ifdo = bin.readUint( data, offset ); + var ifds = []; + while ( true ) { - var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { + var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { - console.log( 'error in TIFF' ); break; + console.log( 'error in TIFF' ); break; - } - - - UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); + } - ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); - if ( ifdo == 0 ) break; - } + UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); - return ifds; + ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); + if ( ifdo == 0 ) break; - }; + } - UTIF.decodeImage = function ( buff, img, ifds ) { + return ifds; - if ( img.data ) return; - var data = new Uint8Array( buff ); - var id = UTIF._binBE.readASCII( data, 0, 2 ); +}; - if ( img[ 't256' ] == null ) return; // No width => probably not an image - img.isLE = id == 'II'; - img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; - img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; +UTIF.decodeImage = function ( buff, img, ifds ) { - var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; - var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; - if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); - if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); + if ( img.data ) return; + var data = new Uint8Array( buff ); + var id = UTIF._binBE.readASCII( data, 0, 2 ); - var bipp; // bits per pixel - if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; - else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { + if ( img[ 't256' ] == null ) return; // No width => probably not an image + img.isLE = id == 'II'; + img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; + img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; - bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); + var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; + var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; + if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); + if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); - } + var bipp; // bits per pixel + if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; + else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { - var bipl = Math.ceil( img.width * bipp / 8 ) * 8; - var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; - var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; + bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); - if ( img[ 't322' ] != null ) { + } - var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; - var tx = Math.floor( ( img.width + tw - 1 ) / tw ); - var ty = Math.floor( ( img.height + th - 1 ) / th ); - var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); - for ( var y = 0; y < ty; y ++ ) - for ( var x = 0; x < tx; x ++ ) { + var bipl = Math.ceil( img.width * bipp / 8 ) * 8; + var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; + var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; - var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); - // Might be required for 7 too. Need to check - if ( cmpr == 6 ) bytes = tbuff; - else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); + if ( img[ 't322' ] != null ) { - } + var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; + var tx = Math.floor( ( img.width + tw - 1 ) / tw ); + var ty = Math.floor( ( img.height + th - 1 ) / th ); + var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); + for ( var y = 0; y < ty; y ++ ) + for ( var x = 0; x < tx; x ++ ) { - bilen = bytes.length * 8; + var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); + // Might be required for 7 too. Need to check + if ( cmpr == 6 ) bytes = tbuff; + else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); - } else { + } - var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); - for ( var i = 0; i < soff.length; i ++ ) { + bilen = bytes.length * 8; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); - bilen += bipl * rps; + } else { - } + var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); + for ( var i = 0; i < soff.length; i ++ ) { - bilen = Math.min( bilen, bytes.length * 8 ); + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); + bilen += bipl * rps; } - img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); + bilen = Math.min( bilen, bytes.length * 8 ); - }; + } - UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { + img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); - //console.log("compression", cmpr); - //var time = Date.now(); - if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); - else console.log( 'Unsupported compression', cmpr ); +}; - //console.log(Date.now()-time); +UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { - var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); - var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); + //console.log("compression", cmpr); + //var time = Date.now(); + if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); + else console.log( 'Unsupported compression', cmpr ); - // convert to Little Endian /* - if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG - for ( var y = 0; y < h; y ++ ) { + //console.log(Date.now()-time); - //console.log("fixing endianity"); - var roff = toff + y * bpl; - for ( var x = 1; x < bpl; x += 2 ) { + var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); + var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); - var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; + // convert to Little Endian /* + if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG + for ( var y = 0; y < h; y ++ ) { - } + //console.log("fixing endianity"); + var roff = toff + y * bpl; + for ( var x = 1; x < bpl; x += 2 ) { - } //*/ + var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; - if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { + } - for ( var y = 0; y < h; y ++ ) { + } //*/ - var ntoff = toff + y * bpl; - if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { + if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { - var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); - tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; + for ( var y = 0; y < h; y ++ ) { - } - else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + var ntoff = toff + y * bpl; + if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { - tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; - tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; - tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); + tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; - } - else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; + } + else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + + tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; + tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; + tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; } + else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; } - }; + } - UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { +}; - var w = img.width, qw = w * 4; - var io = 0, out = new Uint8Array( qw ); +UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { - while ( io < len ) { + var w = img.width, qw = w * 4; + var io = 0, out = new Uint8Array( qw ); - var oo = 0; - while ( oo < qw ) { + while ( io < len ) { - var c = data[ off + io ]; io ++; - if ( c < 128 ) { + var oo = 0; + while ( oo < qw ) { - for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; + var c = data[ off + io ]; io ++; + if ( c < 128 ) { - } else { + for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; - c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; + } else { - } + c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; } - for ( var x = 0; x < w; x ++ ) { + } - tgt[ toff + 0 ] = out[ x ]; - tgt[ toff + 1 ] = out[ x + w ]; - tgt[ toff + 2 ] = out[ x + w * 2 ]; - tgt[ toff + 4 ] = out[ x + w * 3 ]; - toff += 6; + for ( var x = 0; x < w; x ++ ) { - } + tgt[ toff + 0 ] = out[ x ]; + tgt[ toff + 1 ] = out[ x + w ]; + tgt[ toff + 2 ] = out[ x + w * 2 ]; + tgt[ toff + 4 ] = out[ x + w * 3 ]; + toff += 6; } - }; + } - UTIF.tags = {}; - //UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; - // start at tag 250 - UTIF._types = function () { +}; - var main = new Array( 250 ); main.fill( 0 ); - main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); - var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; - return { - basic: { - main: main, - rest: rest - }, - gps: { - main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], - rest: { 18: 2, 29: 2 } - } - }; +UTIF.tags = {}; +//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; +// start at tag 250 +UTIF._types = function () { - }(); + var main = new Array( 250 ); main.fill( 0 ); + main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); + var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; + return { + basic: { + main: main, + rest: rest + }, + gps: { + main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], + rest: { 18: 2, 29: 2 } + } + }; - UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { +}(); - var cnt = bin.readUshort( data, offset ); offset += 2; - var ifd = {}; +UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { - if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); - for ( var i = 0; i < cnt; i ++ ) { + var cnt = bin.readUshort( data, offset ); offset += 2; + var ifd = {}; - var tag = bin.readUshort( data, offset ); offset += 2; - var type = bin.readUshort( data, offset ); offset += 2; - var num = bin.readUint( data, offset ); offset += 4; - var voff = bin.readUint( data, offset ); offset += 4; + if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); + for ( var i = 0; i < cnt; i ++ ) { - var arr = []; - //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; - if ( type == 1 || type == 7 ) { + var tag = bin.readUshort( data, offset ); offset += 2; + var type = bin.readUshort( data, offset ); offset += 2; + var num = bin.readUint( data, offset ); offset += 4; + var voff = bin.readUint( data, offset ); offset += 4; - arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if ( type == 1 || type == 7 ) { - } + arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); - if ( type == 2 ) { + } - var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); - if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); - else arr = new Uint8Array( data.buffer, o0, len ); + if ( type == 2 ) { - } + var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); + if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); + else arr = new Uint8Array( data.buffer, o0, len ); - if ( type == 3 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + if ( type == 3 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - if ( type == 4 - || type == 13 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + if ( type == 4 + || type == 13 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - if ( type == 5 || type == 10 ) { + } - var ri = type == 5 ? bin.readUint : bin.readInt; - for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); + if ( type == 5 || type == 10 ) { - } + var ri = type == 5 ? bin.readUint : bin.readInt; + for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); - if ( type == 8 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + if ( type == 8 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - if ( type == 9 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + if ( type == 9 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - if ( type == 11 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); + if ( type == 11 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); - if ( type == 12 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); + if ( type == 12 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); - if ( num != 0 && arr.length == 0 ) { + } - console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; + if ( num != 0 && arr.length == 0 ) { - } + console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; - if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); + } - ifd[ 't' + tag ] = arr; + if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); - if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { + ifd[ 't' + tag ] = arr; - var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; - var subfd = []; - for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); - if ( tag == 330 ) ifd.subIFD = subfd; - if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; - if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } - if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; - if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; + if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { - } + var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; + var subfd = []; + for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); + if ( tag == 330 ) ifd.subIFD = subfd; + if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; + if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } + if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; + if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; - if ( tag == 37500 && prm.parseMN ) { + } - var mn = arr; - //console.log(bin.readASCII(mn,0,mn.length), mn); - if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; - else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { + if ( tag == 37500 && prm.parseMN ) { - var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); - ifd.makerNote = subsub[ 0 ]; + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; + else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { - } + var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); + ifd.makerNote = subsub[ 0 ]; } } - ifds.push( ifd ); - if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); - return offset; + } - }; + ifds.push( ifd ); + if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); + return offset; - UTIF.toRGBA = function ( out, type ) { +}; - const w = out.width, h = out.height, area = w * h, data = out.data; +UTIF.toRGBA = function ( out, type ) { - let img; + const w = out.width, h = out.height, area = w * h, data = out.data; - switch ( type ) { + let img; - case HalfFloatType: + switch ( type ) { - img = new Uint16Array( area * 4 ); - break; + case HalfFloatType: - case FloatType: + img = new Uint16Array( area * 4 ); + break; - img = new Float32Array( area * 4 ); - break; + case FloatType: - default: - throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); + img = new Float32Array( area * 4 ); + break; - } + default: + throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); - let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; - const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; + } - if ( out[ 't262' ] == null && bps == 1 ) intp = 0; + let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; + const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; - if ( intp == 32845 ) { + if ( out[ 't262' ] == null && bps == 1 ) intp = 0; - for ( let y = 0; y < h; y ++ ) { + if ( intp == 32845 ) { - for ( let x = 0; x < w; x ++ ) { + for ( let y = 0; y < h; y ++ ) { - const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; - let L = ( data[ si + 1 ] << 8 ) | data[ si ]; + for ( let x = 0; x < w; x ++ ) { - L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); - const u = ( data[ si + 3 ] + 0.5 ) / 410; - const v = ( data[ si + 5 ] + 0.5 ) / 410; + const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; + let L = ( data[ si + 1 ] << 8 ) | data[ si ]; - // Luv to xyY - const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); - const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); - const bY = L; + L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); + const u = ( data[ si + 3 ] + 0.5 ) / 410; + const v = ( data[ si + 5 ] + 0.5 ) / 410; - // xyY to XYZ - const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; + // Luv to xyY + const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); + const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); + const bY = L; - // XYZ to linear RGB - const r = 2.690 * X - 1.276 * Y - 0.414 * Z; - const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; - const b = 0.061 * X - 0.224 * Y + 1.163 * Z; + // xyY to XYZ + const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; - if ( type === HalfFloatType ) { + // XYZ to linear RGB + const r = 2.690 * X - 1.276 * Y - 0.414 * Z; + const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; + const b = 0.061 * X - 0.224 * Y + 1.163 * Z; - img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); - img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); - img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); - img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); + if ( type === HalfFloatType ) { + img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); + img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); + img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); + img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); - } else { - img[ qi ] = r; - img[ qi + 1 ] = g; - img[ qi + 2 ] = b; - img[ qi + 3 ] = 1; + } else { - } + img[ qi ] = r; + img[ qi + 1 ] = g; + img[ qi + 2 ] = b; + img[ qi + 3 ] = 1; } } - } else { + } - throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); + } else { - } + throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); - return img; + } - }; + return img; - UTIF._binBE = - { - nextZero: function ( data, o ) { +}; - while ( data[ o ] != 0 ) o ++; return o; +UTIF._binBE = +{ + nextZero: function ( data, o ) { - }, - readUshort: function ( buff, p ) { + while ( data[ o ] != 0 ) o ++; return o; - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + }, + readUshort: function ( buff, p ) { - }, - readShort: function ( buff, p ) { + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; + }, + readShort: function ( buff, p ) { - }, - readInt: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; + }, + readInt: function ( buff, p ) { - }, - readUint: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; + }, + readUint: function ( buff, p ) { - }, - readASCII: function ( buff, p, l ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + }, + readASCII: function ( buff, p, l ) { - }, - readFloat: function ( buff, p ) { + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; + }, + readFloat: function ( buff, p ) { - }, - readDouble: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; + }, + readDouble: function ( buff, p ) { - }, + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; - writeUshort: function ( buff, p, n ) { + }, - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + writeUshort: function ( buff, p, n ) { - }, - writeInt: function ( buff, p, n ) { + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; + }, + writeInt: function ( buff, p, n ) { - }, - writeUint: function ( buff, p, n ) { + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; + }, + writeUint: function ( buff, p, n ) { - }, - writeASCII: function ( buff, p, s ) { + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; - for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); + }, + writeASCII: function ( buff, p, s ) { - }, - writeDouble: function ( buff, p, n ) { + for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); - UTIF._binBE.fl64[ 0 ] = n; - for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; + }, + writeDouble: function ( buff, p, n ) { - } - }; - UTIF._binBE.ui8 = new Uint8Array( 8 ); - UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.fl64[ 0 ] = n; + for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; - UTIF._binLE = - { - nextZero: UTIF._binBE.nextZero, - readUshort: function ( buff, p ) { + } +}; +UTIF._binBE.ui8 = new Uint8Array( 8 ); +UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); - return ( buff[ p + 1 ] << 8 ) | buff[ p ]; +UTIF._binLE = +{ + nextZero: UTIF._binBE.nextZero, + readUshort: function ( buff, p ) { - }, - readShort: function ( buff, p ) { + return ( buff[ p + 1 ] << 8 ) | buff[ p ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; + }, + readShort: function ( buff, p ) { - }, - readInt: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; + }, + readInt: function ( buff, p ) { - }, - readUint: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; + }, + readUint: function ( buff, p ) { - }, - readASCII: UTIF._binBE.readASCII, - readFloat: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; + }, + readASCII: UTIF._binBE.readASCII, + readFloat: function ( buff, p ) { - }, - readDouble: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; + }, + readDouble: function ( buff, p ) { - }, + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; - writeUshort: function ( buff, p, n ) { + }, - buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; + writeUshort: function ( buff, p, n ) { - }, - writeInt: function ( buff, p, n ) { + buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; + }, + writeInt: function ( buff, p, n ) { - }, - writeUint: function ( buff, p, n ) { + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; - buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; + }, + writeUint: function ( buff, p, n ) { - }, - writeASCII: UTIF._binBE.writeASCII - }; - UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { + buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min( tw, w - xoff ); - var ylim = Math.min( th, h - yoff ); - for ( var y = 0; y < ylim; y ++ ) { + }, + writeASCII: UTIF._binBE.writeASCII +}; +UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { - var tof = ( yoff + y ) * w + xoff; - var sof = y * tw; - for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min( tw, w - xoff ); + var ylim = Math.min( th, h - yoff ); + for ( var y = 0; y < ylim; y ++ ) { - } + var tof = ( yoff + y ) * w + xoff; + var sof = y * tw; + for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; - }; + } - return UTIF; +}; } )(); diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index 771c285199eb1c..43496730509f99 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -127,944 +127,942 @@ class RGBMLoader extends DataTextureLoader { // from https://github.com/photopea/UPNG.js (MIT License) -const UPNG = /* @__PURE__ */ ( () => { +var UPNG = {}; - const UPNG = {}; +/* @__PURE__ */ ( () => { - UPNG.toRGBA8 = function ( out ) { +UPNG.toRGBA8 = function ( out ) { - var w = out.width, h = out.height; - if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; + var w = out.width, h = out.height; + if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; - var frms = []; - if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; + var frms = []; + if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; - var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); - for ( var i = 0; i < out.frames.length; i ++ ) { + var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); + for ( var i = 0; i < out.frames.length; i ++ ) { - var frm = out.frames[ i ]; - var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; - var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); + var frm = out.frames[ i ]; + var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; + var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); - if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; + if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; - if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); + if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); - frms.push( img.buffer.slice( 0 ) ); + frms.push( img.buffer.slice( 0 ) ); - if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; + if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; - } - - return frms; + } - }; + return frms; - UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { +}; - var area = w * h, bpp = UPNG.decode._getBPP( out ); - var bpl = Math.ceil( w * bpp / 8 ); // bytes per line +UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { - var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); - var ctype = out.ctype, depth = out.depth; - var rs = UPNG._bin.readUshort; + var area = w * h, bpp = UPNG.decode._getBPP( out ); + var bpl = Math.ceil( w * bpp / 8 ); // bytes per line - if ( ctype == 6 ) { // RGB + alpha + var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); + var ctype = out.ctype, depth = out.depth; + var rs = UPNG._bin.readUshort; - var qarea = area << 2; - if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { + if ( ctype == 6 ) { // RGB + alpha - bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; + var qarea = area << 2; + if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { - } + bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; - if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { + } - bf[ i ] = data[ i << 1 ]; + if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { - } + bf[ i ] = data[ i << 1 ]; - } else if ( ctype == 2 ) { // RGB + } - var ts = out.tabs[ 'tRNS' ]; - if ( ts == null ) { + } else if ( ctype == 2 ) { // RGB - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + var ts = out.tabs[ 'tRNS' ]; + if ( ts == null ) { - var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + } - var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - } + var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - } else { + } - var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + } else { - var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; + var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + } - var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; } - } else if ( ctype == 3 ) { // palette + } - var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; - //console.log(p, ap); - if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { + } else if ( ctype == 3 ) { // palette - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; + //console.log(p, ap); + if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - } + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; } - if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { + } - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - } + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; } - if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { + } - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - } + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; } - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + } - var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - } else if ( ctype == 4 ) { // gray + alpha + } - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + } else if ( ctype == 4 ) { // gray + alpha - var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + } - var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; - } else if ( ctype == 0 ) { // gray + } - var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; - for ( var y = 0; y < h; y ++ ) { + } else if ( ctype == 0 ) { // gray - var off = y * bpl, to = y * w; - if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { + var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; + for ( var y = 0; y < h; y ++ ) { - var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + var off = y * bpl, to = y * w; + if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { + var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { + var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { + var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { + var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { - } + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; } } - //console.log(Date.now()-time); - return bf; - - }; + } + //console.log(Date.now()-time); + return bf; +}; - UPNG.decode = function ( buff ) { - var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; - var out = { tabs: {}, frames: [] }; - var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it - var fd, foff = 0; // frames - var text, keyw, bfr; - var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; - for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); +UPNG.decode = function ( buff ) { - while ( offset < data.length ) { + var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; + var out = { tabs: {}, frames: [] }; + var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it + var fd, foff = 0; // frames + var text, keyw, bfr; - var len = bin.readUint( data, offset ); offset += 4; - var type = bin.readASCII( data, offset, 4 ); offset += 4; - //console.log(type,len); + var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; + for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); - if ( type == 'IHDR' ) { + while ( offset < data.length ) { - UPNG.decode._IHDR( data, offset, out ); + var len = bin.readUint( data, offset ); offset += 4; + var type = bin.readASCII( data, offset, 4 ); offset += 4; + //console.log(type,len); - } else if ( type == 'CgBI' ) { + if ( type == 'IHDR' ) { - out.tabs[ type ] = data.slice( offset, offset + 4 ); + UPNG.decode._IHDR( data, offset, out ); - } else if ( type == 'IDAT' ) { + } else if ( type == 'CgBI' ) { - for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; - doff += len; + out.tabs[ type ] = data.slice( offset, offset + 4 ); - } else if ( type == 'acTL' ) { + } else if ( type == 'IDAT' ) { - out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; - fd = new Uint8Array( data.length ); + for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; + doff += len; - } else if ( type == 'fcTL' ) { + } else if ( type == 'acTL' ) { - if ( foff != 0 ) { + out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; + fd = new Uint8Array( data.length ); - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + } else if ( type == 'fcTL' ) { - } + if ( foff != 0 ) { - var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; - var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); - var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; - //console.log(frm); - out.frames.push( frm ); + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; - } else if ( type == 'fdAT' ) { + } - for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; - foff += len - 4; + var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; + var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); + var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; + //console.log(frm); + out.frames.push( frm ); - } else if ( type == 'pHYs' ) { + } else if ( type == 'fdAT' ) { - out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; + for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; + foff += len - 4; - } else if ( type == 'cHRM' ) { + } else if ( type == 'pHYs' ) { - out.tabs[ type ] = []; - for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); + out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; - } else if ( type == 'tEXt' || type == 'zTXt' ) { + } else if ( type == 'cHRM' ) { - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = bin.nextZero( data, offset ); - keyw = bin.readASCII( data, offset, nz - offset ); - var tl = offset + len - nz - 1; - if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); - else { + out.tabs[ type ] = []; + for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); - bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); + } else if ( type == 'tEXt' || type == 'zTXt' ) { - } + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = bin.nextZero( data, offset ); + keyw = bin.readASCII( data, offset, nz - offset ); + var tl = offset + len - nz - 1; + if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); + else { - out.tabs[ type ][ keyw ] = text; + bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); - } else if ( type == 'iTXt' ) { + } - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = 0, off = offset; - nz = bin.nextZero( data, off ); - keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; - var cflag = data[ off ]; off += 2; - nz = bin.nextZero( data, off ); - bin.readASCII( data, off, nz - off ); off = nz + 1; - nz = bin.nextZero( data, off ); - bin.readUTF8( data, off, nz - off ); off = nz + 1; - var tl = len - ( off - offset ); - if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); - else { + out.tabs[ type ][ keyw ] = text; - bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); + } else if ( type == 'iTXt' ) { - } + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = 0, off = offset; + nz = bin.nextZero( data, off ); + keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; + var cflag = data[ off ]; off += 2; + nz = bin.nextZero( data, off ); + bin.readASCII( data, off, nz - off ); off = nz + 1; + nz = bin.nextZero( data, off ); + bin.readUTF8( data, off, nz - off ); off = nz + 1; + var tl = len - ( off - offset ); + if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); + else { - out.tabs[ type ][ keyw ] = text; + bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); - } else if ( type == 'PLTE' ) { + } - out.tabs[ type ] = bin.readBytes( data, offset, len ); + out.tabs[ type ][ keyw ] = text; - } else if ( type == 'hIST' ) { + } else if ( type == 'PLTE' ) { - var pl = out.tabs[ 'PLTE' ].length / 3; - out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); + out.tabs[ type ] = bin.readBytes( data, offset, len ); - } else if ( type == 'tRNS' ) { + } else if ( type == 'hIST' ) { - if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); - else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); - else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - //else console.log("tRNS for unsupported color type",out.ctype, len); + var pl = out.tabs[ 'PLTE' ].length / 3; + out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); - } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; - else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; - else if ( type == 'bKGD' ) { + } else if ( type == 'tRNS' ) { - if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; - else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; + if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); + else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); + else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + //else console.log("tRNS for unsupported color type",out.ctype, len); - } else if ( type == 'IEND' ) { + } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; + else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; + else if ( type == 'bKGD' ) { - break; + if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; + else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; - } + } else if ( type == 'IEND' ) { - //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } - offset += len; - bin.readUint( data, offset ); offset += 4; + break; } - if ( foff != 0 ) { + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len; + bin.readUint( data, offset ); offset += 4; - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); + } - } + if ( foff != 0 ) { - out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); - delete out.compress; delete out.interlace; delete out.filter; - return out; + } - }; + out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); - UPNG.decode._decompress = function ( out, dd, w, h ) { + delete out.compress; delete out.interlace; delete out.filter; + return out; - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); - if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); - else dd = UPNG.decode._inflate( dd, buff ); +}; - if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); - else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); +UPNG.decode._decompress = function ( out, dd, w, h ) { - return dd; + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); + if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); + else dd = UPNG.decode._inflate( dd, buff ); - }; + if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); + else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); - UPNG.decode._inflate = function ( data, buff ) { + return dd; - var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; +}; - }; +UPNG.decode._inflate = function ( data, buff ) { - UPNG.inflateRaw = function () { + var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; - var H = {}; H.H = {}; H.H.N = function ( N, W ) { +}; - var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; - if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; - if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { +UPNG.inflateRaw = function () { - i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { + var H = {}; H.H = {}; H.H.N = function ( N, W ) { - if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); - var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; - w += q; continue - ; + var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; + if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; + if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { - } + i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { - if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { + if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); + var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; + w += q; continue + ; - v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; + } - } + if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { - if ( m == 2 ) { + v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; - J = A( N, d, 5 ) + 257; - h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { + } - b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; + if ( m == 2 ) { - } + J = A( N, d, 5 ) + 257; + h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { - for ( var c = 0; - c < Q; c ++ ) { + b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; - var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K - ; + } - } + for ( var c = 0; + c < Q; c ++ ) { - d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; - d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); - I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K ; } - while ( ! 0 ) { + d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; + d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); + I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + ; - var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { + } - W[ w ++ ] = p; + while ( ! 0 ) { - } else if ( p == 256 ) { + var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { - break; + W[ w ++ ] = p; - } else { + } else if ( p == 256 ) { - var z = w + p - 254; - if ( p > 264 ) { + break; - var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; + } else { - } + var z = w + p - 254; + if ( p > 264 ) { - var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); - d += Y & 15; while ( w < z ) { + var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; - W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; + } - } + var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); + d += Y & 15; while ( w < z ) { - w = z - ; + W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; } + w = z + ; + } } - return W.length == w ? W : W.slice( 0, w ) - ; + } - }; + return W.length == w ? W : W.slice( 0, w ) + ; - H.H.W = function ( N, W ) { + }; - var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; + H.H.W = function ( N, W ) { - }; + var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; - H.H.R = function ( N, W, R, V, n, A ) { + }; - var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { + H.H.R = function ( N, W, R, V, n, A ) { - var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; - if ( b <= 15 ) { + var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { - A[ I ] = b; I ++; + var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; + if ( b <= 15 ) { - } else { + A[ I ] = b; I ++; - var Z = 0, m = 0; if ( b == 16 ) { + } else { - m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; + var Z = 0, m = 0; if ( b == 16 ) { - } else if ( b == 17 ) { + m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; - m = 3 + l( V, n, 3 ); - n += 3 - ; + } else if ( b == 17 ) { - } else if ( b == 18 ) { + m = 3 + l( V, n, 3 ); + n += 3 + ; - m = 11 + l( V, n, 7 ); n += 7; + } else if ( b == 18 ) { - } + m = 11 + l( V, n, 7 ); n += 7; - var J = I + m; while ( I < J ) { + } - A[ I ] = Z; I ++; + var J = I + m; while ( I < J ) { - } + A[ I ] = Z; I ++; } } - return n - ; + } - }; + return n + ; - H.H.V = function ( N, W, R, V ) { + }; - var n = 0, A = 0, l = V.length >>> 1; - while ( A < R ) { + H.H.V = function ( N, W, R, V ) { - var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; + var n = 0, A = 0, l = V.length >>> 1; + while ( A < R ) { - } + var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; - while ( A < l ) { + } - V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + while ( A < l ) { - } + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; - return n - ; + } - }; + return n + ; - H.H.n = function ( N, W ) { + }; - var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; - var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { + H.H.n = function ( N, W ) { - n = n + e[ A - 1 ] << 1; b[ A ] = n; + var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; + var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { - } + n = n + e[ A - 1 ] << 1; b[ A ] = n; - for ( l = 0; l < V; l += 2 ) { + } - I = N[ l + 1 ]; if ( I != 0 ) { + for ( l = 0; l < V; l += 2 ) { - N[ l ] = b[ I ]; - b[ I ] ++ - ; + I = N[ l + 1 ]; if ( I != 0 ) { - } + N[ l ] = b[ I ]; + b[ I ] ++ + ; } - }; + } - H.H.A = function ( N, W, R ) { + }; - var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { + H.H.A = function ( N, W, R ) { - var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); - while ( Z != m ) { + var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { - var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; + var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); + while ( Z != m ) { - } + var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; } - }; + } - H.H.l = function ( N, W ) { + }; - var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; - n += 2 ) { + H.H.l = function ( N, W ) { - var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; + var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; + n += 2 ) { - } + var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; - }; + } - H.H.M = function ( N, W, R ) { + }; - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + H.H.M = function ( N, W, R ) { - }; + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; - H.H.I = function ( N, W, R ) { + }; - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; + H.H.I = function ( N, W, R ) { - }; + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; - H.H.e = function ( N, W, R ) { + }; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + H.H.e = function ( N, W, R ) { - }; + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - H.H.b = function ( N, W, R ) { + }; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + H.H.b = function ( N, W, R ) { - }; + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - H.H.Z = function ( N, W ) { + }; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + H.H.Z = function ( N, W ) { - }; + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); - H.H.i = function ( N, W ) { + }; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); + H.H.i = function ( N, W ) { - }; + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); - H.H.m = function () { + }; - var N = Uint16Array, W = Uint32Array; - return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } - ; + H.H.m = function () { - }(); - ( function () { + var N = Uint16Array, W = Uint32Array; + return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } + ; - var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { + }(); + ( function () { - var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; - V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; - N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 - ; + var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { - } + var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; + V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; + N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 + ; - function n( A, l, M ) { + } - while ( l -- != 0 )A.push( 0, M ) - ; + function n( A, l, M ) { - } - - for ( var R = 0; R < 32; R ++ ) { + while ( l -- != 0 )A.push( 0, M ) + ; - N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; - N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] - ; + } - } + for ( var R = 0; R < 32; R ++ ) { - n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); - H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); - n( N.D, 30, 0 ); n( N.v, 320, 0 ) + N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; + N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] ; - }() ); + } - return H.H.N + n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); + H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); + n( N.D, 30, 0 ); n( N.v, 320, 0 ) ; - }(); + }() ); + return H.H.N + ; - UPNG.decode._readInterlace = function ( data, out ) { +}(); - var w = out.width, h = out.height; - var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); - var img = new Uint8Array( h * bpl ); - var di = 0; - var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; - var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; - var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; - var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; +UPNG.decode._readInterlace = function ( data, out ) { - var pass = 0; - while ( pass < 7 ) { + var w = out.width, h = out.height; + var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); + var img = new Uint8Array( h * bpl ); + var di = 0; - var ri = row_increment[ pass ], ci = col_increment[ pass ]; - var sw = 0, sh = 0; - var cr = starting_row[ pass ]; while ( cr < h ) { + var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; + var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; + var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; + var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; - cr += ri; sh ++; + var pass = 0; + while ( pass < 7 ) { - } + var ri = row_increment[ pass ], ci = col_increment[ pass ]; + var sw = 0, sh = 0; + var cr = starting_row[ pass ]; while ( cr < h ) { - var cc = starting_col[ pass ]; while ( cc < w ) { + cr += ri; sh ++; - cc += ci; sw ++; + } - } + var cc = starting_col[ pass ]; while ( cc < w ) { - var bpll = Math.ceil( sw * bpp / 8 ); - UPNG.decode._filterZero( data, out, di, sw, sh ); + cc += ci; sw ++; - var y = 0, row = starting_row[ pass ]; - var val; + } - while ( row < h ) { + var bpll = Math.ceil( sw * bpp / 8 ); + UPNG.decode._filterZero( data, out, di, sw, sh ); - var col = starting_col[ pass ]; - var cdi = ( di + y * bpll ) << 3; + var y = 0, row = starting_row[ pass ]; + var val; - while ( col < w ) { + while ( row < h ) { - if ( bpp == 1 ) { + var col = starting_col[ pass ]; + var cdi = ( di + y * bpll ) << 3; - val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; - img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); + while ( col < w ) { - } + if ( bpp == 1 ) { - if ( bpp == 2 ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; + img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); - val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; - img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); + } - } + if ( bpp == 2 ) { - if ( bpp == 4 ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; + img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); - val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; - img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); + } - } + if ( bpp == 4 ) { - if ( bpp >= 8 ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; + img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); - var ii = row * bpl + col * cbpp; - for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; + } - } + if ( bpp >= 8 ) { - cdi += bpp; col += ci; + var ii = row * bpl + col * cbpp; + for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; } - y ++; row += ri; + cdi += bpp; col += ci; } - if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); - pass = pass + 1; + y ++; row += ri; } - return img; + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; - }; + } - UPNG.decode._getBPP = function ( out ) { + return img; - var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; - return noc * out.depth; +}; - }; +UPNG.decode._getBPP = function ( out ) { - UPNG.decode._filterZero = function ( data, out, off, w, h ) { + var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; + return noc * out.depth; - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; - bpp = Math.ceil( bpp / 8 ); +}; - var i, di, type = data[ off ], x = 0; +UPNG.decode._filterZero = function ( data, out, off, w, h ) { - if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; - if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; + bpp = Math.ceil( bpp / 8 ); - for ( var y = 0; y < h; y ++ ) { + var i, di, type = data[ off ], x = 0; + + if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; + if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; - i = off + y * bpl; di = i + y + 1; - type = data[ di - 1 ]; x = 0; + for ( var y = 0; y < h; y ++ ) { - if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; - else if ( type == 1 ) { + i = off + y * bpl; di = i + y + 1; + type = data[ di - 1 ]; x = 0; - for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; + if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; + else if ( type == 1 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); - } else if ( type == 2 ) { + } else if ( type == 2 ) { - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); - } else if ( type == 3 ) { + } else if ( type == 3 ) { - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); - } else { + } else { - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); - } - } - return data; - - }; + } - UPNG.decode._paeth = function ( a, b, c ) { + return data; - var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); - if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; - else if ( pb * pb <= pc * pc ) return b; - return c; +}; - }; +UPNG.decode._paeth = function ( a, b, c ) { - UPNG.decode._IHDR = function ( data, offset, out ) { + var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); + if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; + else if ( pb * pb <= pc * pc ) return b; + return c; - var bin = UPNG._bin; - out.width = bin.readUint( data, offset ); offset += 4; - out.height = bin.readUint( data, offset ); offset += 4; - out.depth = data[ offset ]; offset ++; - out.ctype = data[ offset ]; offset ++; - out.compress = data[ offset ]; offset ++; - out.filter = data[ offset ]; offset ++; - out.interlace = data[ offset ]; offset ++; +}; - }; +UPNG.decode._IHDR = function ( data, offset, out ) { - UPNG._bin = { - nextZero: function ( data, p ) { + var bin = UPNG._bin; + out.width = bin.readUint( data, offset ); offset += 4; + out.height = bin.readUint( data, offset ); offset += 4; + out.depth = data[ offset ]; offset ++; + out.ctype = data[ offset ]; offset ++; + out.compress = data[ offset ]; offset ++; + out.filter = data[ offset ]; offset ++; + out.interlace = data[ offset ]; offset ++; - while ( data[ p ] != 0 ) p ++; return p; +}; - }, - readUshort: function ( buff, p ) { +UPNG._bin = { + nextZero: function ( data, p ) { - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + while ( data[ p ] != 0 ) p ++; return p; - }, - writeUshort: function ( buff, p, n ) { + }, + readUshort: function ( buff, p ) { - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - }, - readUint: function ( buff, p ) { + }, + writeUshort: function ( buff, p, n ) { - return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - }, - writeUint: function ( buff, p, n ) { + }, + readUint: function ( buff, p ) { - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; + return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); - }, - readASCII: function ( buff, p, l ) { + }, + writeUint: function ( buff, p, n ) { - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; - }, - writeASCII: function ( data, p, s ) { + }, + readASCII: function ( buff, p, l ) { - for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - }, - readBytes: function ( buff, p, l ) { + }, + writeASCII: function ( data, p, s ) { - var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; + for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); - }, - pad: function ( n ) { + }, + readBytes: function ( buff, p, l ) { - return n.length < 2 ? '0' + n : n; + var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; - }, - readUTF8: function ( buff, p, l ) { + }, + pad: function ( n ) { - var s = '', ns; - for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); - try { + return n.length < 2 ? '0' + n : n; - ns = decodeURIComponent( s ); + }, + readUTF8: function ( buff, p, l ) { - } catch ( e ) { + var s = '', ns; + for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); + try { - return UPNG._bin.readASCII( buff, p, l ); + ns = decodeURIComponent( s ); - } + } catch ( e ) { - return ns; + return UPNG._bin.readASCII( buff, p, l ); } - }; - UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - - var w = Math.min( sw, tw ), h = Math.min( sh, th ); - var si = 0, ti = 0; - for ( var y = 0; y < h; y ++ ) - for ( var x = 0; x < w; x ++ ) { - if ( xoff >= 0 && yoff >= 0 ) { + return ns; - si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; + } +}; +UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - } else { + var w = Math.min( sw, tw ), h = Math.min( sh, th ); + var si = 0, ti = 0; + for ( var y = 0; y < h; y ++ ) + for ( var x = 0; x < w; x ++ ) { - si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; + if ( xoff >= 0 && yoff >= 0 ) { - } + si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; - if ( mode == 0 ) { + } else { - tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; + si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; - } else if ( mode == 1 ) { + } - var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; - var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; + if ( mode == 0 ) { - var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); - tb[ ti + 3 ] = 255 * oa; - tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; - tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; - tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; + tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; - } else if ( mode == 2 ) { // copy only differences, otherwise zero + } else if ( mode == 1 ) { - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) { + var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; + var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; - tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; + var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); + tb[ ti + 3 ] = 255 * oa; + tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; + tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; + tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; - } else { + } else if ( mode == 2 ) { // copy only differences, otherwise zero - tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) { - } + tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; - } else if ( mode == 3 ) { // check if can be blended + } else { - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; - //if(fa!=255 && ba!=0) return false; - if ( fa < 220 && ba > 20 ) return false; + tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; } + } else if ( mode == 3 ) { // check if can be blended + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; + //if(fa!=255 && ba!=0) return false; + if ( fa < 220 && ba > 20 ) return false; + } - return true; + } - }; + return true; - return UPNG; +}; } )(); diff --git a/examples/jsm/loaders/lwo/IFFParser.js b/examples/jsm/loaders/lwo/IFFParser.js index ec141c14828701..6852d62a4dc22d 100644 --- a/examples/jsm/loaders/lwo/IFFParser.js +++ b/examples/jsm/loaders/lwo/IFFParser.js @@ -345,7 +345,7 @@ class IFFParser { var name = this.reader.getString(); var surface = { - attributes: {}, + attributes: {}, // LWO2 style non-node attributes will go here connections: {}, name: name, inputName: name, @@ -367,7 +367,7 @@ class IFFParser { var name = this.reader.getString(); var surface = { - attributes: {}, + attributes: {}, // LWO2 style non-node attributes will go here connections: {}, name: name, nodes: {}, @@ -644,8 +644,8 @@ class IFFParser { var layer = { number: this.reader.getUint16(), - flags: this.reader.getUint16(), - pivot: this.reader.getFloat32Array( 3 ), + flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden. + pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present name: this.reader.getString(), }; From d9322d64ddb572a32ec5f8fad8470036cceb9d2f Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 08:02:41 -0500 Subject: [PATCH 32/45] Cleanup static fields --- examples/jsm/exporters/DRACOExporter.js | 39 ++- examples/jsm/libs/fflate.module.js | 6 +- examples/jsm/libs/mmdparser.module.js | 12 +- examples/jsm/objects/Lensflare.js | 144 +++++---- examples/jsm/objects/Reflector.js | 88 +++--- examples/jsm/objects/ReflectorForSSRPass.js | 194 ++++++------ examples/jsm/objects/Refractor.js | 76 +++-- examples/jsm/objects/Sky.js | 278 +++++++++--------- examples/jsm/objects/Water2.js | 216 +++++++------- examples/jsm/postprocessing/BloomPass.js | 12 +- examples/jsm/postprocessing/OutlinePass.js | 8 +- examples/jsm/postprocessing/SAOPass.js | 14 +- examples/jsm/postprocessing/SSAOPass.js | 20 +- examples/jsm/postprocessing/SSRPass.js | 22 +- .../jsm/postprocessing/UnrealBloomPass.js | 8 +- 15 files changed, 565 insertions(+), 572 deletions(-) diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 147f58e19fbb53..48a622da757645 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -245,26 +245,37 @@ function createVertexColorSRGBArray( attribute ) { } -/* @__PURE__ */ Object.assign( DRACOExporter, { +// Encoder methods + +const MESH_EDGEBREAKER_ENCODING = 1; +const MESH_SEQUENTIAL_ENCODING = 0; - // Encoder methods +// Geometry type - MESH_EDGEBREAKER_ENCODING: 1, - MESH_SEQUENTIAL_ENCODING: 0, +const POINT_CLOUD = 0; +const TRIANGULAR_MESH = 1; - // Geometry type +// Attribute type - POINT_CLOUD: 0, - TRIANGULAR_MESH: 1, +const INVALID = - 1; +const POSITION = 0; +const NORMAL = 1; +const COLOR = 2; +const TEX_COORD = 3; +const GENERIC = 4; - // Attribute type +/* @__PURE__ */ Object.assign( DRACOExporter, { - INVALID: - 1, - POSITION: 0, - NORMAL: 1, - COLOR: 2, - TEX_COORD: 3, - GENERIC: 4 + MESH_EDGEBREAKER_ENCODING, + MESH_SEQUENTIAL_ENCODING, + POINT_CLOUD, + TRIANGULAR_MESH, + INVALID, + POSITION, + NORMAL, + COLOR, + TEX_COORD, + GENERIC, } ); diff --git a/examples/jsm/libs/fflate.module.js b/examples/jsm/libs/fflate.module.js index 54b752c47b0c05..acee3976ca08e3 100644 --- a/examples/jsm/libs/fflate.module.js +++ b/examples/jsm/libs/fflate.module.js @@ -1477,9 +1477,9 @@ var Decompress = /*#__PURE__*/ (function () { */ var AsyncDecompress = /*#__PURE__*/ (function () { /** - * Creates an asynchronous decompression stream - * @param cb The callback to call whenever data is decompressed - */ + * Creates an asynchronous decompression stream + * @param cb The callback to call whenever data is decompressed + */ function AsyncDecompress(cb) { this.G = AsyncGunzip; this.I = AsyncInflate; diff --git a/examples/jsm/libs/mmdparser.module.js b/examples/jsm/libs/mmdparser.module.js index 637d65e79b39b2..20314272a9b8d3 100644 --- a/examples/jsm/libs/mmdparser.module.js +++ b/examples/jsm/libs/mmdparser.module.js @@ -4,7 +4,7 @@ * Simple CharsetEncoder. */ -const { CharsetEncoder, Parser } = /* @__PURE__ */ ( () => { +const { MMDParser, CharsetEncoder, Parser } = /* @__PURE__ */ ( () => { function CharsetEncoder() { } @@ -11524,13 +11524,13 @@ Parser.prototype.leftToRightVpd = function ( vpd ) { }; -return { CharsetEncoder, Parser }; - -} )(); - -const MMDParser = { +var MMDParser = { CharsetEncoder, Parser }; +return { MMDParser, CharsetEncoder, Parser }; + +} )(); + export { MMDParser, CharsetEncoder, Parser }; diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index 3b2c8b4fa2b86a..216f62786abbbf 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -283,31 +283,6 @@ class Lensflare extends Mesh { } -/* @__PURE__ */ Object.assign( Lensflare, { - - Geometry: /* @__PURE__ */ ( function () { - - const geometry = new BufferGeometry(); - - const float32Array = new Float32Array( [ - - 1, - 1, 0, 0, 0, - 1, - 1, 0, 1, 0, - 1, 1, 0, 1, 1, - - 1, 1, 0, 0, 1 - ] ); - - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - - geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - - return geometry; - - } )() - -} ); - // class LensflareElement { @@ -323,80 +298,101 @@ class LensflareElement { } -/* @__PURE__ */ Object.assign( LensflareElement, { +const Shader = { - Shader: { + uniforms: { - uniforms: { + 'map': { value: null }, + 'occlusionMap': { value: null }, + 'color': { value: null }, + 'scale': { value: null }, + 'screenPosition': { value: null } - 'map': { value: null }, - 'occlusionMap': { value: null }, - 'color': { value: null }, - 'scale': { value: null }, - 'screenPosition': { value: null } + }, - }, + vertexShader: /* glsl */` - vertexShader: /* glsl */` + precision highp float; - precision highp float; + uniform vec3 screenPosition; + uniform vec2 scale; - uniform vec3 screenPosition; - uniform vec2 scale; + uniform sampler2D occlusionMap; - uniform sampler2D occlusionMap; + attribute vec3 position; + attribute vec2 uv; - attribute vec3 position; - attribute vec2 uv; + varying vec2 vUV; + varying float vVisibility; - varying vec2 vUV; - varying float vVisibility; + void main() { - void main() { + vUV = uv; - vUV = uv; + vec2 pos = position.xy; - vec2 pos = position.xy; + vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); - vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); + vVisibility = visibility.r / 9.0; + vVisibility *= 1.0 - visibility.g / 9.0; + vVisibility *= visibility.b / 9.0; - vVisibility = visibility.r / 9.0; - vVisibility *= 1.0 - visibility.g / 9.0; - vVisibility *= visibility.b / 9.0; + gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); - gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); + }`, - }`, + fragmentShader: /* glsl */` - fragmentShader: /* glsl */` + precision highp float; - precision highp float; + uniform sampler2D map; + uniform vec3 color; - uniform sampler2D map; - uniform vec3 color; + varying vec2 vUV; + varying float vVisibility; - varying vec2 vUV; - varying float vVisibility; + void main() { - void main() { + vec4 texture = texture2D( map, vUV ); + texture.a *= vVisibility; + gl_FragColor = texture; + gl_FragColor.rgb *= color; - vec4 texture = texture2D( map, vUV ); - texture.a *= vVisibility; - gl_FragColor = texture; - gl_FragColor.rgb *= color; + }` - }` +}; - } +/* @__PURE__ */ Object.assign( LensflareElement, { Shader } ); + +const Geometry = /* @__PURE__ */ ( function () { + + const geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + return geometry; + +} )(); -} ); +/* @__PURE__ */ Object.assign( Lensflare, { Geometry } ); export { Lensflare, LensflareElement }; diff --git a/examples/jsm/objects/Reflector.js b/examples/jsm/objects/Reflector.js index b95e18cd081def..101303f5a829e0 100644 --- a/examples/jsm/objects/Reflector.js +++ b/examples/jsm/objects/Reflector.js @@ -192,77 +192,75 @@ class Reflector extends Mesh { } -/* @__PURE__ */ Object.assign( Reflector, { +const ReflectorShader = { - ReflectorShader: { + name: 'ReflectorShader', - name: 'ReflectorShader', + uniforms: { - uniforms: { - - 'color': { - value: null - }, + 'color': { + value: null + }, - 'tDiffuse': { - value: null - }, + 'tDiffuse': { + value: null + }, - 'textureMatrix': { - value: null - } + 'textureMatrix': { + value: null + } - }, + }, - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; - #include - #include + #include + #include - void main() { + void main() { - vUv = textureMatrix * vec4( position, 1.0 ); + vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - #include + #include - }`, + }`, - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - varying vec4 vUv; + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + varying vec4 vUv; - #include + #include - float blendOverlay( float base, float blend ) { + float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } + } - vec3 blendOverlay( vec3 base, vec3 blend ) { + vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } + } - void main() { + void main() { - #include + #include - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #include - #include + #include + #include - }` - } + }` +}; -} ); +/* @__PURE__ */ Object.assign( Reflector, { ReflectorShader } ); export { Reflector }; diff --git a/examples/jsm/objects/ReflectorForSSRPass.js b/examples/jsm/objects/ReflectorForSSRPass.js index 52d77eb9ab7d31..2d6dda256714f2 100644 --- a/examples/jsm/objects/ReflectorForSSRPass.js +++ b/examples/jsm/objects/ReflectorForSSRPass.js @@ -249,105 +249,103 @@ class ReflectorForSSRPass extends Mesh { } -/* @__PURE__ */ Object.assign( ReflectorForSSRPass, { - - ReflectorShader: { - - defines: { - DISTANCE_ATTENUATION: true, - FRESNEL: true, - }, - - uniforms: { - - color: { value: null }, - tDiffuse: { value: null }, - tDepth: { value: null }, - textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, - maxDistance: { value: 180 }, - opacity: { value: 0.5 }, - fresnelCoe: { value: null }, - virtualCameraNear: { value: null }, - virtualCameraFar: { value: null }, - virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, - resolution: { value: /* @__PURE__ */ new Vector2() }, - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - uniform sampler2D tDepth; - uniform float maxDistance; - uniform float opacity; - uniform float fresnelCoe; - uniform float virtualCameraNear; - uniform float virtualCameraFar; - uniform mat4 virtualCameraProjectionMatrix; - uniform mat4 virtualCameraProjectionMatrixInverse; - uniform mat4 virtualCameraMatrixWorld; - uniform vec2 resolution; - varying vec4 vUv; - #include - float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } - vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } - float getDepth( const in vec2 uv ) { - return texture2D( tDepth, uv ).x; - } - float getViewZ( const in float depth ) { - return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); - } - vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { - vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc - clipPosition *= clipW; //clip - return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view - } - void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - #ifdef useDepthTexture - vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; - uv.x=1.-uv.x; - float depth = texture2DProj( tDepth, vUv ).r; - float viewZ = getViewZ( depth ); - float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; - vec3 viewPosition=getViewPosition( uv, depth, clipW ); - vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; - if(worldPosition.y>maxDistance) discard; - float op=opacity; - #ifdef DISTANCE_ATTENUATION - float ratio=1.-(worldPosition.y/maxDistance); - float attenuation=ratio*ratio; - op=opacity*attenuation; - #endif - #ifdef FRESNEL - op*=fresnelCoe; - #endif - gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); - #else - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); +const ReflectorShader = { + + defines: { + DISTANCE_ATTENUATION: true, + FRESNEL: true, + }, + + uniforms: { + + color: { value: null }, + tDiffuse: { value: null }, + tDepth: { value: null }, + textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, + maxDistance: { value: 180 }, + opacity: { value: 0.5 }, + fresnelCoe: { value: null }, + virtualCameraNear: { value: null }, + virtualCameraFar: { value: null }, + virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + uniform sampler2D tDepth; + uniform float maxDistance; + uniform float opacity; + uniform float fresnelCoe; + uniform float virtualCameraNear; + uniform float virtualCameraFar; + uniform mat4 virtualCameraProjectionMatrix; + uniform mat4 virtualCameraProjectionMatrixInverse; + uniform mat4 virtualCameraMatrixWorld; + uniform vec2 resolution; + varying vec4 vUv; + #include + float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } + vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } + float getDepth( const in vec2 uv ) { + return texture2D( tDepth, uv ).x; + } + float getViewZ( const in float depth ) { + return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); + } + vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { + vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc + clipPosition *= clipW; //clip + return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view + } + void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + #ifdef useDepthTexture + vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; + uv.x=1.-uv.x; + float depth = texture2DProj( tDepth, vUv ).r; + float viewZ = getViewZ( depth ); + float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; + vec3 viewPosition=getViewPosition( uv, depth, clipW ); + vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; + if(worldPosition.y>maxDistance) discard; + float op=opacity; + #ifdef DISTANCE_ATTENUATION + float ratio=1.-(worldPosition.y/maxDistance); + float attenuation=ratio*ratio; + op=opacity*attenuation; #endif - } - `, - } + #ifdef FRESNEL + op*=fresnelCoe; + #endif + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); + #else + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #endif + } + `, +}; -} ); +/* @__PURE__ */ Object.assign( ReflectorForSSRPass, { ReflectorShader } ); export { ReflectorForSSRPass }; diff --git a/examples/jsm/objects/Refractor.js b/examples/jsm/objects/Refractor.js index b096538062f3da..ec2938e2d07ef5 100644 --- a/examples/jsm/objects/Refractor.js +++ b/examples/jsm/objects/Refractor.js @@ -259,70 +259,68 @@ class Refractor extends Mesh { } -/* @__PURE__ */ Object.assign( Refractor, { +const RefractorShader = { - RefractorShader: { + uniforms: { - uniforms: { - - 'color': { - value: null - }, + 'color': { + value: null + }, - 'tDiffuse': { - value: null - }, + 'tDiffuse': { + value: null + }, - 'textureMatrix': { - value: null - } + 'textureMatrix': { + value: null + } - }, + }, - vertexShader: /* glsl */` + vertexShader: /* glsl */` - uniform mat4 textureMatrix; + uniform mat4 textureMatrix; - varying vec4 vUv; + varying vec4 vUv; - void main() { + void main() { - vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, + }`, - fragmentShader: /* glsl */` + fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; + uniform vec3 color; + uniform sampler2D tDiffuse; - varying vec4 vUv; + varying vec4 vUv; - float blendOverlay( float base, float blend ) { + float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } + } - vec3 blendOverlay( vec3 base, vec3 blend ) { + vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } + } - void main() { + void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #include - #include + #include + #include - }` + }` - } +}; -} ); +/* @__PURE__ */ Object.assign( Refractor, { RefractorShader } ); export { Refractor }; diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index 98291980f90154..d441ec827d81af 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -44,178 +44,176 @@ class Sky extends Mesh { } -/* @__PURE__ */ Object.assign( Sky, { +const SkyShader = { + + uniforms: { + 'turbidity': { value: 2 }, + 'rayleigh': { value: 1 }, + 'mieCoefficient': { value: 0.005 }, + 'mieDirectionalG': { value: 0.8 }, + 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, + 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } + }, + + vertexShader: /* glsl */` + uniform vec3 sunPosition; + uniform float rayleigh; + uniform float turbidity; + uniform float mieCoefficient; + uniform vec3 up; + + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; + + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; + + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); + + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); + + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; + + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } + + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } + + void main() { + + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - SkyShader: { - - uniforms: { - 'turbidity': { value: 2 }, - 'rayleigh': { value: 1 }, - 'mieCoefficient': { value: 0.005 }, - 'mieDirectionalG': { value: 0.8 }, - 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, - 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } - }, - - vertexShader: /* glsl */` - uniform vec3 sunPosition; - uniform float rayleigh; - uniform float turbidity; - uniform float mieCoefficient; - uniform vec3 up; - - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; + vSunDirection = normalize( sunPosition ); - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); - } + vSunE = sunIntensity( dot( vSunDirection, up ) ); - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } - - void main() { + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - vSunDirection = normalize( sunPosition ); + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - vSunE = sunIntensity( dot( vSunDirection, up ) ); + }`, - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); + uniform float mieDirectionalG; + uniform vec3 up; - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - }`, + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; - uniform float mieDirectionalG; - uniform vec3 up; + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); + } - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) + void main() { - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; + vec3 direction = normalize( vWorldPosition - cameraPosition ); - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } + // in scattering + float cosTheta = dot( direction, vSunDirection ); - void main() { + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - vec3 direction = normalize( vWorldPosition - cameraPosition ); + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - // in scattering - float cosTheta = dot( direction, vSunDirection ); + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); + gl_FragColor = vec4( retColor, 1.0 ); - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; + #include + #include - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; + }` - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); +}; - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - - gl_FragColor = vec4( retColor, 1.0 ); - - #include - #include - - }` - - } - -} ); +/* @__PURE__ */ Object.assign( Sky, { SkyShader } ); export { Sky }; diff --git a/examples/jsm/objects/Water2.js b/examples/jsm/objects/Water2.js index b0ae6ff17885ed..de0fd4351ec977 100644 --- a/examples/jsm/objects/Water2.js +++ b/examples/jsm/objects/Water2.js @@ -203,152 +203,158 @@ class Water extends Mesh { } -/* @__PURE__ */ Object.assign( Water, { +const WaterShader = { - WaterShader: { + uniforms: { - uniforms: { - - color: { - value: null - }, + 'color': { + type: 'c', + value: null + }, - reflectivity: { - value: 0 - }, + 'reflectivity': { + type: 'f', + value: 0 + }, - tReflectionMap: { - value: null - }, + 'tReflectionMap': { + type: 't', + value: null + }, - tRefractionMap: { - value: null - }, + 'tRefractionMap': { + type: 't', + value: null + }, - tNormalMap0: { - value: null - }, + 'tNormalMap0': { + type: 't', + value: null + }, - tNormalMap1: { - value: null - }, + 'tNormalMap1': { + type: 't', + value: null + }, - textureMatrix: { - value: null - }, + 'textureMatrix': { + type: 'm4', + value: null + }, - config: { - value: /* @__PURE__ */ new Vector4() - } + 'config': { + type: 'v4', + value: /* @__PURE__ */ new Vector4() + } - }, + }, - vertexShader: /* glsl */` + vertexShader: /* glsl */` - #include - #include - #include + #include + #include + #include - uniform mat4 textureMatrix; + uniform mat4 textureMatrix; - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; - void main() { + void main() { - vUv = uv; - vCoord = textureMatrix * vec4( position, 1.0 ); + vUv = uv; + vCoord = textureMatrix * vec4( position, 1.0 ); - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vToEye = cameraPosition - worldPosition.xyz; + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vToEye = cameraPosition - worldPosition.xyz; - vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex - gl_Position = projectionMatrix * mvPosition; + vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex + gl_Position = projectionMatrix * mvPosition; - #include - #include + #include + #include - }`, + }`, - fragmentShader: /* glsl */` + fragmentShader: /* glsl */` - #include - #include - #include + #include + #include + #include - uniform sampler2D tReflectionMap; - uniform sampler2D tRefractionMap; - uniform sampler2D tNormalMap0; - uniform sampler2D tNormalMap1; + uniform sampler2D tReflectionMap; + uniform sampler2D tRefractionMap; + uniform sampler2D tNormalMap0; + uniform sampler2D tNormalMap1; - #ifdef USE_FLOWMAP - uniform sampler2D tFlowMap; - #else - uniform vec2 flowDirection; - #endif + #ifdef USE_FLOWMAP + uniform sampler2D tFlowMap; + #else + uniform vec2 flowDirection; + #endif - uniform vec3 color; - uniform float reflectivity; - uniform vec4 config; + uniform vec3 color; + uniform float reflectivity; + uniform vec4 config; - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; - void main() { + void main() { - #include + #include - float flowMapOffset0 = config.x; - float flowMapOffset1 = config.y; - float halfCycle = config.z; - float scale = config.w; + float flowMapOffset0 = config.x; + float flowMapOffset1 = config.y; + float halfCycle = config.z; + float scale = config.w; - vec3 toEye = normalize( vToEye ); + vec3 toEye = normalize( vToEye ); - // determine flow direction - vec2 flow; - #ifdef USE_FLOWMAP - flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; - #else - flow = flowDirection; - #endif - flow.x *= - 1.0; + // determine flow direction + vec2 flow; + #ifdef USE_FLOWMAP + flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; + #else + flow = flowDirection; + #endif + flow.x *= - 1.0; - // sample normal maps (distort uvs with flowdata) - vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); - vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); + // sample normal maps (distort uvs with flowdata) + vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); + vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); - // linear interpolate to get the final normal color - float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; - vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); + // linear interpolate to get the final normal color + float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; + vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); - // calculate normal vector - vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); + // calculate normal vector + vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); - // calculate the fresnel term to blend reflection and refraction maps - float theta = max( dot( toEye, normal ), 0.0 ); - float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); + // calculate the fresnel term to blend reflection and refraction maps + float theta = max( dot( toEye, normal ), 0.0 ); + float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); - // calculate final uv coords - vec3 coord = vCoord.xyz / vCoord.w; - vec2 uv = coord.xy + coord.z * normal.xz * 0.05; + // calculate final uv coords + vec3 coord = vCoord.xyz / vCoord.w; + vec2 uv = coord.xy + coord.z * normal.xz * 0.05; - vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); - vec4 refractColor = texture2D( tRefractionMap, uv ); + vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); + vec4 refractColor = texture2D( tRefractionMap, uv ); - // multiply water color with the mix of both textures - gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); + // multiply water color with the mix of both textures + gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); - #include - #include - #include + #include + #include + #include - }` + }` - } +}; -} ); +/* @__PURE__ */ Object.assign( Water, { WaterShader } ); export { Water }; diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index 4e77c6ede68bc6..c2e66c5c02736f 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -127,13 +127,6 @@ class BloomPass extends Pass { } -/* @__PURE__ */ Object.assign( BloomPass, { - - blurX: /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ), - blurY: /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ) - -} ); - const CombineShader = { name: 'CombineShader', @@ -173,4 +166,9 @@ const CombineShader = { }; +const blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); +const blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); + +/* @__PURE__ */ Object.assign( BloomPass, { blurX, blurY } ); + export { BloomPass }; diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index db050b8a951bb3..419b89964ddcf1 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -648,11 +648,9 @@ class OutlinePass extends Pass { } -/* @__PURE__ */ Object.assign( OutlinePass, { +const BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); +const BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); - BlurDirectionX: /* @__PURE__ */ new Vector2( 1.0, 0.0 ), - BlurDirectionY: /* @__PURE__ */ new Vector2( 0.0, 1.0 ) - -} ); +/* @__PURE__ */ Object.assign( OutlinePass, { BlurDirectionX, BlurDirectionY } ); export { OutlinePass }; diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index cf9a5f4784f7af..921af6a24bd879 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -327,14 +327,12 @@ class SAOPass extends Pass { } -/* @__PURE__ */ Object.assign( SAOPass, { +const OUTPUT = { + 'Default': 0, + 'SAO': 1, + 'Normal': 2 +}; - OUTPUT: { - Default: 0, - SAO: 1, - Normal: 2 - } - -} ); +/* @__PURE__ */ Object.assign( SAOPass, { OUTPUT } ); export { SAOPass }; diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index e41850a91a1705..5e9c427d0ea708 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -409,16 +409,14 @@ class SSAOPass extends Pass { } -/* @__PURE__ */ Object.assign( SSAOPass, { - - OUTPUT: { - Default: 0, - SSAO: 1, - Blur: 2, - Depth: 3, - Normal: 4 - } - -} ); +const OUTPUT = { + 'Default': 0, + 'SSAO': 1, + 'Blur': 2, + 'Depth': 3, + 'Normal': 4 +}; + +/* @__PURE__ */ Object.assign( SSAOPass, { OUTPUT } ); export { SSAOPass }; diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index d0fe646471dfeb..3596325cb94141 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -629,17 +629,15 @@ class SSRPass extends Pass { } -/* @__PURE__ */ Object.assign( SSRPass, { - - OUTPUT: { - Default: 0, - SSR: 1, - Beauty: 3, - Depth: 4, - Normal: 5, - Metalness: 7, - } - -} ); +const OUTPUT = { + 'Default': 0, + 'SSR': 1, + 'Beauty': 3, + 'Depth': 4, + 'Normal': 5, + 'Metalness': 7, +}; + +/* @__PURE__ */ Object.assign( SSRPass, { OUTPUT } ); export { SSRPass }; diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index d7bfa46ff66ba9..1fffa33f80579c 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -409,11 +409,9 @@ class UnrealBloomPass extends Pass { } -/* @__PURE__ */ Object.assign( UnrealBloomPass, { +const BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); +const BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); - BlurDirectionX: /* @__PURE__ */ new Vector2( 1.0, 0.0 ), - BlurDirectionY: /* @__PURE__ */ new Vector2( 0.0, 1.0 ) - -} ); +/* @__PURE__ */ Object.assign( UnrealBloomPass, { BlurDirectionX, BlurDirectionY } ); export { UnrealBloomPass }; From e1c95bf615f1b532fa2dcc6d0f732c7b14f4136a Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 08:56:16 -0500 Subject: [PATCH 33/45] VRMLLoader: cleanup --- examples/jsm/loaders/VRMLLoader.js | 278 +++++++++++++++-------------- 1 file changed, 141 insertions(+), 137 deletions(-) diff --git a/examples/jsm/loaders/VRMLLoader.js b/examples/jsm/loaders/VRMLLoader.js index 0ca07f4cc772d4..916e46a53e1b61 100644 --- a/examples/jsm/loaders/VRMLLoader.js +++ b/examples/jsm/loaders/VRMLLoader.js @@ -91,7 +91,7 @@ class VRMLLoader extends Loader { const tokenData = createTokens(); const lexer = new VRMLLexer( tokenData.tokens ); - const parser = createParser( tokenData.tokenVocabulary ); + const parser = VRMLParser.createParser( tokenData.tokenVocabulary ); const visitor = createVisitor( parser.getBaseCstVisitorConstructor() ); // lexing @@ -3275,232 +3275,236 @@ class VRMLLexer { } -function createParser( tokenVocabulary ) { +class VRMLParser { - const $ = new chevrotain.CstParser( tokenVocabulary ); + static createParser( tokenVocabulary ) { - const Version = tokenVocabulary[ 'Version' ]; - const LCurly = tokenVocabulary[ 'LCurly' ]; - const RCurly = tokenVocabulary[ 'RCurly' ]; - const LSquare = tokenVocabulary[ 'LSquare' ]; - const RSquare = tokenVocabulary[ 'RSquare' ]; - const Identifier = tokenVocabulary[ 'Identifier' ]; - const RouteIdentifier = tokenVocabulary[ 'RouteIdentifier' ]; - const StringLiteral = tokenVocabulary[ 'StringLiteral' ]; - const HexLiteral = tokenVocabulary[ 'HexLiteral' ]; - const NumberLiteral = tokenVocabulary[ 'NumberLiteral' ]; - const TrueLiteral = tokenVocabulary[ 'TrueLiteral' ]; - const FalseLiteral = tokenVocabulary[ 'FalseLiteral' ]; - const NullLiteral = tokenVocabulary[ 'NullLiteral' ]; - const DEF = tokenVocabulary[ 'DEF' ]; - const USE = tokenVocabulary[ 'USE' ]; - const ROUTE = tokenVocabulary[ 'ROUTE' ]; - const TO = tokenVocabulary[ 'TO' ]; - const NodeName = tokenVocabulary[ 'NodeName' ]; + const $ = new chevrotain.CstParser( tokenVocabulary ); - $.RULE( 'vrml', function () { + const Version = tokenVocabulary[ 'Version' ]; + const LCurly = tokenVocabulary[ 'LCurly' ]; + const RCurly = tokenVocabulary[ 'RCurly' ]; + const LSquare = tokenVocabulary[ 'LSquare' ]; + const RSquare = tokenVocabulary[ 'RSquare' ]; + const Identifier = tokenVocabulary[ 'Identifier' ]; + const RouteIdentifier = tokenVocabulary[ 'RouteIdentifier' ]; + const StringLiteral = tokenVocabulary[ 'StringLiteral' ]; + const HexLiteral = tokenVocabulary[ 'HexLiteral' ]; + const NumberLiteral = tokenVocabulary[ 'NumberLiteral' ]; + const TrueLiteral = tokenVocabulary[ 'TrueLiteral' ]; + const FalseLiteral = tokenVocabulary[ 'FalseLiteral' ]; + const NullLiteral = tokenVocabulary[ 'NullLiteral' ]; + const DEF = tokenVocabulary[ 'DEF' ]; + const USE = tokenVocabulary[ 'USE' ]; + const ROUTE = tokenVocabulary[ 'ROUTE' ]; + const TO = tokenVocabulary[ 'TO' ]; + const NodeName = tokenVocabulary[ 'NodeName' ]; - $.SUBRULE( $.version ); - $.AT_LEAST_ONE( function () { + $.RULE( 'vrml', function () { - $.SUBRULE( $.node ); + $.SUBRULE( $.version ); + $.AT_LEAST_ONE( function () { - } ); - $.MANY( function () { + $.SUBRULE( $.node ); + + } ); + $.MANY( function () { - $.SUBRULE( $.route ); + $.SUBRULE( $.route ); + + } ); } ); - } ); + $.RULE( 'version', function () { - $.RULE( 'version', function () { + $.CONSUME( Version ); - $.CONSUME( Version ); + } ); - } ); + $.RULE( 'node', function () { - $.RULE( 'node', function () { + $.OPTION( function () { - $.OPTION( function () { + $.SUBRULE( $.def ); - $.SUBRULE( $.def ); + } ); - } ); + $.CONSUME( NodeName ); + $.CONSUME( LCurly ); + $.MANY( function () { - $.CONSUME( NodeName ); - $.CONSUME( LCurly ); - $.MANY( function () { + $.SUBRULE( $.field ); - $.SUBRULE( $.field ); + } ); + $.CONSUME( RCurly ); } ); - $.CONSUME( RCurly ); - } ); + $.RULE( 'field', function () { - $.RULE( 'field', function () { + $.CONSUME( Identifier ); - $.CONSUME( Identifier ); + $.OR2( [ + { ALT: function () { - $.OR2( [ - { ALT: function () { + $.SUBRULE( $.singleFieldValue ); - $.SUBRULE( $.singleFieldValue ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.multiFieldValue ); - $.SUBRULE( $.multiFieldValue ); + } } + ] ); - } } - ] ); + } ); - } ); + $.RULE( 'def', function () { - $.RULE( 'def', function () { + $.CONSUME( DEF ); + $.OR( [ + { ALT: function () { - $.CONSUME( DEF ); - $.OR( [ - { ALT: function () { + $.CONSUME( Identifier ); - $.CONSUME( Identifier ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NodeName ); - $.CONSUME( NodeName ); + } } + ] ); - } } - ] ); + } ); - } ); + $.RULE( 'use', function () { - $.RULE( 'use', function () { + $.CONSUME( USE ); + $.OR( [ + { ALT: function () { - $.CONSUME( USE ); - $.OR( [ - { ALT: function () { + $.CONSUME( Identifier ); - $.CONSUME( Identifier ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NodeName ); - $.CONSUME( NodeName ); + } } + ] ); - } } - ] ); + } ); - } ); + $.RULE( 'singleFieldValue', function () { - $.RULE( 'singleFieldValue', function () { + $.AT_LEAST_ONE( function () { - $.AT_LEAST_ONE( function () { + $.OR( [ + { ALT: function () { - $.OR( [ - { ALT: function () { + $.SUBRULE( $.node ); - $.SUBRULE( $.node ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.use ); - $.SUBRULE( $.use ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( StringLiteral ); - $.CONSUME( StringLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( HexLiteral ); - $.CONSUME( HexLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NumberLiteral ); - $.CONSUME( NumberLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( TrueLiteral ); - $.CONSUME( TrueLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( FalseLiteral ); - $.CONSUME( FalseLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NullLiteral ); - $.CONSUME( NullLiteral ); + } } + ] ); - } } - ] ); + } ); } ); - } ); + $.RULE( 'multiFieldValue', function () { - $.RULE( 'multiFieldValue', function () { + $.CONSUME( LSquare ); + $.MANY( function () { - $.CONSUME( LSquare ); - $.MANY( function () { + $.OR( [ + { ALT: function () { - $.OR( [ - { ALT: function () { + $.SUBRULE( $.node ); - $.SUBRULE( $.node ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.SUBRULE( $.use ); - $.SUBRULE( $.use ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( StringLiteral ); - $.CONSUME( StringLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( HexLiteral ); - $.CONSUME( HexLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NumberLiteral ); - $.CONSUME( NumberLiteral ); + } }, + { ALT: function () { - } }, - { ALT: function () { + $.CONSUME( NullLiteral ); - $.CONSUME( NullLiteral ); + } } + ] ); - } } - ] ); + } ); + $.CONSUME( RSquare ); } ); - $.CONSUME( RSquare ); - } ); + $.RULE( 'route', function () { - $.RULE( 'route', function () { + $.CONSUME( ROUTE ); + $.CONSUME( RouteIdentifier ); + $.CONSUME( TO ); + $.CONSUME2( RouteIdentifier ); - $.CONSUME( ROUTE ); - $.CONSUME( RouteIdentifier ); - $.CONSUME( TO ); - $.CONSUME2( RouteIdentifier ); + } ); - } ); + $.performSelfAnalysis(); - $.performSelfAnalysis(); + return $; - return $; + } } From 6dab494362156fc5a90662e2ff7a80de6437d280 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 10:33:02 -0500 Subject: [PATCH 34/45] IFFParser: cleanup --- examples/jsm/loaders/lwo/IFFParser.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/examples/jsm/loaders/lwo/IFFParser.js b/examples/jsm/loaders/lwo/IFFParser.js index 6852d62a4dc22d..8503e750328f16 100644 --- a/examples/jsm/loaders/lwo/IFFParser.js +++ b/examples/jsm/loaders/lwo/IFFParser.js @@ -120,9 +120,11 @@ class IFFParser { } + /// // FORM PARSING METHODS /// + // Forms are organisational and can contain any number of sub chunks and sub forms // FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * } parseForm( length ) { @@ -133,6 +135,7 @@ class IFFParser { // SKIPPED FORMS // if skipForm( length ) is called, the entire form and any sub forms and chunks are skipped + case 'ISEQ': // Image sequence case 'ANIM': // plug in animation case 'STCC': // Color-cycling Still @@ -194,7 +197,8 @@ class IFFParser { this.parseEnvelope( length ); break; - // CLIP FORM AND SUB FORMS + // CLIP FORM AND SUB FORMS + case 'CLIP': if ( this.tree.format === 'LWO2' ) { @@ -220,12 +224,14 @@ class IFFParser { }; break; - // Not in spec, used by texture nodes + // Not in spec, used by texture nodes + case 'IMST': this.parseImageStateForm( length ); break; - // SURF FORM AND SUB FORMS + // SURF FORM AND SUB FORMS + case 'SURF': this.parseSurfaceForm( length ); break; @@ -261,7 +267,8 @@ class IFFParser { this.parseEntryForm( length ); break; - // Image Map Layer + // Image Map Layer + case 'IMAP': this.parseImageMap( length ); break; @@ -270,7 +277,8 @@ class IFFParser { this.parseXVAL( 'amplitude', length ); break; - //Texture Mapping Form + //Texture Mapping Form + case 'TMAP': this.setupForm( 'textureMap', length ); break; @@ -528,6 +536,7 @@ class IFFParser { /// // CHUNK PARSING METHODS /// + // clips can either be defined inside a surface node, or at the top // level and they have a different format in each case parseClip( length ) { @@ -654,7 +663,6 @@ class IFFParser { var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength - // if we have not reached then end of the layer block, there must be a parent defined this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent @@ -883,6 +891,7 @@ class IFFParser { // print the chunk plus some bytes padding either side // printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 ); + var data = this.reader.getString( length ); this.currentForm[ blockID ] = data; @@ -1177,7 +1186,6 @@ class Debugger { } - // ************** UTILITY FUNCTIONS ************** function isEven( num ) { From 94602e1e0af6994b23786e793271e5e1df4a765e Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sun, 8 Oct 2023 10:34:26 -0500 Subject: [PATCH 35/45] IFFParser: cleanup --- examples/jsm/loaders/lwo/IFFParser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/jsm/loaders/lwo/IFFParser.js b/examples/jsm/loaders/lwo/IFFParser.js index 8503e750328f16..75e031089a7acf 100644 --- a/examples/jsm/loaders/lwo/IFFParser.js +++ b/examples/jsm/loaders/lwo/IFFParser.js @@ -686,6 +686,7 @@ class IFFParser { // parse VMAP or VMAD // Associates a set of floating-point vectors with a set of points. // VMAP: { type[ID4], dimension[U2], name[S0], ( vert[VX], value[F4] # dimension ) * } + // VMAD Associates a set of floating-point vectors with the vertices of specific polygons. // Similar to VMAP UVs, but associates with polygon vertices rather than points // to solve to problem of UV seams: VMAD chunks are paired with VMAPs of the same name, From 6d46eb48ae490be6214ce613c64fbdc4ae5f9f4f Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:49:27 -0500 Subject: [PATCH 36/45] Addons: use IIFE for static fields See latter case in https://github.com/mrdoob/three.js/pull/26912#issuecomment-1752998540. --- examples/jsm/exporters/DRACOExporter.js | 285 +- examples/jsm/exporters/GLTFExporter.js | 3926 +++++++++-------- examples/jsm/libs/utif.module.js | 6 +- examples/jsm/lines/LineMaterial.js | 202 +- examples/jsm/loaders/KTX2Loader.js | 1081 +++-- examples/jsm/loaders/LogLuvLoader.js | 698 +-- examples/jsm/loaders/RGBMLoader.js | 1072 ++--- examples/jsm/objects/Lensflare.js | 478 +- examples/jsm/objects/Reflector.js | 328 +- examples/jsm/objects/ReflectorForSSRPass.js | 516 +-- examples/jsm/objects/Refractor.js | 380 +- examples/jsm/objects/Sky.js | 304 +- examples/jsm/objects/Water2.js | 480 +- examples/jsm/postprocessing/BloomPass.js | 194 +- examples/jsm/postprocessing/OutlinePass.js | 942 ++-- examples/jsm/postprocessing/Pass.js | 12 +- examples/jsm/postprocessing/SAOPass.js | 498 +-- examples/jsm/postprocessing/SSAOPass.js | 512 +-- examples/jsm/postprocessing/SSRPass.js | 922 ++-- .../jsm/postprocessing/UnrealBloomPass.js | 557 +-- examples/jsm/webxr/VRButton.js | 233 +- 21 files changed, 6845 insertions(+), 6781 deletions(-) diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 48a622da757645..d70548db6edfa9 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -17,266 +17,257 @@ import { Color } from 'three'; /* global DracoEncoderModule */ -class DRACOExporter { +const DRACOExporter = /* @__PURE__ */ ( () => { - parse( object, options = {} ) { + class DRACOExporter { - options = Object.assign( { - decodeSpeed: 5, - encodeSpeed: 5, - encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, - quantization: [ 16, 8, 8, 8, 8 ], - exportUvs: true, - exportNormals: true, - exportColor: false, - }, options ); + parse( object, options = {} ) { - if ( DracoEncoderModule === undefined ) { + options = Object.assign( { + decodeSpeed: 5, + encodeSpeed: 5, + encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, + quantization: [ 16, 8, 8, 8, 8 ], + exportUvs: true, + exportNormals: true, + exportColor: false, + }, options ); - throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); + if ( DracoEncoderModule === undefined ) { - } + throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); - const geometry = object.geometry; + } - const dracoEncoder = DracoEncoderModule(); - const encoder = new dracoEncoder.Encoder(); - let builder; - let dracoObject; + const geometry = object.geometry; - if ( object.isMesh === true ) { + const dracoEncoder = DracoEncoderModule(); + const encoder = new dracoEncoder.Encoder(); + let builder; + let dracoObject; - builder = new dracoEncoder.MeshBuilder(); - dracoObject = new dracoEncoder.Mesh(); + if ( object.isMesh === true ) { - const vertices = geometry.getAttribute( 'position' ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + builder = new dracoEncoder.MeshBuilder(); + dracoObject = new dracoEncoder.Mesh(); - const faces = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); - if ( faces !== null ) { + const faces = geometry.getIndex(); - builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array ); + if ( faces !== null ) { - } else { + builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array ); - const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); + } else { - for ( let i = 0; i < faces.length; i ++ ) { + const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); - faces[ i ] = i; + for ( let i = 0; i < faces.length; i ++ ) { - } + faces[ i ] = i; - builder.AddFacesToMesh( dracoObject, vertices.count, faces ); + } - } + builder.AddFacesToMesh( dracoObject, vertices.count, faces ); + + } + + if ( options.exportNormals === true ) { - if ( options.exportNormals === true ) { + const normals = geometry.getAttribute( 'normal' ); - const normals = geometry.getAttribute( 'normal' ); + if ( normals !== undefined ) { - if ( normals !== undefined ) { + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array ); + } } - } + if ( options.exportUvs === true ) { - if ( options.exportUvs === true ) { + const uvs = geometry.getAttribute( 'uv' ); - const uvs = geometry.getAttribute( 'uv' ); + if ( uvs !== undefined ) { - if ( uvs !== undefined ) { + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array ); + } } - } + if ( options.exportColor === true ) { - if ( options.exportColor === true ) { + const colors = geometry.getAttribute( 'color' ); - const colors = geometry.getAttribute( 'color' ); + if ( colors !== undefined ) { - if ( colors !== undefined ) { + const array = createVertexColorSRGBArray( colors ); - const array = createVertexColorSRGBArray( colors ); + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); + } } - } + } else if ( object.isPoints === true ) { - } else if ( object.isPoints === true ) { + builder = new dracoEncoder.PointCloudBuilder(); + dracoObject = new dracoEncoder.PointCloud(); - builder = new dracoEncoder.PointCloudBuilder(); - dracoObject = new dracoEncoder.PointCloud(); + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); - const vertices = geometry.getAttribute( 'position' ); - builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + if ( options.exportColor === true ) { - if ( options.exportColor === true ) { + const colors = geometry.getAttribute( 'color' ); - const colors = geometry.getAttribute( 'color' ); + if ( colors !== undefined ) { - if ( colors !== undefined ) { + const array = createVertexColorSRGBArray( colors ); - const array = createVertexColorSRGBArray( colors ); + builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); - builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); + } } - } + } else { - } else { + throw new Error( 'DRACOExporter: Unsupported object type.' ); - throw new Error( 'DRACOExporter: Unsupported object type.' ); + } - } + //Compress using draco encoder - //Compress using draco encoder + const encodedData = new dracoEncoder.DracoInt8Array(); - const encodedData = new dracoEncoder.DracoInt8Array(); + //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). - //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). + const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; + const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; - const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; - const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; + encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); - encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); + // Sets the desired encoding method for a given geometry. - // Sets the desired encoding method for a given geometry. + if ( options.encoderMethod !== undefined ) { - if ( options.encoderMethod !== undefined ) { + encoder.SetEncodingMethod( options.encoderMethod ); - encoder.SetEncodingMethod( options.encoderMethod ); + } - } + // Sets the quantization (number of bits used to represent) compression options for a named attribute. + // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. + if ( options.quantization !== undefined ) { - // Sets the quantization (number of bits used to represent) compression options for a named attribute. - // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. - if ( options.quantization !== undefined ) { + for ( let i = 0; i < 5; i ++ ) { - for ( let i = 0; i < 5; i ++ ) { + if ( options.quantization[ i ] !== undefined ) { - if ( options.quantization[ i ] !== undefined ) { + encoder.SetAttributeQuantization( i, options.quantization[ i ] ); - encoder.SetAttributeQuantization( i, options.quantization[ i ] ); + } } } - } + let length; - let length; + if ( object.isMesh === true ) { - if ( object.isMesh === true ) { + length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); - length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); + } else { - } else { + length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); - length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); + } - } + dracoEncoder.destroy( dracoObject ); - dracoEncoder.destroy( dracoObject ); + if ( length === 0 ) { - if ( length === 0 ) { + throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' ); - throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' ); + } - } + //Copy encoded data to buffer. + const outputData = new Int8Array( new ArrayBuffer( length ) ); - //Copy encoded data to buffer. - const outputData = new Int8Array( new ArrayBuffer( length ) ); + for ( let i = 0; i < length; i ++ ) { - for ( let i = 0; i < length; i ++ ) { + outputData[ i ] = encodedData.GetValue( i ); - outputData[ i ] = encodedData.GetValue( i ); + } - } + dracoEncoder.destroy( encodedData ); + dracoEncoder.destroy( encoder ); + dracoEncoder.destroy( builder ); - dracoEncoder.destroy( encodedData ); - dracoEncoder.destroy( encoder ); - dracoEncoder.destroy( builder ); + return outputData; - return outputData; + } } -} + function createVertexColorSRGBArray( attribute ) { -function createVertexColorSRGBArray( attribute ) { + // While .drc files do not specify colorspace, the only 'official' tooling + // is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected + // for .drc files, but note that Draco buffers embedded in glTF files will + // be Linear-sRGB instead. - // While .drc files do not specify colorspace, the only 'official' tooling - // is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected - // for .drc files, but note that Draco buffers embedded in glTF files will - // be Linear-sRGB instead. + const _color = new Color(); - const _color = new Color(); + const count = attribute.count; + const itemSize = attribute.itemSize; + const array = new Float32Array( count * itemSize ); - const count = attribute.count; - const itemSize = attribute.itemSize; - const array = new Float32Array( count * itemSize ); + for ( let i = 0, il = count; i < il; i ++ ) { - for ( let i = 0, il = count; i < il; i ++ ) { + _color.fromBufferAttribute( attribute, i ).convertLinearToSRGB(); - _color.fromBufferAttribute( attribute, i ).convertLinearToSRGB(); + array[ i * itemSize ] = _color.r; + array[ i * itemSize + 1 ] = _color.g; + array[ i * itemSize + 2 ] = _color.b; - array[ i * itemSize ] = _color.r; - array[ i * itemSize + 1 ] = _color.g; - array[ i * itemSize + 2 ] = _color.b; + if ( itemSize === 4 ) { - if ( itemSize === 4 ) { + array[ i * itemSize + 3 ] = attribute.getW( i ); - array[ i * itemSize + 3 ] = attribute.getW( i ); + } } - } + return array; - return array; - -} - -// Encoder methods + } -const MESH_EDGEBREAKER_ENCODING = 1; -const MESH_SEQUENTIAL_ENCODING = 0; + // Encoder methods -// Geometry type + DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; + DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; -const POINT_CLOUD = 0; -const TRIANGULAR_MESH = 1; + // Geometry type -// Attribute type + DRACOExporter.POINT_CLOUD = 0; + DRACOExporter.TRIANGULAR_MESH = 1; -const INVALID = - 1; -const POSITION = 0; -const NORMAL = 1; -const COLOR = 2; -const TEX_COORD = 3; -const GENERIC = 4; + // Attribute type -/* @__PURE__ */ Object.assign( DRACOExporter, { + DRACOExporter.INVALID = - 1; + DRACOExporter.POSITION = 0; + DRACOExporter.NORMAL = 1; + DRACOExporter.COLOR = 2; + DRACOExporter.TEX_COORD = 3; + DRACOExporter.GENERIC = 4; - MESH_EDGEBREAKER_ENCODING, - MESH_SEQUENTIAL_ENCODING, - POINT_CLOUD, - TRIANGULAR_MESH, - INVALID, - POSITION, - NORMAL, - COLOR, - TEX_COORD, - GENERIC, + return DRACOExporter; -} ); +} )(); export { DRACOExporter }; diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 4c02c2bd95e15c..7a5efe0807905e 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -27,3207 +27,3211 @@ import { } from 'three'; import { decompress } from './../utils/TextureUtils.js'; +const GLTFExporter = /* @__PURE__ */ ( () => { -/** - * The KHR_mesh_quantization extension allows these extra attribute component types - * - * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes - */ -const KHR_mesh_quantization_ExtraAttrTypes = { - POSITION: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'unsigned byte normalized', - 'short', - 'short normalized', - 'unsigned short', - 'unsigned short normalized', - ], - NORMAL: [ - 'byte normalized', - 'short normalized', - ], - TANGENT: [ - 'byte normalized', - 'short normalized', - ], - TEXCOORD: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'short', - 'short normalized', - 'unsigned short', - ], -}; - - -class GLTFExporter { - - constructor() { - - this.pluginCallbacks = []; - - this.register( function ( writer ) { - - return new GLTFLightExtension( writer ); - } ); + /** + * The KHR_mesh_quantization extension allows these extra attribute component types + * + * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes + */ + const KHR_mesh_quantization_ExtraAttrTypes = { + POSITION: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'unsigned byte normalized', + 'short', + 'short normalized', + 'unsigned short', + 'unsigned short normalized', + ], + NORMAL: [ + 'byte normalized', + 'short normalized', + ], + TANGENT: [ + 'byte normalized', + 'short normalized', + ], + TEXCOORD: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'short', + 'short normalized', + 'unsigned short', + ], + }; - this.register( function ( writer ) { - return new GLTFMaterialsUnlitExtension( writer ); + class GLTFExporter { - } ); + constructor() { - this.register( function ( writer ) { + this.pluginCallbacks = []; - return new GLTFMaterialsTransmissionExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFLightExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsVolumeExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsUnlitExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsIorExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsTransmissionExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsSpecularExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsVolumeExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsClearcoatExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsIorExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsIridescenceExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsSpecularExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsSheenExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsClearcoatExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsAnisotropyExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsIridescenceExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsEmissiveStrengthExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsSheenExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMeshGpuInstancing( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsAnisotropyExtension( writer ); - } + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsEmissiveStrengthExtension( writer ); + + } ); - register( callback ) { + this.register( function ( writer ) { - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + return new GLTFMeshGpuInstancing( writer ); - this.pluginCallbacks.push( callback ); + } ); } - return this; + register( callback ) { - } + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - unregister( callback ) { + this.pluginCallbacks.push( callback ); - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + } - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + return this; } - return this; + unregister( callback ) { - } - - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Function} onError Callback on errors - * @param {Object} options options - */ - parse( input, onDone, onError, options ) { + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - const writer = new GLTFWriter(); - const plugins = []; + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + } - plugins.push( this.pluginCallbacks[ i ]( writer ) ); + return this; } - writer.setPlugins( plugins ); - writer.write( input, onDone, options ).catch( onError ); + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Function} onError Callback on errors + * @param {Object} options options + */ + parse( input, onDone, onError, options ) { - } + const writer = new GLTFWriter(); + const plugins = []; - parseAsync( input, options ) { + for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { - const scope = this; + plugins.push( this.pluginCallbacks[ i ]( writer ) ); - return new Promise( function ( resolve, reject ) { + } - scope.parse( input, resolve, reject, options ); + writer.setPlugins( plugins ); + writer.write( input, onDone, options ).catch( onError ); - } ); + } - } + parseAsync( input, options ) { -} + const scope = this; -//------------------------------------------------------------------------------ -// Constants -//------------------------------------------------------------------------------ + return new Promise( function ( resolve, reject ) { -const WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - - BYTE: 0x1400, - UNSIGNED_BYTE: 0x1401, - SHORT: 0x1402, - UNSIGNED_SHORT: 0x1403, - INT: 0x1404, - UNSIGNED_INT: 0x1405, - FLOAT: 0x1406, - - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, + scope.parse( input, resolve, reject, options ); - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 -}; + } ); -const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; + } -const THREE_TO_WEBGL = {}; + } -THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; -THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; -THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; -THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; -THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; -THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + //------------------------------------------------------------------------------ + // Constants + //------------------------------------------------------------------------------ + + const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + + BYTE: 0x1400, + UNSIGNED_BYTE: 0x1401, + SHORT: 0x1402, + UNSIGNED_SHORT: 0x1403, + INT: 0x1404, + UNSIGNED_INT: 0x1405, + FLOAT: 0x1406, + + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, + + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497 + }; -THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; -THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; -THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; -const PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' -}; + const THREE_TO_WEBGL = {}; -const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color(); + THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; + THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; + THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; + THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; + THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; + THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; -// GLB constants -// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; + THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; + THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; -const GLB_HEADER_BYTES = 12; -const GLB_HEADER_MAGIC = 0x46546C67; -const GLB_VERSION = 2; + const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights' + }; -const GLB_CHUNK_PREFIX_BYTES = 8; -const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; -const GLB_CHUNK_TYPE_BIN = 0x004E4942; + const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color(); -//------------------------------------------------------------------------------ -// Utility functions -//------------------------------------------------------------------------------ + // GLB constants + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification -/** - * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal - */ -function equalArray( array1, array2 ) { + const GLB_HEADER_BYTES = 12; + const GLB_HEADER_MAGIC = 0x46546C67; + const GLB_VERSION = 2; - return ( array1.length === array2.length ) && array1.every( function ( element, index ) { + const GLB_CHUNK_PREFIX_BYTES = 8; + const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; + const GLB_CHUNK_TYPE_BIN = 0x004E4942; - return element === array2[ index ]; + //------------------------------------------------------------------------------ + // Utility functions + //------------------------------------------------------------------------------ - } ); + /** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ + function equalArray( array1, array2 ) { -} + return ( array1.length === array2.length ) && array1.every( function ( element, index ) { -/** - * Converts a string to an ArrayBuffer. - * @param {string} text - * @return {ArrayBuffer} - */ -function stringToArrayBuffer( text ) { + return element === array2[ index ]; - return new TextEncoder().encode( text ).buffer; - -} - -/** - * Is identity matrix - * - * @param {Matrix4} matrix - * @returns {Boolean} Returns true, if parameter is identity matrix - */ -function isIdentityMatrix( matrix ) { + } ); - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); + } -} + /** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ + function stringToArrayBuffer( text ) { -/** - * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ -function getMinMax( attribute, start, count ) { + return new TextEncoder().encode( text ).buffer; - const output = { + } - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + /** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ + function isIdentityMatrix( matrix ) { - }; + return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - for ( let i = start; i < start + count; i ++ ) { + } + + /** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ + function getMinMax( attribute, start, count ) { - for ( let a = 0; a < attribute.itemSize; a ++ ) { + const output = { + + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - let value; + }; - if ( attribute.itemSize > 4 ) { + for ( let i = start; i < start + count; i ++ ) { + + for ( let a = 0; a < attribute.itemSize; a ++ ) { + + let value; + + if ( attribute.itemSize > 4 ) { // no support for interleaved data for itemSize > 4 - value = attribute.array[ i * attribute.itemSize + a ]; + value = attribute.array[ i * attribute.itemSize + a ]; + + } else { - } else { + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + if ( attribute.normalized === true ) { - if ( attribute.normalized === true ) { + value = MathUtils.normalize( value, attribute.array ); - value = MathUtils.normalize( value, attribute.array ); + } } - } + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); + } } - } + return output; - return output; + } -} + /** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ + function getPaddedBufferSize( bufferSize ) { -/** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. - * - */ -function getPaddedBufferSize( bufferSize ) { + return Math.ceil( bufferSize / 4 ) * 4; - return Math.ceil( bufferSize / 4 ) * 4; + } -} + /** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ + function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { -/** - * Returns a buffer aligned to 4-byte boundary. - * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ -function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { + const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); + if ( paddedLength !== arrayBuffer.byteLength ) { - if ( paddedLength !== arrayBuffer.byteLength ) { + const array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); - const array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); + if ( paddingByte !== 0 ) { - if ( paddingByte !== 0 ) { + for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + array[ i ] = paddingByte; - array[ i ] = paddingByte; + } } + return array.buffer; + } - return array.buffer; + return arrayBuffer; } - return arrayBuffer; + function getCanvas() { -} + if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { -function getCanvas() { + return new OffscreenCanvas( 1, 1 ); - if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { + } - return new OffscreenCanvas( 1, 1 ); + return document.createElement( 'canvas' ); } - return document.createElement( 'canvas' ); - -} + function getToBlobPromise( canvas, mimeType ) { -function getToBlobPromise( canvas, mimeType ) { + if ( canvas.toBlob !== undefined ) { - if ( canvas.toBlob !== undefined ) { + return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); - return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); - - } + } - let quality; + let quality; - // Blink's implementation of convertToBlob seems to default to a quality level of 100% - // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. - if ( mimeType === 'image/jpeg' ) { + // Blink's implementation of convertToBlob seems to default to a quality level of 100% + // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. + if ( mimeType === 'image/jpeg' ) { - quality = 0.92; + quality = 0.92; - } else if ( mimeType === 'image/webp' ) { + } else if ( mimeType === 'image/webp' ) { - quality = 0.8; + quality = 0.8; - } + } - return canvas.convertToBlob( { + return canvas.convertToBlob( { - type: mimeType, - quality: quality + type: mimeType, + quality: quality - } ); + } ); -} + } -/** - * Writer - */ -class GLTFWriter { + /** + * Writer + */ + class GLTFWriter { - constructor() { + constructor() { - this.plugins = []; + this.plugins = []; - this.options = {}; - this.pending = []; - this.buffers = []; + this.options = {}; + this.pending = []; + this.buffers = []; - this.byteOffset = 0; - this.buffers = []; - this.nodeMap = new Map(); - this.skins = []; + this.byteOffset = 0; + this.buffers = []; + this.nodeMap = new Map(); + this.skins = []; - this.extensionsUsed = {}; - this.extensionsRequired = {}; + this.extensionsUsed = {}; + this.extensionsRequired = {}; - this.uids = new Map(); - this.uid = 0; + this.uids = new Map(); + this.uid = 0; - this.json = { - asset: { - version: '2.0', - generator: 'THREE.GLTFExporter' - } - }; + this.json = { + asset: { + version: '2.0', + generator: 'THREE.GLTFExporter' + } + }; - this.cache = { - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map() - }; + this.cache = { + meshes: new Map(), + attributes: new Map(), + attributesNormalized: new Map(), + materials: new Map(), + textures: new Map(), + images: new Map() + }; - } + } - setPlugins( plugins ) { + setPlugins( plugins ) { - this.plugins = plugins; + this.plugins = plugins; - } + } - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - async write( input, onDone, options = {} ) { + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + async write( input, onDone, options = {} ) { - this.options = Object.assign( { + this.options = Object.assign( { // default options - binary: false, - trs: false, - onlyVisible: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false - }, options ); + binary: false, + trs: false, + onlyVisible: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false + }, options ); - if ( this.options.animations.length > 0 ) { + if ( this.options.animations.length > 0 ) { - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true; - } - - this.processInput( input ); - - await Promise.all( this.pending ); + } - const writer = this; - const buffers = writer.buffers; - const json = writer.json; - options = writer.options; + this.processInput( input ); - const extensionsUsed = writer.extensionsUsed; - const extensionsRequired = writer.extensionsRequired; + await Promise.all( this.pending ); - // Merge buffers. - const blob = new Blob( buffers, { type: 'application/octet-stream' } ); + const writer = this; + const buffers = writer.buffers; + const json = writer.json; + options = writer.options; - // Declare extensions. - const extensionsUsedList = Object.keys( extensionsUsed ); - const extensionsRequiredList = Object.keys( extensionsRequired ); + const extensionsUsed = writer.extensionsUsed; + const extensionsRequired = writer.extensionsRequired; - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; - if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; + // Merge buffers. + const blob = new Blob( buffers, { type: 'application/octet-stream' } ); - // Update bytelength of the single buffer. - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + // Declare extensions. + const extensionsUsedList = Object.keys( extensionsUsed ); + const extensionsRequiredList = Object.keys( extensionsRequired ); - if ( options.binary === true ) { + if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; + if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + // Update bytelength of the single buffer. + if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + if ( options.binary === true ) { - // Binary chunk. - const binaryChunk = getPaddedArrayBuffer( reader.result ); - const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - // JSON chunk. - const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + const reader = new FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - // GLB header. - const header = new ArrayBuffer( GLB_HEADER_BYTES ); - const headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - const totalByteLength = GLB_HEADER_BYTES + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer( reader.result ); + const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); + binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + + // JSON chunk. + const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); + const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); + jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + + // GLB header. + const header = new ArrayBuffer( GLB_HEADER_BYTES ); + const headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + const totalByteLength = GLB_HEADER_BYTES + jsonChunkPrefix.byteLength + jsonChunk.byteLength + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); + headerView.setUint32( 8, totalByteLength, true ); + + const glbBlob = new Blob( [ + header, + jsonChunkPrefix, + jsonChunk, + binaryChunkPrefix, + binaryChunk + ], { type: 'application/octet-stream' } ); - const glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); + const glbReader = new FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { - const glbReader = new FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { + onDone( glbReader.result ); - onDone( glbReader.result ); + }; }; - }; + } else { - } else { + if ( json.buffers && json.buffers.length > 0 ) { - if ( json.buffers && json.buffers.length > 0 ) { + const reader = new FileReader(); + reader.readAsDataURL( blob ); + reader.onloadend = function () { - const reader = new FileReader(); - reader.readAsDataURL( blob ); - reader.onloadend = function () { + const base64data = reader.result; + json.buffers[ 0 ].uri = base64data; + onDone( json ); - const base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); + }; - }; + } else { - } else { + onDone( json ); - onDone( json ); + } } + } + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ + serializeUserData( object, objectDef ) { - } + if ( Object.keys( object.userData ).length === 0 ) return; - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material} object - * @param {Object} objectDef - */ - serializeUserData( object, objectDef ) { + const options = this.options; + const extensionsUsed = this.extensionsUsed; - if ( Object.keys( object.userData ).length === 0 ) return; + try { - const options = this.options; - const extensionsUsed = this.extensionsUsed; + const json = JSON.parse( JSON.stringify( object.userData ) ); - try { + if ( options.includeCustomExtensions && json.gltfExtensions ) { - const json = JSON.parse( JSON.stringify( object.userData ) ); + if ( objectDef.extensions === undefined ) objectDef.extensions = {}; - if ( options.includeCustomExtensions && json.gltfExtensions ) { + for ( const extensionName in json.gltfExtensions ) { - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; + objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; + extensionsUsed[ extensionName ] = true; - for ( const extensionName in json.gltfExtensions ) { + } - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; + delete json.gltfExtensions; } - delete json.gltfExtensions; + if ( Object.keys( json ).length > 0 ) objectDef.extras = json; - } - - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + } catch ( error ) { - } catch ( error ) { - - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + } + } - } + /** + * Returns ids for buffer attributes. + * @param {Object} object + * @return {Integer} + */ + getUID( attribute, isRelativeCopy = false ) { - /** - * Returns ids for buffer attributes. - * @param {Object} object - * @return {Integer} - */ - getUID( attribute, isRelativeCopy = false ) { + if ( this.uids.has( attribute ) === false ) { - if ( this.uids.has( attribute ) === false ) { + const uids = new Map(); - const uids = new Map(); + uids.set( true, this.uid ++ ); + uids.set( false, this.uid ++ ); - uids.set( true, this.uid ++ ); - uids.set( false, this.uid ++ ); + this.uids.set( attribute, uids ); - this.uids.set( attribute, uids ); + } - } + const uids = this.uids.get( attribute ); - const uids = this.uids.get( attribute ); + return uids.get( isRelativeCopy ); - return uids.get( isRelativeCopy ); + } - } + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ + isNormalizedNormalAttribute( normal ) { - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - */ - isNormalizedNormalAttribute( normal ) { + const cache = this.cache; - const cache = this.cache; + if ( cache.attributesNormalized.has( normal ) ) return false; - if ( cache.attributesNormalized.has( normal ) ) return false; + const v = new Vector3(); - const v = new Vector3(); + for ( let i = 0, il = normal.count; i < il; i ++ ) { - for ( let i = 0, il = normal.count; i < il; i ++ ) { + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; + } + + return true; } - return true; + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + createNormalizedNormalAttribute( normal ) { - } + const cache = this.cache; - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute( normal ) { + if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); - const cache = this.cache; + const attribute = normal.clone(); + const v = new Vector3(); - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + for ( let i = 0, il = attribute.count; i < il; i ++ ) { - const attribute = normal.clone(); - const v = new Vector3(); + v.fromBufferAttribute( attribute, i ); - for ( let i = 0, il = attribute.count; i < il; i ++ ) { + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - v.fromBufferAttribute( attribute, i ); + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + } else { - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); + v.normalize(); - } else { + } - v.normalize(); + attribute.setXYZ( i, v.x, v.y, v.z ); } - attribute.setXYZ( i, v.x, v.y, v.z ); + cache.attributesNormalized.set( normal, attribute ); + + return attribute; } - cache.attributesNormalized.set( normal, attribute ); + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ + applyTextureTransform( mapDef, texture ) { - return attribute; + let didTransform = false; + const transformDef = {}; - } + if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform( mapDef, texture ) { + transformDef.offset = texture.offset.toArray(); + didTransform = true; - let didTransform = false; - const transformDef = {}; + } - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + if ( texture.rotation !== 0 ) { - transformDef.offset = texture.offset.toArray(); - didTransform = true; + transformDef.rotation = texture.rotation; + didTransform = true; - } + } - if ( texture.rotation !== 0 ) { + if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { - transformDef.rotation = texture.rotation; - didTransform = true; + transformDef.scale = texture.repeat.toArray(); + didTransform = true; - } + } - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + if ( didTransform ) { - transformDef.scale = texture.repeat.toArray(); - didTransform = true; + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + this.extensionsUsed[ 'KHR_texture_transform' ] = true; + + } } - if ( didTransform ) { + buildMetalRoughTexture( metalnessMap, roughnessMap ) { - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; + if ( metalnessMap === roughnessMap ) return metalnessMap; - } + function getEncodingConversion( map ) { - } + if ( map.colorSpace === SRGBColorSpace ) { - buildMetalRoughTexture( metalnessMap, roughnessMap ) { + return function SRGBToLinear( c ) { - if ( metalnessMap === roughnessMap ) return metalnessMap; + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - function getEncodingConversion( map ) { + }; - if ( map.colorSpace === SRGBColorSpace ) { + } - return function SRGBToLinear( c ) { + return function LinearToLinear( c ) { - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + return c; }; } - return function LinearToLinear( c ) { - - return c; - - }; - - } + console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); - console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + if ( metalnessMap instanceof CompressedTexture ) { - if ( metalnessMap instanceof CompressedTexture ) { + metalnessMap = decompress( metalnessMap ); - metalnessMap = decompress( metalnessMap ); + } - } + if ( roughnessMap instanceof CompressedTexture ) { - if ( roughnessMap instanceof CompressedTexture ) { + roughnessMap = decompress( roughnessMap ); - roughnessMap = decompress( roughnessMap ); + } - } + const metalness = metalnessMap ? metalnessMap.image : null; + const roughness = roughnessMap ? roughnessMap.image : null; - const metalness = metalnessMap ? metalnessMap.image : null; - const roughness = roughnessMap ? roughnessMap.image : null; + const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); + const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); - const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); - const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); + const canvas = getCanvas(); + canvas.width = width; + canvas.height = height; - const canvas = getCanvas(); - canvas.width = width; - canvas.height = height; + const context = canvas.getContext( '2d' ); + context.fillStyle = '#00ffff'; + context.fillRect( 0, 0, width, height ); - const context = canvas.getContext( '2d' ); - context.fillStyle = '#00ffff'; - context.fillRect( 0, 0, width, height ); + const composite = context.getImageData( 0, 0, width, height ); - const composite = context.getImageData( 0, 0, width, height ); + if ( metalness ) { - if ( metalness ) { + context.drawImage( metalness, 0, 0, width, height ); - context.drawImage( metalness, 0, 0, width, height ); + const convert = getEncodingConversion( metalnessMap ); + const data = context.getImageData( 0, 0, width, height ).data; - const convert = getEncodingConversion( metalnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; + for ( let i = 2; i < data.length; i += 4 ) { - for ( let i = 2; i < data.length; i += 4 ) { + composite.data[ i ] = convert( data[ i ] / 256 ) * 256; - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + } } - } + if ( roughness ) { - if ( roughness ) { + context.drawImage( roughness, 0, 0, width, height ); - context.drawImage( roughness, 0, 0, width, height ); + const convert = getEncodingConversion( roughnessMap ); + const data = context.getImageData( 0, 0, width, height ).data; - const convert = getEncodingConversion( roughnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; + for ( let i = 1; i < data.length; i += 4 ) { - for ( let i = 1; i < data.length; i += 4 ) { + composite.data[ i ] = convert( data[ i ] / 256 ) * 256; - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + } } - } + context.putImageData( composite, 0, 0 ); - context.putImageData( composite, 0, 0 ); + // - // + const reference = metalnessMap || roughnessMap; - const reference = metalnessMap || roughnessMap; + const texture = reference.clone(); - const texture = reference.clone(); + texture.source = new Source( canvas ); + texture.colorSpace = NoColorSpace; + texture.channel = ( metalnessMap || roughnessMap ).channel; - texture.source = new Source( canvas ); - texture.colorSpace = NoColorSpace; - texture.channel = ( metalnessMap || roughnessMap ).channel; + if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { - if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { + console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); + + } - console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); + return texture; } - return texture; + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + processBuffer( buffer ) { - } + const json = this.json; + const buffers = this.buffers; - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - processBuffer( buffer ) { + if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; - const json = this.json; - const buffers = this.buffers; + // All buffers are merged before export. + buffers.push( buffer ); - if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; + return 0; - // All buffers are merged before export. - buffers.push( buffer ); - - return 0; - - } + } - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - processBufferView( attribute, componentType, start, count, target ) { + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + processBufferView( attribute, componentType, start, count, target ) { - const json = this.json; + const json = this.json; - if ( ! json.bufferViews ) json.bufferViews = []; + if ( ! json.bufferViews ) json.bufferViews = []; - // Create a new dataview and dump the attribute's array into it + // Create a new dataview and dump the attribute's array into it - let componentSize; + let componentSize; - switch ( componentType ) { + switch ( componentType ) { - case WEBGL_CONSTANTS.BYTE: - case WEBGL_CONSTANTS.UNSIGNED_BYTE: + case WEBGL_CONSTANTS.BYTE: + case WEBGL_CONSTANTS.UNSIGNED_BYTE: - componentSize = 1; + componentSize = 1; - break; + break; - case WEBGL_CONSTANTS.SHORT: - case WEBGL_CONSTANTS.UNSIGNED_SHORT: + case WEBGL_CONSTANTS.SHORT: + case WEBGL_CONSTANTS.UNSIGNED_SHORT: - componentSize = 2; + componentSize = 2; - break; + break; - default: + default: - componentSize = 4; + componentSize = 4; - } + } - const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); - const dataView = new DataView( new ArrayBuffer( byteLength ) ); - let offset = 0; + const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + const dataView = new DataView( new ArrayBuffer( byteLength ) ); + let offset = 0; - for ( let i = start; i < start + count; i ++ ) { + for ( let i = start; i < start + count; i ++ ) { - for ( let a = 0; a < attribute.itemSize; a ++ ) { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - let value; + let value; - if ( attribute.itemSize > 4 ) { + if ( attribute.itemSize > 4 ) { // no support for interleaved data for itemSize > 4 - value = attribute.array[ i * attribute.itemSize + a ]; + value = attribute.array[ i * attribute.itemSize + a ]; - } else { + } else { - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); - if ( attribute.normalized === true ) { + if ( attribute.normalized === true ) { - value = MathUtils.normalize( value, attribute.array ); + value = MathUtils.normalize( value, attribute.array ); + + } } - } + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + dataView.setFloat32( offset, value, true ); - dataView.setFloat32( offset, value, true ); + } else if ( componentType === WEBGL_CONSTANTS.INT ) { - } else if ( componentType === WEBGL_CONSTANTS.INT ) { + dataView.setInt32( offset, value, true ); - dataView.setInt32( offset, value, true ); + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + dataView.setUint32( offset, value, true ); - dataView.setUint32( offset, value, true ); + } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { - } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { + dataView.setInt16( offset, value, true ); - dataView.setInt16( offset, value, true ); + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + dataView.setUint16( offset, value, true ); - dataView.setUint16( offset, value, true ); + } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { - } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { + dataView.setInt8( offset, value ); - dataView.setInt8( offset, value ); + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + dataView.setUint8( offset, value ); - dataView.setUint8( offset, value ); + } - } + offset += componentSize; - offset += componentSize; + } } - } + const bufferViewDef = { - const bufferViewDef = { + buffer: this.processBuffer( dataView.buffer ), + byteOffset: this.byteOffset, + byteLength: byteLength - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength + }; - }; + if ( target !== undefined ) bufferViewDef.target = target; - if ( target !== undefined ) bufferViewDef.target = target; + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize; - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize; + } - } + this.byteOffset += byteLength; - this.byteOffset += byteLength; + json.bufferViews.push( bufferViewDef ); - json.bufferViews.push( bufferViewDef ); + // @TODO Merge bufferViews where possible. + const output = { - // @TODO Merge bufferViews where possible. - const output = { + id: json.bufferViews.length - 1, + byteLength: 0 - id: json.bufferViews.length - 1, - byteLength: 0 + }; - }; + return output; - return output; + } - } + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + processBufferViewImage( blob ) { - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - processBufferViewImage( blob ) { + const writer = this; + const json = writer.json; + + if ( ! json.bufferViews ) json.bufferViews = []; - const writer = this; - const json = writer.json; + return new Promise( function ( resolve ) { - if ( ! json.bufferViews ) json.bufferViews = []; + const reader = new FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - return new Promise( function ( resolve ) { + const buffer = getPaddedArrayBuffer( reader.result ); - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + const bufferViewDef = { + buffer: writer.processBuffer( buffer ), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength + }; - const buffer = getPaddedArrayBuffer( reader.result ); + writer.byteOffset += buffer.byteLength; + resolve( json.bufferViews.push( bufferViewDef ) - 1 ); - const bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength }; - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); + } ); - }; + } - } ); + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ + processAccessor( attribute, geometry, start, count ) { - } + const json = this.json; - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array - */ - processAccessor( attribute, geometry, start, count ) { - - const json = this.json; + const types = { - const types = { + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 9: 'MAT3', + 16: 'MAT4' - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 9: 'MAT3', - 16: 'MAT4' + }; - }; + let componentType; - let componentType; + // Detect the component type of the attribute array + if ( attribute.array.constructor === Float32Array ) { - // Detect the component type of the attribute array - if ( attribute.array.constructor === Float32Array ) { + componentType = WEBGL_CONSTANTS.FLOAT; - componentType = WEBGL_CONSTANTS.FLOAT; + } else if ( attribute.array.constructor === Int32Array ) { - } else if ( attribute.array.constructor === Int32Array ) { + componentType = WEBGL_CONSTANTS.INT; - componentType = WEBGL_CONSTANTS.INT; + } else if ( attribute.array.constructor === Uint32Array ) { - } else if ( attribute.array.constructor === Uint32Array ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + } else if ( attribute.array.constructor === Int16Array ) { - } else if ( attribute.array.constructor === Int16Array ) { + componentType = WEBGL_CONSTANTS.SHORT; - componentType = WEBGL_CONSTANTS.SHORT; + } else if ( attribute.array.constructor === Uint16Array ) { - } else if ( attribute.array.constructor === Uint16Array ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + } else if ( attribute.array.constructor === Int8Array ) { - } else if ( attribute.array.constructor === Int8Array ) { + componentType = WEBGL_CONSTANTS.BYTE; - componentType = WEBGL_CONSTANTS.BYTE; + } else if ( attribute.array.constructor === Uint8Array ) { - } else if ( attribute.array.constructor === Uint8Array ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + } else { - } else { + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); + } - } + if ( start === undefined ) start = 0; + if ( count === undefined || count === Infinity ) count = attribute.count; - if ( start === undefined ) start = 0; - if ( count === undefined || count === Infinity ) count = attribute.count; + // Skip creating an accessor if the attribute doesn't have data to export + if ( count === 0 ) return null; - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) return null; + const minMax = getMinMax( attribute, start, count ); + let bufferViewTarget; - const minMax = getMinMax( attribute, start, count ); - let bufferViewTarget; + // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. + if ( geometry !== undefined ) { - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if ( geometry !== undefined ) { + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + } - } + const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); - const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + const accessorDef = { - const accessorDef = { + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] + }; - }; + if ( attribute.normalized === true ) accessorDef.normalized = true; + if ( ! json.accessors ) json.accessors = []; - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; + return json.accessors.push( accessorDef ) - 1; - return json.accessors.push( accessorDef ) - 1; + } - } + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (RGBAFormat) + * @param {Boolean} flipY before writing out the image + * @param {String} mimeType export format + * @return {Integer} Index of the processed texture in the "images" array + */ + processImage( image, format, flipY, mimeType = 'image/png' ) { - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (RGBAFormat) - * @param {Boolean} flipY before writing out the image - * @param {String} mimeType export format - * @return {Integer} Index of the processed texture in the "images" array - */ - processImage( image, format, flipY, mimeType = 'image/png' ) { + if ( image !== null ) { - if ( image !== null ) { + const writer = this; + const cache = writer.cache; + const json = writer.json; + const options = writer.options; + const pending = writer.pending; - const writer = this; - const cache = writer.cache; - const json = writer.json; - const options = writer.options; - const pending = writer.pending; + if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); + const cachedImages = cache.images.get( image ); - const cachedImages = cache.images.get( image ); + const key = mimeType + ':flipY/' + flipY.toString(); - const key = mimeType + ':flipY/' + flipY.toString(); + if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; + if ( ! json.images ) json.images = []; - if ( ! json.images ) json.images = []; + const imageDef = { mimeType: mimeType }; - const imageDef = { mimeType: mimeType }; + const canvas = getCanvas(); - const canvas = getCanvas(); + canvas.width = Math.min( image.width, options.maxTextureSize ); + canvas.height = Math.min( image.height, options.maxTextureSize ); - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); + const ctx = canvas.getContext( '2d' ); - const ctx = canvas.getContext( '2d' ); + if ( flipY === true ) { - if ( flipY === true ) { + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); + } - } + if ( image.data !== undefined ) { // THREE.DataTexture - if ( image.data !== undefined ) { // THREE.DataTexture + if ( format !== RGBAFormat ) { - if ( format !== RGBAFormat ) { + console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); - console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); + } - } + if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + } - } + const data = new Uint8ClampedArray( image.height * image.width * 4 ); - const data = new Uint8ClampedArray( image.height * image.width * 4 ); + for ( let i = 0; i < data.length; i += 4 ) { - for ( let i = 0; i < data.length; i += 4 ) { + data[ i + 0 ] = image.data[ i + 0 ]; + data[ i + 1 ] = image.data[ i + 1 ]; + data[ i + 2 ] = image.data[ i + 2 ]; + data[ i + 3 ] = image.data[ i + 3 ]; - data[ i + 0 ] = image.data[ i + 0 ]; - data[ i + 1 ] = image.data[ i + 1 ]; - data[ i + 2 ] = image.data[ i + 2 ]; - data[ i + 3 ] = image.data[ i + 3 ]; + } - } + ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); + } else { - } else { + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + } - } + if ( options.binary === true ) { - if ( options.binary === true ) { + pending.push( - pending.push( + getToBlobPromise( canvas, mimeType ) + .then( blob => writer.processBufferViewImage( blob ) ) + .then( bufferViewIndex => { - getToBlobPromise( canvas, mimeType ) - .then( blob => writer.processBufferViewImage( blob ) ) - .then( bufferViewIndex => { + imageDef.bufferView = bufferViewIndex; - imageDef.bufferView = bufferViewIndex; + } ) - } ) + ); - ); + } else { - } else { + if ( canvas.toDataURL !== undefined ) { - if ( canvas.toDataURL !== undefined ) { + imageDef.uri = canvas.toDataURL( mimeType ); - imageDef.uri = canvas.toDataURL( mimeType ); + } else { - } else { + pending.push( - pending.push( + getToBlobPromise( canvas, mimeType ) + .then( blob => new FileReader().readAsDataURL( blob ) ) + .then( dataURL => { - getToBlobPromise( canvas, mimeType ) - .then( blob => new FileReader().readAsDataURL( blob ) ) - .then( dataURL => { + imageDef.uri = dataURL; - imageDef.uri = dataURL; + } ) - } ) + ); - ); + } } - } + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; - const index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; + } else { - } else { + throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); - throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); + } } - } + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + processSampler( map ) { - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - processSampler( map ) { + const json = this.json; - const json = this.json; + if ( ! json.samplers ) json.samplers = []; - if ( ! json.samplers ) json.samplers = []; + const samplerDef = { + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] + }; - const samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; + return json.samplers.push( samplerDef ) - 1; - return json.samplers.push( samplerDef ) - 1; + } - } + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + processTexture( map ) { - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - processTexture( map ) { + const writer = this; + const options = writer.options; + const cache = this.cache; + const json = this.json; - const writer = this; - const options = writer.options; - const cache = this.cache; - const json = this.json; + if ( cache.textures.has( map ) ) return cache.textures.get( map ); - if ( cache.textures.has( map ) ) return cache.textures.get( map ); + if ( ! json.textures ) json.textures = []; - if ( ! json.textures ) json.textures = []; + // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture + if ( map instanceof CompressedTexture ) { - // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture - if ( map instanceof CompressedTexture ) { + map = decompress( map, options.maxTextureSize ); - map = decompress( map, options.maxTextureSize ); + } - } + let mimeType = map.userData.mimeType; - let mimeType = map.userData.mimeType; + if ( mimeType === 'image/webp' ) mimeType = 'image/png'; - if ( mimeType === 'image/webp' ) mimeType = 'image/png'; + const textureDef = { + sampler: this.processSampler( map ), + source: this.processImage( map.image, map.format, map.flipY, mimeType ) + }; - const textureDef = { - sampler: this.processSampler( map ), - source: this.processImage( map.image, map.format, map.flipY, mimeType ) - }; + if ( map.name ) textureDef.name = map.name; - if ( map.name ) textureDef.name = map.name; + this._invokeAll( function ( ext ) { - this._invokeAll( function ( ext ) { + ext.writeTexture && ext.writeTexture( map, textureDef ); - ext.writeTexture && ext.writeTexture( map, textureDef ); + } ); - } ); + const index = json.textures.push( textureDef ) - 1; + cache.textures.set( map, index ); + return index; - const index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; + } - } + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ + processMaterial( material ) { - /** - * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array - */ - processMaterial( material ) { + const cache = this.cache; + const json = this.json; - const cache = this.cache; - const json = this.json; + if ( cache.materials.has( material ) ) return cache.materials.get( material ); - if ( cache.materials.has( material ) ) return cache.materials.get( material ); + if ( material.isShaderMaterial ) { - if ( material.isShaderMaterial ) { + console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); + return null; - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; + } - } + if ( ! json.materials ) json.materials = []; - if ( ! json.materials ) json.materials = []; + // @QUESTION Should we avoid including any attribute that has the default value? + const materialDef = { pbrMetallicRoughness: {} }; - // @QUESTION Should we avoid including any attribute that has the default value? - const materialDef = { pbrMetallicRoughness: {} }; + if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + } - } + // pbrMetallicRoughness.baseColorFactor + const color = material.color.toArray().concat( [ material.opacity ] ); - // pbrMetallicRoughness.baseColorFactor - const color = material.color.toArray().concat( [ material.opacity ] ); + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + materialDef.pbrMetallicRoughness.baseColorFactor = color; - materialDef.pbrMetallicRoughness.baseColorFactor = color; + } - } + if ( material.isMeshStandardMaterial ) { - if ( material.isMeshStandardMaterial ) { + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + } else { - } else { + materialDef.pbrMetallicRoughness.metallicFactor = 0.5; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; - materialDef.pbrMetallicRoughness.metallicFactor = 0.5; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + } - } + // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.metalnessMap || material.roughnessMap ) { - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { + const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); - const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); + const metalRoughMapDef = { + index: this.processTexture( metalRoughTexture ), + channel: metalRoughTexture.channel + }; + this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; - const metalRoughMapDef = { - index: this.processTexture( metalRoughTexture ), - channel: metalRoughTexture.channel - }; - this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + } - } + // pbrMetallicRoughness.baseColorTexture + if ( material.map ) { - // pbrMetallicRoughness.baseColorTexture - if ( material.map ) { + const baseColorMapDef = { + index: this.processTexture( material.map ), + texCoord: material.map.channel + }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - const baseColorMapDef = { - index: this.processTexture( material.map ), - texCoord: material.map.channel - }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + } - } + if ( material.emissive ) { - if ( material.emissive ) { + const emissive = material.emissive; + const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); - const emissive = material.emissive; - const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); + if ( maxEmissiveComponent > 0 ) { - if ( maxEmissiveComponent > 0 ) { + materialDef.emissiveFactor = material.emissive.toArray(); - materialDef.emissiveFactor = material.emissive.toArray(); + } - } + // emissiveTexture + if ( material.emissiveMap ) { - // emissiveTexture - if ( material.emissiveMap ) { + const emissiveMapDef = { + index: this.processTexture( material.emissiveMap ), + texCoord: material.emissiveMap.channel + }; + this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); + materialDef.emissiveTexture = emissiveMapDef; - const emissiveMapDef = { - index: this.processTexture( material.emissiveMap ), - texCoord: material.emissiveMap.channel - }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; + } } - } + // normalTexture + if ( material.normalMap ) { - // normalTexture - if ( material.normalMap ) { + const normalMapDef = { + index: this.processTexture( material.normalMap ), + texCoord: material.normalMap.channel + }; - const normalMapDef = { - index: this.processTexture( material.normalMap ), - texCoord: material.normalMap.channel - }; + if ( material.normalScale && material.normalScale.x !== 1 ) { - if ( material.normalScale && material.normalScale.x !== 1 ) { + // glTF normal scale is univariate. Ignore `y`, which may be flipped. + // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + normalMapDef.scale = material.normalScale.x; - // glTF normal scale is univariate. Ignore `y`, which may be flipped. - // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - normalMapDef.scale = material.normalScale.x; + } + + this.applyTextureTransform( normalMapDef, material.normalMap ); + materialDef.normalTexture = normalMapDef; } - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; + // occlusionTexture + if ( material.aoMap ) { - } + const occlusionMapDef = { + index: this.processTexture( material.aoMap ), + texCoord: material.aoMap.channel + }; - // occlusionTexture - if ( material.aoMap ) { + if ( material.aoMapIntensity !== 1.0 ) { - const occlusionMapDef = { - index: this.processTexture( material.aoMap ), - texCoord: material.aoMap.channel - }; + occlusionMapDef.strength = material.aoMapIntensity; - if ( material.aoMapIntensity !== 1.0 ) { + } - occlusionMapDef.strength = material.aoMapIntensity; + this.applyTextureTransform( occlusionMapDef, material.aoMap ); + materialDef.occlusionTexture = occlusionMapDef; } - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; - - } + // alphaMode + if ( material.transparent ) { - // alphaMode - if ( material.transparent ) { + materialDef.alphaMode = 'BLEND'; - materialDef.alphaMode = 'BLEND'; + } else { - } else { + if ( material.alphaTest > 0.0 ) { - if ( material.alphaTest > 0.0 ) { + materialDef.alphaMode = 'MASK'; + materialDef.alphaCutoff = material.alphaTest; - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; + } } - } - - // doubleSided - if ( material.side === DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; + // doubleSided + if ( material.side === DoubleSide ) materialDef.doubleSided = true; + if ( material.name !== '' ) materialDef.name = material.name; - this.serializeUserData( material, materialDef ); + this.serializeUserData( material, materialDef ); - this._invokeAll( function ( ext ) { + this._invokeAll( function ( ext ) { - ext.writeMaterial && ext.writeMaterial( material, materialDef ); + ext.writeMaterial && ext.writeMaterial( material, materialDef ); - } ); + } ); - const index = json.materials.push( materialDef ) - 1; - cache.materials.set( material, index ); - return index; + const index = json.materials.push( materialDef ) - 1; + cache.materials.set( material, index ); + return index; - } + } - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array - */ - processMesh( mesh ) { + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ + processMesh( mesh ) { - const cache = this.cache; - const json = this.json; + const cache = this.cache; + const json = this.json; - const meshCacheKeyParts = [ mesh.geometry.uuid ]; + const meshCacheKeyParts = [ mesh.geometry.uuid ]; - if ( Array.isArray( mesh.material ) ) { + if ( Array.isArray( mesh.material ) ) { - for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { + for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { - meshCacheKeyParts.push( mesh.material[ i ].uuid ); + meshCacheKeyParts.push( mesh.material[ i ].uuid ); - } + } - } else { + } else { - meshCacheKeyParts.push( mesh.material.uuid ); + meshCacheKeyParts.push( mesh.material.uuid ); - } + } - const meshCacheKey = meshCacheKeyParts.join( ':' ); + const meshCacheKey = meshCacheKeyParts.join( ':' ); - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); - const geometry = mesh.geometry; + const geometry = mesh.geometry; - let mode; + let mode; - // Use the correct mode - if ( mesh.isLineSegments ) { + // Use the correct mode + if ( mesh.isLineSegments ) { - mode = WEBGL_CONSTANTS.LINES; + mode = WEBGL_CONSTANTS.LINES; - } else if ( mesh.isLineLoop ) { + } else if ( mesh.isLineLoop ) { - mode = WEBGL_CONSTANTS.LINE_LOOP; + mode = WEBGL_CONSTANTS.LINE_LOOP; - } else if ( mesh.isLine ) { + } else if ( mesh.isLine ) { - mode = WEBGL_CONSTANTS.LINE_STRIP; + mode = WEBGL_CONSTANTS.LINE_STRIP; - } else if ( mesh.isPoints ) { + } else if ( mesh.isPoints ) { - mode = WEBGL_CONSTANTS.POINTS; + mode = WEBGL_CONSTANTS.POINTS; - } else { + } else { - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; - } + } - const meshDef = {}; - const attributes = {}; - const primitives = []; - const targets = []; - - // Conversion between attributes names in threejs and gltf spec - const nameConversion = { - uv: 'TEXCOORD_0', - uv1: 'TEXCOORD_1', - uv2: 'TEXCOORD_2', - uv3: 'TEXCOORD_3', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; + const meshDef = {}; + const attributes = {}; + const primitives = []; + const targets = []; + + // Conversion between attributes names in threejs and gltf spec + const nameConversion = { + uv: 'TEXCOORD_0', + uv1: 'TEXCOORD_1', + uv2: 'TEXCOORD_2', + uv3: 'TEXCOORD_3', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' + }; - const originalNormal = geometry.getAttribute( 'normal' ); + const originalNormal = geometry.getAttribute( 'normal' ); - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); - } + } - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - let modifiedAttribute = null; + // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor + let modifiedAttribute = null; - for ( let attributeName in geometry.attributes ) { + for ( let attributeName in geometry.attributes ) { - // Ignore morph target attributes, which are exported later. - if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; + // Ignore morph target attributes, which are exported later. + if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; - const attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); + const attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - const validVertexAttributes = + // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. + const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; - if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + if ( cache.attributes.has( this.getUID( attribute ) ) ) { - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; - } + } - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - modifiedAttribute = null; - const array = attribute.array; + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null; + const array = attribute.array; - if ( attributeName === 'JOINTS_0' && + if ( attributeName === 'JOINTS_0' && ! ( array instanceof Uint16Array ) && ! ( array instanceof Uint8Array ) ) { - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); - } + } - const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); - if ( accessor !== null ) { + if ( accessor !== null ) { - if ( ! attributeName.startsWith( '_' ) ) { + if ( ! attributeName.startsWith( '_' ) ) { - this.detectMeshQuantization( attributeName, attribute ); + this.detectMeshQuantization( attributeName, attribute ); - } + } - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); + attributes[ attributeName ] = accessor; + cache.attributes.set( this.getUID( attribute ), accessor ); + + } } - } + if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); + // Skip if no exportable attributes found + if ( Object.keys( attributes ).length === 0 ) return null; - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) return null; + // Morph targets + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + const weights = []; + const targetNames = []; + const reverseDictionary = {}; - const weights = []; - const targetNames = []; - const reverseDictionary = {}; + if ( mesh.morphTargetDictionary !== undefined ) { - if ( mesh.morphTargetDictionary !== undefined ) { + for ( const key in mesh.morphTargetDictionary ) { - for ( const key in mesh.morphTargetDictionary ) { + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + } } - } + for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { + const target = {}; + let warned = false; - const target = {}; - let warned = false; + for ( const attributeName in geometry.morphAttributes ) { - for ( const attributeName in geometry.morphAttributes ) { + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. + if ( attributeName !== 'position' && attributeName !== 'normal' ) { - if ( attributeName !== 'position' && attributeName !== 'normal' ) { + if ( ! warned ) { - if ( ! warned ) { + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; + } + + continue; } - continue; + const attribute = geometry.morphAttributes[ attributeName ][ i ]; + const gltfAttributeName = attributeName.toUpperCase(); - } + // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - const attribute = geometry.morphAttributes[ attributeName ][ i ]; - const gltfAttributeName = attributeName.toUpperCase(); + const baseAttribute = geometry.attributes[ attributeName ]; - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { - const baseAttribute = geometry.attributes[ attributeName ]; + target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); + continue; - if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { - - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); - continue; + } - } + // Clones attribute not to override + const relativeAttribute = attribute.clone(); - // Clones attribute not to override - const relativeAttribute = attribute.clone(); + if ( ! geometry.morphTargetsRelative ) { - if ( ! geometry.morphTargetsRelative ) { + for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - for ( let a = 0; a < attribute.itemSize; a ++ ) { + if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); + if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); + if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); + if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); - if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); - if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); - if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); - if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); + } } } - } + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); + } - } + targets.push( target ); - targets.push( target ); + weights.push( mesh.morphTargetInfluences[ i ] ); - weights.push( mesh.morphTargetInfluences[ i ] ); + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + } - } + meshDef.weights = weights; - meshDef.weights = weights; + if ( targetNames.length > 0 ) { - if ( targetNames.length > 0 ) { + meshDef.extras = {}; + meshDef.extras.targetNames = targetNames; - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; + } } - } + const isMultiMaterial = Array.isArray( mesh.material ); - const isMultiMaterial = Array.isArray( mesh.material ); + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; + for ( let i = 0, il = groups.length; i < il; i ++ ) { - for ( let i = 0, il = groups.length; i < il; i ++ ) { + const primitive = { + mode: mode, + attributes: attributes, + }; - const primitive = { - mode: mode, - attributes: attributes, - }; + this.serializeUserData( geometry, primitive ); - this.serializeUserData( geometry, primitive ); + if ( targets.length > 0 ) primitive.targets = targets; - if ( targets.length > 0 ) primitive.targets = targets; + if ( geometry.index !== null ) { - if ( geometry.index !== null ) { + let cacheKey = this.getUID( geometry.index ); - let cacheKey = this.getUID( geometry.index ); + if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { + cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; + } - } + if ( cache.attributes.has( cacheKey ) ) { - if ( cache.attributes.has( cacheKey ) ) { + primitive.indices = cache.attributes.get( cacheKey ); - primitive.indices = cache.attributes.get( cacheKey ); + } else { - } else { + primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cache.attributes.set( cacheKey, primitive.indices ); + + } - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); + if ( primitive.indices === null ) delete primitive.indices; } - if ( primitive.indices === null ) delete primitive.indices; - - } + const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); - const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + if ( material !== null ) primitive.material = material; - if ( material !== null ) primitive.material = material; + primitives.push( primitive ); - primitives.push( primitive ); + } - } + meshDef.primitives = primitives; - meshDef.primitives = primitives; + if ( ! json.meshes ) json.meshes = []; - if ( ! json.meshes ) json.meshes = []; + this._invokeAll( function ( ext ) { - this._invokeAll( function ( ext ) { + ext.writeMesh && ext.writeMesh( mesh, meshDef ); - ext.writeMesh && ext.writeMesh( mesh, meshDef ); + } ); - } ); + const index = json.meshes.push( meshDef ) - 1; + cache.meshes.set( meshCacheKey, index ); + return index; - const index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; + } - } + /** + * If a vertex attribute with a + * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) + * is used, it is checked whether it is a valid data type according to the + * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) + * extension. + * In this case the extension is automatically added to the list of used extensions. + * + * @param {string} attributeName + * @param {THREE.BufferAttribute} attribute + */ + detectMeshQuantization( attributeName, attribute ) { - /** - * If a vertex attribute with a - * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) - * is used, it is checked whether it is a valid data type according to the - * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) - * extension. - * In this case the extension is automatically added to the list of used extensions. - * - * @param {string} attributeName - * @param {THREE.BufferAttribute} attribute - */ - detectMeshQuantization( attributeName, attribute ) { + if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; - if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; + let attrType = undefined; - let attrType = undefined; + switch ( attribute.array.constructor ) { - switch ( attribute.array.constructor ) { + case Int8Array: - case Int8Array: + attrType = 'byte'; - attrType = 'byte'; + break; - break; + case Uint8Array: - case Uint8Array: + attrType = 'unsigned byte'; - attrType = 'unsigned byte'; + break; - break; + case Int16Array: - case Int16Array: + attrType = 'short'; - attrType = 'short'; + break; - break; + case Uint16Array: - case Uint16Array: + attrType = 'unsigned short'; - attrType = 'unsigned short'; + break; - break; + default: - default: + return; - return; + } - } + if ( attribute.normalized ) attrType += ' normalized'; - if ( attribute.normalized ) attrType += ' normalized'; + const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; - const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; + if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { - if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { + this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; + this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; - this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; - this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; + } } - } - - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - processCamera( camera ) { + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + processCamera( camera ) { - const json = this.json; + const json = this.json; - if ( ! json.cameras ) json.cameras = []; - - const isOrtho = camera.isOrthographicCamera; - - const cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; + if ( ! json.cameras ) json.cameras = []; - if ( isOrtho ) { + const isOrtho = camera.isOrthographicCamera; - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective' }; - } else { + if ( isOrtho ) { - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - } + } else { - // Question: Is saving "type" as name intentional? - if ( camera.name !== '' ) cameraDef.name = camera.type; + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: MathUtils.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - return json.cameras.push( cameraDef ) - 1; + } - } + // Question: Is saving "type" as name intentional? + if ( camera.name !== '' ) cameraDef.name = camera.type; - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {number|null} - */ - processAnimation( clip, root ) { + return json.cameras.push( cameraDef ) - 1; - const json = this.json; - const nodeMap = this.nodeMap; + } - if ( ! json.animations ) json.animations = []; + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ + processAnimation( clip, root ) { - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + const json = this.json; + const nodeMap = this.nodeMap; - const tracks = clip.tracks; - const channels = []; - const samplers = []; + if ( ! json.animations ) json.animations = []; - for ( let i = 0; i < tracks.length; ++ i ) { + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - const track = tracks[ i ]; - const trackBinding = PropertyBinding.parseTrackName( track.name ); - let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; + const tracks = clip.tracks; + const channels = []; + const samplers = []; - if ( trackBinding.objectName === 'bones' ) { + for ( let i = 0; i < tracks.length; ++ i ) { - if ( trackNode.isSkinnedMesh === true ) { + const track = tracks[ i ]; + const trackBinding = PropertyBinding.parseTrackName( track.name ); + let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); + const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + if ( trackBinding.objectName === 'bones' ) { - } else { + if ( trackNode.isSkinnedMesh === true ) { - trackNode = undefined; + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - } + } else { - } + trackNode = undefined; - if ( ! trackNode || ! trackProperty ) { + } - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + } - } + if ( ! trackNode || ! trackProperty ) { - const inputItemSize = 1; - let outputItemSize = track.values.length / track.times.length; + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + } - outputItemSize /= trackNode.morphTargetInfluences.length; + const inputItemSize = 1; + let outputItemSize = track.values.length / track.times.length; - } + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - let interpolation; + outputItemSize /= trackNode.morphTargetInfluences.length; - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + } - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { + let interpolation; - interpolation = 'CUBICSPLINE'; + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3; + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). + if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - } else if ( track.getInterpolation() === InterpolateDiscrete ) { + interpolation = 'CUBICSPLINE'; - interpolation = 'STEP'; + // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= 3; - } else { + } else if ( track.getInterpolation() === InterpolateDiscrete ) { - interpolation = 'LINEAR'; + interpolation = 'STEP'; - } + } else { - samplers.push( { - input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), - output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation - } ); + interpolation = 'LINEAR'; - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty } - } ); - } + samplers.push( { + input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), + output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), + interpolation: interpolation + } ); + + channels.push( { + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } + } ); + + } - json.animations.push( { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels - } ); + json.animations.push( { + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels + } ); - return json.animations.length - 1; + return json.animations.length - 1; - } + } - /** - * @param {THREE.Object3D} object - * @return {number|null} - */ + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ processSkin( object ) { - const json = this.json; - const nodeMap = this.nodeMap; + const json = this.json; + const nodeMap = this.nodeMap; - const node = json.nodes[ nodeMap.get( object ) ]; + const node = json.nodes[ nodeMap.get( object ) ]; - const skeleton = object.skeleton; + const skeleton = object.skeleton; - if ( skeleton === undefined ) return null; + if ( skeleton === undefined ) return null; - const rootJoint = object.skeleton.bones[ 0 ]; + const rootJoint = object.skeleton.bones[ 0 ]; - if ( rootJoint === undefined ) return null; + if ( rootJoint === undefined ) return null; - const joints = []; - const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - const temporaryBoneInverse = new Matrix4(); + const joints = []; + const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + const temporaryBoneInverse = new Matrix4(); - for ( let i = 0; i < skeleton.bones.length; ++ i ) { + for ( let i = 0; i < skeleton.bones.length; ++ i ) { - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); + temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - } + } - if ( json.skins === undefined ) json.skins = []; + if ( json.skins === undefined ) json.skins = []; - json.skins.push( { - inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); + json.skins.push( { + inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) + } ); - const skinIndex = node.skin = json.skins.length - 1; + const skinIndex = node.skin = json.skins.length - 1; - return skinIndex; + return skinIndex; - } + } - /** - * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - processNode( object ) { + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + processNode( object ) { - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; + const json = this.json; + const options = this.options; + const nodeMap = this.nodeMap; - if ( ! json.nodes ) json.nodes = []; + if ( ! json.nodes ) json.nodes = []; - const nodeDef = {}; + const nodeDef = {}; - if ( options.trs ) { + if ( options.trs ) { - const rotation = object.quaternion.toArray(); - const position = object.position.toArray(); - const scale = object.scale.toArray(); + const rotation = object.quaternion.toArray(); + const position = object.position.toArray(); + const scale = object.scale.toArray(); - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - nodeDef.rotation = rotation; + nodeDef.rotation = rotation; - } + } - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - nodeDef.translation = position; + nodeDef.translation = position; - } + } - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - nodeDef.scale = scale; + nodeDef.scale = scale; - } + } - } else { + } else { - if ( object.matrixAutoUpdate ) { + if ( object.matrixAutoUpdate ) { - object.updateMatrix(); + object.updateMatrix(); - } + } + + if ( isIdentityMatrix( object.matrix ) === false ) { - if ( isIdentityMatrix( object.matrix ) === false ) { + nodeDef.matrix = object.matrix.elements; - nodeDef.matrix = object.matrix.elements; + } } - } + // We don't export empty strings name because it represents no-name in Three.js. + if ( object.name !== '' ) nodeDef.name = String( object.name ); - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) nodeDef.name = String( object.name ); + this.serializeUserData( object, nodeDef ); - this.serializeUserData( object, nodeDef ); + if ( object.isMesh || object.isLine || object.isPoints ) { - if ( object.isMesh || object.isLine || object.isPoints ) { + const meshIndex = this.processMesh( object ); - const meshIndex = this.processMesh( object ); + if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; + } else if ( object.isCamera ) { - } else if ( object.isCamera ) { + nodeDef.camera = this.processCamera( object ); - nodeDef.camera = this.processCamera( object ); + } - } + if ( object.isSkinnedMesh ) this.skins.push( object ); - if ( object.isSkinnedMesh ) this.skins.push( object ); + if ( object.children.length > 0 ) { - if ( object.children.length > 0 ) { + const children = []; - const children = []; + for ( let i = 0, l = object.children.length; i < l; i ++ ) { - for ( let i = 0, l = object.children.length; i < l; i ++ ) { + const child = object.children[ i ]; - const child = object.children[ i ]; + if ( child.visible || options.onlyVisible === false ) { - if ( child.visible || options.onlyVisible === false ) { + const nodeIndex = this.processNode( child ); - const nodeIndex = this.processNode( child ); + if ( nodeIndex !== null ) children.push( nodeIndex ); - if ( nodeIndex !== null ) children.push( nodeIndex ); + } } - } + if ( children.length > 0 ) nodeDef.children = children; - if ( children.length > 0 ) nodeDef.children = children; + } - } + this._invokeAll( function ( ext ) { - this._invokeAll( function ( ext ) { + ext.writeNode && ext.writeNode( object, nodeDef ); - ext.writeNode && ext.writeNode( object, nodeDef ); + } ); - } ); + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + return nodeIndex; - const nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - return nodeIndex; + } - } + /** + * Process Scene + * @param {Scene} node Scene to process + */ + processScene( scene ) { - /** - * Process Scene - * @param {Scene} node Scene to process - */ - processScene( scene ) { + const json = this.json; + const options = this.options; - const json = this.json; - const options = this.options; + if ( ! json.scenes ) { - if ( ! json.scenes ) { + json.scenes = []; + json.scene = 0; - json.scenes = []; - json.scene = 0; + } - } + const sceneDef = {}; - const sceneDef = {}; + if ( scene.name !== '' ) sceneDef.name = scene.name; - if ( scene.name !== '' ) sceneDef.name = scene.name; + json.scenes.push( sceneDef ); - json.scenes.push( sceneDef ); + const nodes = []; - const nodes = []; + for ( let i = 0, l = scene.children.length; i < l; i ++ ) { - for ( let i = 0, l = scene.children.length; i < l; i ++ ) { + const child = scene.children[ i ]; - const child = scene.children[ i ]; + if ( child.visible || options.onlyVisible === false ) { - if ( child.visible || options.onlyVisible === false ) { + const nodeIndex = this.processNode( child ); - const nodeIndex = this.processNode( child ); + if ( nodeIndex !== null ) nodes.push( nodeIndex ); - if ( nodeIndex !== null ) nodes.push( nodeIndex ); + } } - } + if ( nodes.length > 0 ) sceneDef.nodes = nodes; - if ( nodes.length > 0 ) sceneDef.nodes = nodes; + this.serializeUserData( scene, sceneDef ); - this.serializeUserData( scene, sceneDef ); + } - } + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + processObjects( objects ) { - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process - */ - processObjects( objects ) { + const scene = new Scene(); + scene.name = 'AuxScene'; - const scene = new Scene(); - scene.name = 'AuxScene'; + for ( let i = 0; i < objects.length; i ++ ) { - for ( let i = 0; i < objects.length; i ++ ) { + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push( objects[ i ] ); - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); + } - } + this.processScene( scene ); - this.processScene( scene ); + } - } + /** + * @param {THREE.Object3D|Array} input + */ + processInput( input ) { - /** - * @param {THREE.Object3D|Array} input - */ - processInput( input ) { + const options = this.options; - const options = this.options; + input = input instanceof Array ? input : [ input ]; - input = input instanceof Array ? input : [ input ]; + this._invokeAll( function ( ext ) { - this._invokeAll( function ( ext ) { + ext.beforeParse && ext.beforeParse( input ); - ext.beforeParse && ext.beforeParse( input ); + } ); - } ); + const objectsWithoutScene = []; - const objectsWithoutScene = []; + for ( let i = 0; i < input.length; i ++ ) { - for ( let i = 0; i < input.length; i ++ ) { + if ( input[ i ] instanceof Scene ) { - if ( input[ i ] instanceof Scene ) { + this.processScene( input[ i ] ); - this.processScene( input[ i ] ); + } else { - } else { + objectsWithoutScene.push( input[ i ] ); - objectsWithoutScene.push( input[ i ] ); + } } - } + if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); - if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + for ( let i = 0; i < this.skins.length; ++ i ) { - for ( let i = 0; i < this.skins.length; ++ i ) { + this.processSkin( this.skins[ i ] ); - this.processSkin( this.skins[ i ] ); + } - } + for ( let i = 0; i < options.animations.length; ++ i ) { - for ( let i = 0; i < options.animations.length; ++ i ) { + this.processAnimation( options.animations[ i ], input[ 0 ] ); - this.processAnimation( options.animations[ i ], input[ 0 ] ); + } - } + this._invokeAll( function ( ext ) { - this._invokeAll( function ( ext ) { + ext.afterParse && ext.afterParse( input ); - ext.afterParse && ext.afterParse( input ); + } ); - } ); + } - } + _invokeAll( func ) { - _invokeAll( func ) { + for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { + func( this.plugins[ i ] ); - func( this.plugins[ i ] ); + } } } -} - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ -class GLTFLightExtension { + /** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ + class GLTFLightExtension { - constructor( writer ) { + constructor( writer ) { - this.writer = writer; - this.name = 'KHR_lights_punctual'; + this.writer = writer; + this.name = 'KHR_lights_punctual'; - } + } - writeNode( light, nodeDef ) { + writeNode( light, nodeDef ) { - if ( ! light.isLight ) return; + if ( ! light.isLight ) return; - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { + if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); + return; - } + } - const writer = this.writer; - const json = writer.json; - const extensionsUsed = writer.extensionsUsed; + const writer = this.writer; + const json = writer.json; + const extensionsUsed = writer.extensionsUsed; - const lightDef = {}; + const lightDef = {}; - if ( light.name ) lightDef.name = light.name; + if ( light.name ) lightDef.name = light.name; - lightDef.color = light.color.toArray(); + lightDef.color = light.color.toArray(); - lightDef.intensity = light.intensity; + lightDef.intensity = light.intensity; - if ( light.isDirectionalLight ) { + if ( light.isDirectionalLight ) { - lightDef.type = 'directional'; + lightDef.type = 'directional'; - } else if ( light.isPointLight ) { + } else if ( light.isPointLight ) { - lightDef.type = 'point'; + lightDef.type = 'point'; - if ( light.distance > 0 ) lightDef.range = light.distance; + if ( light.distance > 0 ) lightDef.range = light.distance; - } else if ( light.isSpotLight ) { + } else if ( light.isSpotLight ) { - lightDef.type = 'spot'; + lightDef.type = 'spot'; - if ( light.distance > 0 ) lightDef.range = light.distance; + if ( light.distance > 0 ) lightDef.range = light.distance; - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; - lightDef.spot.outerConeAngle = light.angle; + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; + lightDef.spot.outerConeAngle = light.angle; - } + } - if ( light.decay !== undefined && light.decay !== 2 ) { + if ( light.decay !== undefined && light.decay !== 2 ) { - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.' ); - } + } - if ( light.target + if ( light.target && ( light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== - 1 ) ) { - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + 'make light.target a child of the light with position 0,0,-1.' ); - } + } - if ( ! extensionsUsed[ this.name ] ) { + if ( ! extensionsUsed[ this.name ] ) { - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { lights: [] }; - extensionsUsed[ this.name ] = true; + json.extensions = json.extensions || {}; + json.extensions[ this.name ] = { lights: [] }; + extensionsUsed[ this.name ] = true; - } + } - const lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); + const lights = json.extensions[ this.name ].lights; + lights.push( lightDef ); - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + + } } -} + /** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ + class GLTFMaterialsUnlitExtension { -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ -class GLTFMaterialsUnlitExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_unlit'; - this.writer = writer; - this.name = 'KHR_materials_unlit'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshBasicMaterial ) return; - if ( ! material.isMeshBasicMaterial ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = {}; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + materialDef.pbrMetallicRoughness.metallicFactor = 0.0; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; + } } -} + /** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ + class GLTFMaterialsClearcoatExtension { -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - */ -class GLTFMaterialsClearcoatExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_clearcoat'; - this.writer = writer; - this.name = 'KHR_materials_clearcoat'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; - if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + extensionDef.clearcoatFactor = material.clearcoat; - extensionDef.clearcoatFactor = material.clearcoat; + if ( material.clearcoatMap ) { - if ( material.clearcoatMap ) { + const clearcoatMapDef = { + index: writer.processTexture( material.clearcoatMap ), + texCoord: material.clearcoatMap.channel + }; + writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); + extensionDef.clearcoatTexture = clearcoatMapDef; - const clearcoatMapDef = { - index: writer.processTexture( material.clearcoatMap ), - texCoord: material.clearcoatMap.channel - }; - writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); - extensionDef.clearcoatTexture = clearcoatMapDef; + } - } + extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; - extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; + if ( material.clearcoatRoughnessMap ) { - if ( material.clearcoatRoughnessMap ) { + const clearcoatRoughnessMapDef = { + index: writer.processTexture( material.clearcoatRoughnessMap ), + texCoord: material.clearcoatRoughnessMap.channel + }; + writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); + extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; - const clearcoatRoughnessMapDef = { - index: writer.processTexture( material.clearcoatRoughnessMap ), - texCoord: material.clearcoatRoughnessMap.channel - }; - writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); - extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; + } - } + if ( material.clearcoatNormalMap ) { - if ( material.clearcoatNormalMap ) { + const clearcoatNormalMapDef = { + index: writer.processTexture( material.clearcoatNormalMap ), + texCoord: material.clearcoatNormalMap.channel + }; + writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); + extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; - const clearcoatNormalMapDef = { - index: writer.processTexture( material.clearcoatNormalMap ), - texCoord: material.clearcoatNormalMap.channel - }; - writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); - extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; + } - } + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ + class GLTFMaterialsIridescenceExtension { -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - */ -class GLTFMaterialsIridescenceExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_iridescence'; - this.writer = writer; - this.name = 'KHR_materials_iridescence'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; - if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + extensionDef.iridescenceFactor = material.iridescence; - extensionDef.iridescenceFactor = material.iridescence; + if ( material.iridescenceMap ) { - if ( material.iridescenceMap ) { + const iridescenceMapDef = { + index: writer.processTexture( material.iridescenceMap ), + texCoord: material.iridescenceMap.channel + }; + writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); + extensionDef.iridescenceTexture = iridescenceMapDef; - const iridescenceMapDef = { - index: writer.processTexture( material.iridescenceMap ), - texCoord: material.iridescenceMap.channel - }; - writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); - extensionDef.iridescenceTexture = iridescenceMapDef; + } - } + extensionDef.iridescenceIor = material.iridescenceIOR; + extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; + extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; - extensionDef.iridescenceIor = material.iridescenceIOR; - extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; - extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; + if ( material.iridescenceThicknessMap ) { - if ( material.iridescenceThicknessMap ) { + const iridescenceThicknessMapDef = { + index: writer.processTexture( material.iridescenceThicknessMap ), + texCoord: material.iridescenceThicknessMap.channel + }; + writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); + extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; - const iridescenceThicknessMapDef = { - index: writer.processTexture( material.iridescenceThicknessMap ), - texCoord: material.iridescenceThicknessMap.channel - }; - writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); - extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; + } - } + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + */ + class GLTFMaterialsTransmissionExtension { -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - */ -class GLTFMaterialsTransmissionExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_transmission'; - this.writer = writer; - this.name = 'KHR_materials_transmission'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + extensionDef.transmissionFactor = material.transmission; - extensionDef.transmissionFactor = material.transmission; + if ( material.transmissionMap ) { - if ( material.transmissionMap ) { + const transmissionMapDef = { + index: writer.processTexture( material.transmissionMap ), + texCoord: material.transmissionMap.channel + }; + writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); + extensionDef.transmissionTexture = transmissionMapDef; - const transmissionMapDef = { - index: writer.processTexture( material.transmissionMap ), - texCoord: material.transmissionMap.channel - }; - writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); - extensionDef.transmissionTexture = transmissionMapDef; + } - } + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ + class GLTFMaterialsVolumeExtension { -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ -class GLTFMaterialsVolumeExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_volume'; - this.writer = writer; - this.name = 'KHR_materials_volume'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + extensionDef.thicknessFactor = material.thickness; - extensionDef.thicknessFactor = material.thickness; + if ( material.thicknessMap ) { - if ( material.thicknessMap ) { + const thicknessMapDef = { + index: writer.processTexture( material.thicknessMap ), + texCoord: material.thicknessMap.channel + }; + writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); + extensionDef.thicknessTexture = thicknessMapDef; - const thicknessMapDef = { - index: writer.processTexture( material.thicknessMap ), - texCoord: material.thicknessMap.channel - }; - writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); - extensionDef.thicknessTexture = thicknessMapDef; + } - } + extensionDef.attenuationDistance = material.attenuationDistance; + extensionDef.attenuationColor = material.attenuationColor.toArray(); - extensionDef.attenuationDistance = material.attenuationDistance; - extensionDef.attenuationColor = material.attenuationColor.toArray(); + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Materials ior Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + */ + class GLTFMaterialsIorExtension { -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - */ -class GLTFMaterialsIorExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_ior'; - this.writer = writer; - this.name = 'KHR_materials_ior'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; - if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + extensionDef.ior = material.ior; - extensionDef.ior = material.ior; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - */ -class GLTFMaterialsSpecularExtension { + /** + * Materials specular Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + */ + class GLTFMaterialsSpecularExtension { - constructor( writer ) { + constructor( writer ) { - this.writer = writer; - this.name = 'KHR_materials_specular'; + this.writer = writer; + this.name = 'KHR_materials_specular'; - } + } - writeMaterial( material, materialDef ) { + writeMaterial( material, materialDef ) { - if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && + if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const extensionDef = {}; + const extensionDef = {}; - if ( material.specularIntensityMap ) { + if ( material.specularIntensityMap ) { - const specularIntensityMapDef = { - index: writer.processTexture( material.specularIntensityMap ), - texCoord: material.specularIntensityMap.channel - }; - writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); - extensionDef.specularTexture = specularIntensityMapDef; + const specularIntensityMapDef = { + index: writer.processTexture( material.specularIntensityMap ), + texCoord: material.specularIntensityMap.channel + }; + writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); + extensionDef.specularTexture = specularIntensityMapDef; - } + } - if ( material.specularColorMap ) { + if ( material.specularColorMap ) { - const specularColorMapDef = { - index: writer.processTexture( material.specularColorMap ), - texCoord: material.specularColorMap.channel - }; - writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); - extensionDef.specularColorTexture = specularColorMapDef; + const specularColorMapDef = { + index: writer.processTexture( material.specularColorMap ), + texCoord: material.specularColorMap.channel + }; + writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); + extensionDef.specularColorTexture = specularColorMapDef; - } + } - extensionDef.specularFactor = material.specularIntensity; - extensionDef.specularColorFactor = material.specularColor.toArray(); + extensionDef.specularFactor = material.specularIntensity; + extensionDef.specularColorFactor = material.specularColor.toArray(); - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Sheen Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + */ + class GLTFMaterialsSheenExtension { -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - */ -class GLTFMaterialsSheenExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_sheen'; - this.writer = writer; - this.name = 'KHR_materials_sheen'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; - if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + if ( material.sheenRoughnessMap ) { - if ( material.sheenRoughnessMap ) { + const sheenRoughnessMapDef = { + index: writer.processTexture( material.sheenRoughnessMap ), + texCoord: material.sheenRoughnessMap.channel + }; + writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); + extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; - const sheenRoughnessMapDef = { - index: writer.processTexture( material.sheenRoughnessMap ), - texCoord: material.sheenRoughnessMap.channel - }; - writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); - extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; + } - } + if ( material.sheenColorMap ) { - if ( material.sheenColorMap ) { + const sheenColorMapDef = { + index: writer.processTexture( material.sheenColorMap ), + texCoord: material.sheenColorMap.channel + }; + writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); + extensionDef.sheenColorTexture = sheenColorMapDef; - const sheenColorMapDef = { - index: writer.processTexture( material.sheenColorMap ), - texCoord: material.sheenColorMap.channel - }; - writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); - extensionDef.sheenColorTexture = sheenColorMapDef; + } - } + extensionDef.sheenRoughnessFactor = material.sheenRoughness; + extensionDef.sheenColorFactor = material.sheenColor.toArray(); - extensionDef.sheenRoughnessFactor = material.sheenRoughness; - extensionDef.sheenColorFactor = material.sheenColor.toArray(); + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Anisotropy Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy + */ + class GLTFMaterialsAnisotropyExtension { -/** - * Anisotropy Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy - */ -class GLTFMaterialsAnisotropyExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_anisotropy'; - this.writer = writer; - this.name = 'KHR_materials_anisotropy'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; - if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + if ( material.anisotropyMap ) { - if ( material.anisotropyMap ) { + const anisotropyMapDef = { index: writer.processTexture( material.anisotropyMap ) }; + writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); + extensionDef.anisotropyTexture = anisotropyMapDef; - const anisotropyMapDef = { index: writer.processTexture( material.anisotropyMap ) }; - writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); - extensionDef.anisotropyTexture = anisotropyMapDef; + } - } + extensionDef.anisotropyStrength = material.anisotropy; + extensionDef.anisotropyRotation = material.anisotropyRotation; - extensionDef.anisotropyStrength = material.anisotropy; - extensionDef.anisotropyRotation = material.anisotropyRotation; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * Materials Emissive Strength Extension + * + * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md + */ + class GLTFMaterialsEmissiveStrengthExtension { -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - */ -class GLTFMaterialsEmissiveStrengthExtension { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'KHR_materials_emissive_strength'; - this.writer = writer; - this.name = 'KHR_materials_emissive_strength'; + } - } + writeMaterial( material, materialDef ) { - writeMaterial( material, materialDef ) { + if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; - if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - const extensionDef = {}; + extensionDef.emissiveStrength = material.emissiveIntensity; - extensionDef.emissiveStrength = material.emissiveIntensity; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - extensionsUsed[ this.name ] = true; + } } -} + /** + * GPU Instancing Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing + */ + class GLTFMeshGpuInstancing { -/** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - */ -class GLTFMeshGpuInstancing { + constructor( writer ) { - constructor( writer ) { + this.writer = writer; + this.name = 'EXT_mesh_gpu_instancing'; - this.writer = writer; - this.name = 'EXT_mesh_gpu_instancing'; + } - } + writeNode( object, nodeDef ) { - writeNode( object, nodeDef ) { + if ( ! object.isInstancedMesh ) return; - if ( ! object.isInstancedMesh ) return; + const writer = this.writer; - const writer = this.writer; + const mesh = object; - const mesh = object; + const translationAttr = new Float32Array( mesh.count * 3 ); + const rotationAttr = new Float32Array( mesh.count * 4 ); + const scaleAttr = new Float32Array( mesh.count * 3 ); - const translationAttr = new Float32Array( mesh.count * 3 ); - const rotationAttr = new Float32Array( mesh.count * 4 ); - const scaleAttr = new Float32Array( mesh.count * 3 ); + const matrix = new Matrix4(); + const position = new Vector3(); + const quaternion = new Quaternion(); + const scale = new Vector3(); - const matrix = new Matrix4(); - const position = new Vector3(); - const quaternion = new Quaternion(); - const scale = new Vector3(); + for ( let i = 0; i < mesh.count; i ++ ) { - for ( let i = 0; i < mesh.count; i ++ ) { + mesh.getMatrixAt( i, matrix ); + matrix.decompose( position, quaternion, scale ); - mesh.getMatrixAt( i, matrix ); - matrix.decompose( position, quaternion, scale ); + position.toArray( translationAttr, i * 3 ); + quaternion.toArray( rotationAttr, i * 4 ); + scale.toArray( scaleAttr, i * 3 ); - position.toArray( translationAttr, i * 3 ); - quaternion.toArray( rotationAttr, i * 4 ); - scale.toArray( scaleAttr, i * 3 ); + } - } + const attributes = { + TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), + ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), + SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), + }; - const attributes = { - TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), - ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), - SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), - }; + if ( mesh.instanceColor ) + attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); - if ( mesh.instanceColor ) - attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { attributes }; - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { attributes }; + writer.extensionsUsed[ this.name ] = true; + writer.extensionsRequired[ this.name ] = true; - writer.extensionsUsed[ this.name ] = true; - writer.extensionsRequired[ this.name ] = true; + } } -} + /** + * Static utility functions + */ + GLTFExporter.Utils = { -/** - * Static utility functions - */ -const Utils = { + insertKeyframe: function ( track, time ) { - insertKeyframe: function ( track, time ) { + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + let index; - let index; + if ( track.times.length === 0 ) { - if ( track.times.length === 0 ) { + times[ 0 ] = time; - times[ 0 ] = time; + for ( let i = 0; i < valueSize; i ++ ) { - for ( let i = 0; i < valueSize; i ++ ) { + values[ i ] = 0; - values[ i ] = 0; + } - } + index = 0; - index = 0; + } else if ( time < track.times[ 0 ] ) { - } else if ( time < track.times[ 0 ] ) { + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + times[ 0 ] = time; + times.set( track.times, 1 ); - times[ 0 ] = time; - times.set( track.times, 1 ); + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); + index = 0; - index = 0; + } else if ( time > track.times[ track.times.length - 1 ] ) { - } else if ( time > track.times[ track.times.length - 1 ] ) { + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + return track.times.length - 1; - return track.times.length - 1; + } - } + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); + index = times.length - 1; - index = times.length - 1; + } else { - } else { + for ( let i = 0; i < track.times.length; i ++ ) { - for ( let i = 0; i < track.times.length; i ++ ) { + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + index = i + 1; - index = i + 1; + break; - break; + } } } - } - - track.times = times; - track.values = values; + track.times = times; + track.values = values; - return index; + return index; - }, + }, - mergeMorphTargetTracks: function ( clip, root ) { + mergeMorphTargetTracks: function ( clip, root ) { - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; - for ( let i = 0; i < sourceTracks.length; ++ i ) { + for ( let i = 0; i < sourceTracks.length; ++ i ) { - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; - } + } - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); - } + } - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); - } + } - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - if ( targetIndex === undefined ) { + if ( targetIndex === undefined ) { - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - } + } - let mergedTrack; + let mergedTrack; - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - mergedTrack = sourceTrack.clone(); + mergedTrack = sourceTrack.clone(); - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; - } + } - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); - continue; + continue; - } + } + + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + } - } + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + } } - } + clip.tracks = tracks; - clip.tracks = tracks; + return clip; - return clip; + } - } + }; -}; + return GLTFExporter; -/* @__PURE__ */ Object.assign( GLTFExporter, { Utils } ); +} )(); export { GLTFExporter }; diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index 66cf4f057e27a3..4869ca70fd3609 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -1,6 +1,6 @@ -var UTIF = {}; +const UTIF = /* @__PURE__ */ ( () => { -/* @__PURE__ */ ( () => { +var UTIF = {}; // Following lines add a JPEG decoder to UTIF.JpegDecoder (function(){"use strict";var W=function a1(){function W(p){this.message="JPEG error: "+p}W.prototype=new Error;W.prototype.name="JpegError";W.constructor=W;return W}(),ak=function ag(){var p=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),t=4017,ac=799,ah=3406,ao=2276,ar=1567,ai=3784,s=5793,ad=2896;function ak(Q){if(Q==null)Q={};if(Q.w==null)Q.w=-1;this.V=Q.n;this.N=Q.w}function a5(Q,h){var f=0,G=[],n,E,a=16,F;while(a>0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n { +const LineMaterial = /* @__PURE__ */ ( () => { UniformsLib.line = { @@ -435,205 +435,207 @@ import { ` }; -} )(); + class LineMaterial extends ShaderMaterial { -class LineMaterial extends ShaderMaterial { + constructor( parameters ) { - constructor( parameters ) { + super( { - super( { + type: 'LineMaterial', - type: 'LineMaterial', + uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), - uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), + vertexShader: ShaderLib[ 'line' ].vertexShader, + fragmentShader: ShaderLib[ 'line' ].fragmentShader, - vertexShader: ShaderLib[ 'line' ].vertexShader, - fragmentShader: ShaderLib[ 'line' ].fragmentShader, + clipping: true // required for clipping support - clipping: true // required for clipping support + } ); - } ); + this.isLineMaterial = true; - this.isLineMaterial = true; + this.setValues( parameters ); - this.setValues( parameters ); + } - } + get color() { - get color() { + return this.uniforms.diffuse.value; - return this.uniforms.diffuse.value; + } - } + set color( value ) { - set color( value ) { + this.uniforms.diffuse.value = value; - this.uniforms.diffuse.value = value; + } - } + get worldUnits() { - get worldUnits() { + return 'WORLD_UNITS' in this.defines; - return 'WORLD_UNITS' in this.defines; + } - } + set worldUnits( value ) { - set worldUnits( value ) { + if ( value === true ) { - if ( value === true ) { + this.defines.WORLD_UNITS = ''; - this.defines.WORLD_UNITS = ''; + } else { - } else { + delete this.defines.WORLD_UNITS; - delete this.defines.WORLD_UNITS; + } } - } + get linewidth() { - get linewidth() { + return this.uniforms.linewidth.value; - return this.uniforms.linewidth.value; + } - } + set linewidth( value ) { - set linewidth( value ) { + if ( ! this.uniforms.linewidth ) return; + this.uniforms.linewidth.value = value; - if ( ! this.uniforms.linewidth ) return; - this.uniforms.linewidth.value = value; + } - } + get dashed() { - get dashed() { + return 'USE_DASH' in this.defines; - return 'USE_DASH' in this.defines; + } - } + set dashed( value ) { - set dashed( value ) { + if ( ( value === true ) !== this.dashed ) { - if ( ( value === true ) !== this.dashed ) { + this.needsUpdate = true; - this.needsUpdate = true; + } - } + if ( value === true ) { - if ( value === true ) { + this.defines.USE_DASH = ''; - this.defines.USE_DASH = ''; + } else { - } else { + delete this.defines.USE_DASH; - delete this.defines.USE_DASH; + } } - } + get dashScale() { - get dashScale() { + return this.uniforms.dashScale.value; - return this.uniforms.dashScale.value; + } - } + set dashScale( value ) { - set dashScale( value ) { + this.uniforms.dashScale.value = value; - this.uniforms.dashScale.value = value; + } - } + get dashSize() { - get dashSize() { + return this.uniforms.dashSize.value; - return this.uniforms.dashSize.value; + } - } + set dashSize( value ) { - set dashSize( value ) { + this.uniforms.dashSize.value = value; - this.uniforms.dashSize.value = value; + } - } + get dashOffset() { - get dashOffset() { + return this.uniforms.dashOffset.value; - return this.uniforms.dashOffset.value; + } - } + set dashOffset( value ) { - set dashOffset( value ) { + this.uniforms.dashOffset.value = value; - this.uniforms.dashOffset.value = value; + } - } + get gapSize() { - get gapSize() { + return this.uniforms.gapSize.value; - return this.uniforms.gapSize.value; + } - } + set gapSize( value ) { - set gapSize( value ) { + this.uniforms.gapSize.value = value; - this.uniforms.gapSize.value = value; + } - } + get opacity() { - get opacity() { + return this.uniforms.opacity.value; - return this.uniforms.opacity.value; + } - } + set opacity( value ) { - set opacity( value ) { + if ( ! this.uniforms ) return; + this.uniforms.opacity.value = value; - if ( ! this.uniforms ) return; - this.uniforms.opacity.value = value; + } - } + get resolution() { - get resolution() { + return this.uniforms.resolution.value; - return this.uniforms.resolution.value; + } - } + set resolution( value ) { - set resolution( value ) { + this.uniforms.resolution.value.copy( value ); - this.uniforms.resolution.value.copy( value ); + } - } + get alphaToCoverage() { - get alphaToCoverage() { + return 'USE_ALPHA_TO_COVERAGE' in this.defines; - return 'USE_ALPHA_TO_COVERAGE' in this.defines; + } - } + set alphaToCoverage( value ) { - set alphaToCoverage( value ) { + if ( ! this.defines ) return; - if ( ! this.defines ) return; + if ( ( value === true ) !== this.alphaToCoverage ) { - if ( ( value === true ) !== this.alphaToCoverage ) { + this.needsUpdate = true; - this.needsUpdate = true; + } - } + if ( value === true ) { - if ( value === true ) { + this.defines.USE_ALPHA_TO_COVERAGE = ''; + this.extensions.derivatives = true; - this.defines.USE_ALPHA_TO_COVERAGE = ''; - this.extensions.derivatives = true; + } else { - } else { + delete this.defines.USE_ALPHA_TO_COVERAGE; + this.extensions.derivatives = false; - delete this.defines.USE_ALPHA_TO_COVERAGE; - this.extensions.derivatives = false; + } } } -} + return LineMaterial; + +} )(); export { LineMaterial }; diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index 9fb6f7ec9089ff..aed2600ae5fef4 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -71,856 +71,853 @@ import { } from '../libs/ktx-parse.module.js'; import { ZSTDDecoder } from '../libs/zstddec.module.js'; -const _taskCache = new WeakMap(); +const KTX2Loader = /* @__PURE__ */ ( () => { -let _activeLoaders = 0; + const _taskCache = new WeakMap(); -let _zstd; + let _activeLoaders = 0; -class KTX2Loader extends Loader { + let _zstd; - constructor( manager ) { + class KTX2Loader extends Loader { - super( manager ); + constructor( manager ) { - this.transcoderPath = ''; - this.transcoderBinary = null; - this.transcoderPending = null; + super( manager ); - this.workerPool = new WorkerPool(); - this.workerSourceURL = ''; - this.workerConfig = null; + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; - if ( typeof MSC_TRANSCODER !== 'undefined' ) { + this.workerPool = new WorkerPool(); + this.workerSourceURL = ''; + this.workerConfig = null; - console.warn( + if ( typeof MSC_TRANSCODER !== 'undefined' ) { - 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + console.warn( + + 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' - ); + ); - } + } - } + } - setTranscoderPath( path ) { + setTranscoderPath( path ) { - this.transcoderPath = path; + this.transcoderPath = path; - return this; + return this; - } + } - setWorkerLimit( num ) { + setWorkerLimit( num ) { - this.workerPool.setWorkerLimit( num ); + this.workerPool.setWorkerLimit( num ); - return this; + return this; - } + } - detectSupport( renderer ) { + detectSupport( renderer ) { - if ( renderer.isWebGPURenderer === true ) { + if ( renderer.isWebGPURenderer === true ) { - this.workerConfig = { - astcSupported: renderer.hasFeature( 'texture-compression-astc' ), - etc1Supported: false, - etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), - dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), - bptcSupported: false, - pvrtcSupported: false - }; + this.workerConfig = { + astcSupported: renderer.hasFeature( 'texture-compression-astc' ), + etc1Supported: false, + etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), + dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), + bptcSupported: false, + pvrtcSupported: false + }; - } else { + } else { - this.workerConfig = { - astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), - etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), - etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), - dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), - bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), - pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) - }; + }; - if ( renderer.capabilities.isWebGL2 ) { + if ( renderer.capabilities.isWebGL2 ) { - // https://github.com/mrdoob/three.js/pull/22928 - this.workerConfig.etc1Supported = false; + // https://github.com/mrdoob/three.js/pull/22928 + this.workerConfig.etc1Supported = false; + + } } - } + return this; - return this; + } - } + init() { - init() { + if ( ! this.transcoderPending ) { - if ( ! this.transcoderPending ) { + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); - // Load transcoder wrapper. - const jsLoader = new FileLoader( this.manager ); - jsLoader.setPath( this.transcoderPath ); - jsLoader.setWithCredentials( this.withCredentials ); - const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); - // Load transcoder WASM binary. - const binaryLoader = new FileLoader( this.manager ); - binaryLoader.setPath( this.transcoderPath ); - binaryLoader.setResponseType( 'arraybuffer' ); - binaryLoader.setWithCredentials( this.withCredentials ); - const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { - this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) - .then( ( [ jsContent, binaryContent ] ) => { + const fn = KTX2Loader.BasisWorker.toString(); - const fn = KTX2Loader.BasisWorker.toString(); + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); - const body = [ - '/* constants */', - 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), - 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), - 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), - '/* basis_transcoder.js */', - jsContent, - '/* worker */', - fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) - ].join( '\n' ); + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; - this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); - this.transcoderBinary = binaryContent; + this.workerPool.setWorkerCreator( () => { - this.workerPool.setWorkerCreator( () => { + const worker = new Worker( this.workerSourceURL ); + const transcoderBinary = this.transcoderBinary.slice( 0 ); - const worker = new Worker( this.workerSourceURL ); - const transcoderBinary = this.transcoderBinary.slice( 0 ); + worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); - worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); + return worker; - return worker; + } ); } ); - } ); + if ( _activeLoaders > 0 ) { - if ( _activeLoaders > 0 ) { + // Each instance loads a transcoder and allocates workers, increasing network and memory cost. - // Each instance loads a transcoder and allocates workers, increasing network and memory cost. + console.warn( - console.warn( - - 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' + 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' - ); + ); - } + } - _activeLoaders ++; + _activeLoaders ++; - } + } - return this.transcoderPending; + return this.transcoderPending; - } + } - load( url, onLoad, onProgress, onError ) { + load( url, onLoad, onProgress, onError ) { - if ( this.workerConfig === null ) { + if ( this.workerConfig === null ) { - throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); + throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); - } + } - const loader = new FileLoader( this.manager ); + const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setWithCredentials( this.withCredentials ); + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); - loader.load( url, ( buffer ) => { + loader.load( url, ( buffer ) => { - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache.has( buffer ) ) { + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { - const cachedTask = _taskCache.get( buffer ); + const cachedTask = _taskCache.get( buffer ); - return cachedTask.promise.then( onLoad ).catch( onError ); + return cachedTask.promise.then( onLoad ).catch( onError ); - } + } - this._createTexture( buffer ) - .then( ( texture ) => onLoad ? onLoad( texture ) : null ) - .catch( onError ); + this._createTexture( buffer ) + .then( ( texture ) => onLoad ? onLoad( texture ) : null ) + .catch( onError ); - }, onProgress, onError ); + }, onProgress, onError ); - } + } - _createTextureFrom( transcodeResult, container ) { + _createTextureFrom( transcodeResult, container ) { - const { faces, width, height, format, type, error, dfdFlags } = transcodeResult; + const { faces, width, height, format, type, error, dfdFlags } = transcodeResult; - if ( type === 'error' ) return Promise.reject( error ); + if ( type === 'error' ) return Promise.reject( error ); - let texture; + let texture; - if ( container.faceCount === 6 ) { + if ( container.faceCount === 6 ) { - texture = new CompressedCubeTexture( faces, format, UnsignedByteType ); + texture = new CompressedCubeTexture( faces, format, UnsignedByteType ); - } else { + } else { - const mipmaps = faces[ 0 ].mipmaps; + const mipmaps = faces[ 0 ].mipmaps; - texture = container.layerCount > 1 - ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) - : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + texture = container.layerCount > 1 + ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) + : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); - } + } - texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; - texture.magFilter = LinearFilter; - texture.generateMipmaps = false; + texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; - texture.needsUpdate = true; - texture.colorSpace = parseColorSpace( container ); - texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); + texture.needsUpdate = true; + texture.colorSpace = parseColorSpace( container ); + texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); - return texture; + return texture; - } + } - /** + /** * @param {ArrayBuffer} buffer * @param {object?} config * @return {Promise} */ - async _createTexture( buffer, config = {} ) { + async _createTexture( buffer, config = {} ) { - const container = read( new Uint8Array( buffer ) ); + const container = read( new Uint8Array( buffer ) ); - if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { + if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { - return createRawTexture( container ); + return createRawTexture( container ); - } + } - // - const taskConfig = config; - const texturePending = this.init().then( () => { + // + const taskConfig = config; + const texturePending = this.init().then( () => { - return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] ); + return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] ); - } ).then( ( e ) => this._createTextureFrom( e.data, container ) ); + } ).then( ( e ) => this._createTextureFrom( e.data, container ) ); - // Cache the task result. - _taskCache.set( buffer, { promise: texturePending } ); + // Cache the task result. + _taskCache.set( buffer, { promise: texturePending } ); - return texturePending; + return texturePending; - } + } - dispose() { + dispose() { - this.workerPool.dispose(); - if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); + this.workerPool.dispose(); + if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); - _activeLoaders --; + _activeLoaders --; - return this; + return this; + + } } -} + /* CONSTANTS */ -/* CONSTANTS */ + KTX2Loader.BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, + }; -const BasisFormat = { - ETC1S: 0, - UASTC_4x4: 1, -}; + KTX2Loader.TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, + }; -const TranscoderFormat = { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, -}; + KTX2Loader.EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, + }; -const EngineFormat = { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, -}; + /* WEB WORKER */ -/* WEB WORKER */ + KTX2Loader.BasisWorker = function () { -const BasisWorker = function () { + let config; + let transcoderPending; + let BasisModule; - let config; - let transcoderPending; - let BasisModule; + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef - const EngineFormat = _EngineFormat; // eslint-disable-line no-undef - const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef - const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + self.addEventListener( 'message', function ( e ) { - self.addEventListener( 'message', function ( e ) { + const message = e.data; - const message = e.data; + switch ( message.type ) { - switch ( message.type ) { + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; - case 'init': - config = message.config; - init( message.transcoderBinary ); - break; + case 'transcode': + transcoderPending.then( () => { - case 'transcode': - transcoderPending.then( () => { + try { - try { + const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); - const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); + self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); - self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); + } catch ( error ) { - } catch ( error ) { + console.error( error ); - console.error( error ); + self.postMessage( { type: 'error', id: message.id, error: error.message } ); - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + } - } + } ); + break; - } ); - break; + } - } + } ); - } ); + function init( wasmBinary ) { - function init( wasmBinary ) { + transcoderPending = new Promise( ( resolve ) => { - transcoderPending = new Promise( ( resolve ) => { + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef - BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; - BASIS( BasisModule ); // eslint-disable-line no-undef + } ).then( () => { - } ).then( () => { + BasisModule.initializeBasis(); - BasisModule.initializeBasis(); + if ( BasisModule.KTX2File === undefined ) { - if ( BasisModule.KTX2File === undefined ) { + console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); - console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); + } - } + } ); - } ); + } - } + function transcode( buffer ) { - function transcode( buffer ) { + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); - const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + function cleanup() { - function cleanup() { + ktx2File.close(); + ktx2File.delete(); - ktx2File.close(); - ktx2File.delete(); + } - } + if ( ! ktx2File.isValid() ) { - if ( ! ktx2File.isValid() ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); + } - } + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const layerCount = ktx2File.getLayers() || 1; + const levelCount = ktx2File.getLevels(); + const faceCount = ktx2File.getFaces(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdFlags = ktx2File.getDFDFlags(); - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; - const width = ktx2File.getWidth(); - const height = ktx2File.getHeight(); - const layerCount = ktx2File.getLayers() || 1; - const levelCount = ktx2File.getLevels(); - const faceCount = ktx2File.getFaces(); - const hasAlpha = ktx2File.getHasAlpha(); - const dfdFlags = ktx2File.getDFDFlags(); + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); - const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + if ( ! width || ! height || ! levelCount ) { - if ( ! width || ! height || ! levelCount ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid texture' ); - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid texture' ); + } - } + if ( ! ktx2File.startTranscoding() ) { - if ( ! ktx2File.startTranscoding() ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); - cleanup(); - throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); + } - } + const faces = []; + const buffers = []; - const faces = []; - const buffers = []; + for ( let face = 0; face < faceCount; face ++ ) { - for ( let face = 0; face < faceCount; face ++ ) { + const mipmaps = []; - const mipmaps = []; + for ( let mip = 0; mip < levelCount; mip ++ ) { - for ( let mip = 0; mip < levelCount; mip ++ ) { + const layerMips = []; - const layerMips = []; + let mipWidth, mipHeight; - let mipWidth, mipHeight; + for ( let layer = 0; layer < layerCount; layer ++ ) { - for ( let layer = 0; layer < layerCount; layer ++ ) { + const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); - const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { - if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); - console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + } - } + if ( levelCount > 1 ) { - if ( levelCount > 1 ) { + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; - mipWidth = levelInfo.origWidth; - mipHeight = levelInfo.origHeight; + } else { - } else { + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; - // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with - // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. - // See mrdoob/three.js#25908. - mipWidth = levelInfo.width; - mipHeight = levelInfo.height; + } - } + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); - const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); - const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + if ( ! status ) { - if ( ! status ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); - cleanup(); - throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + } + + layerMips.push( dst ); } - layerMips.push( dst ); + const mipData = concat( layerMips ); - } + mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); + buffers.push( mipData.buffer ); - const mipData = concat( layerMips ); + } - mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); - buffers.push( mipData.buffer ); + faces.push( { mipmaps, width, height, format: engineFormat } ); } - faces.push( { mipmaps, width, height, format: engineFormat } ); + cleanup(); + + return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; } - cleanup(); + // - return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityETC1S - b.priorityETC1S; - } + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - // + return a.priorityUASTC - b.priorityUASTC; - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [ BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], - engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], - engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], - engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], - engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1 ], - engineFormat: [ EngineFormat.RGB_ETC1_Format ], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], - engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ]; - - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityETC1S - b.priorityETC1S; - - } ); - const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityUASTC - b.priorityUASTC; - - } ); - - function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { - - let transcoderFormat; - let engineFormat; - - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; - - for ( let i = 0; i < options.length; i ++ ) { - - const opt = options[ i ]; - - if ( ! config[ opt.if ] ) continue; - if ( ! opt.basisFormat.includes( basisFormat ) ) continue; - if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; - if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; - - transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; - engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + } ); - return { transcoderFormat, engineFormat }; + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { - } + let transcoderFormat; + let engineFormat; - console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; - transcoderFormat = TranscoderFormat.RGBA32; - engineFormat = EngineFormat.RGBAFormat; + for ( let i = 0; i < options.length; i ++ ) { - return { transcoderFormat, engineFormat }; + const opt = options[ i ]; - } + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; - function isPowerOfTwo( value ) { + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; - if ( value <= 2 ) return true; + return { transcoderFormat, engineFormat }; - return ( value & ( value - 1 ) ) === 0 && value !== 0; + } - } + console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); - /** Concatenates N byte arrays. */ - function concat( arrays ) { + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; - if ( arrays.length === 1 ) return arrays[ 0 ]; + return { transcoderFormat, engineFormat }; + + } - let totalByteLength = 0; + function isPowerOfTwo( value ) { - for ( let i = 0; i < arrays.length; i ++ ) { + if ( value <= 2 ) return true; - const array = arrays[ i ]; - totalByteLength += array.byteLength; + return ( value & ( value - 1 ) ) === 0 && value !== 0; } - const result = new Uint8Array( totalByteLength ); + /** Concatenates N byte arrays. */ + function concat( arrays ) { - let byteOffset = 0; + if ( arrays.length === 1 ) return arrays[ 0 ]; - for ( let i = 0; i < arrays.length; i ++ ) { + let totalByteLength = 0; - const array = arrays[ i ]; - result.set( array, byteOffset ); + for ( let i = 0; i < arrays.length; i ++ ) { - byteOffset += array.byteLength; + const array = arrays[ i ]; + totalByteLength += array.byteLength; - } + } - return result; + const result = new Uint8Array( totalByteLength ); - } + let byteOffset = 0; -}; + for ( let i = 0; i < arrays.length; i ++ ) { -/* @__PURE__ */ Object.assign( KTX2Loader, { + const array = arrays[ i ]; + result.set( array, byteOffset ); - BasisFormat, - TranscoderFormat, - EngineFormat, - BasisWorker, + byteOffset += array.byteLength; -} ); + } -// -// Parsing for non-Basis textures. These textures are may have supercompression -// like Zstd, but they do not require transcoding. + return result; -const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] ); + } -const FORMAT_MAP = { + }; - [ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat, - [ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat, - [ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat, - [ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat, + // + // Parsing for non-Basis textures. These textures are may have supercompression + // like Zstd, but they do not require transcoding. - [ VK_FORMAT_R32G32_SFLOAT ]: RGFormat, - [ VK_FORMAT_R16G16_SFLOAT ]: RGFormat, - [ VK_FORMAT_R8G8_UNORM ]: RGFormat, - [ VK_FORMAT_R8G8_SRGB ]: RGFormat, + const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] ); - [ VK_FORMAT_R32_SFLOAT ]: RedFormat, - [ VK_FORMAT_R16_SFLOAT ]: RedFormat, - [ VK_FORMAT_R8_SRGB ]: RedFormat, - [ VK_FORMAT_R8_UNORM ]: RedFormat, + const FORMAT_MAP = { - [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format, - [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format, + [ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat, + [ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat, + [ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat, + [ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat, -}; + [ VK_FORMAT_R32G32_SFLOAT ]: RGFormat, + [ VK_FORMAT_R16G16_SFLOAT ]: RGFormat, + [ VK_FORMAT_R8G8_UNORM ]: RGFormat, + [ VK_FORMAT_R8G8_SRGB ]: RGFormat, -const TYPE_MAP = { + [ VK_FORMAT_R32_SFLOAT ]: RedFormat, + [ VK_FORMAT_R16_SFLOAT ]: RedFormat, + [ VK_FORMAT_R8_SRGB ]: RedFormat, + [ VK_FORMAT_R8_UNORM ]: RedFormat, - [ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType, - [ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType, - [ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType, - [ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType, + [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format, + [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format, - [ VK_FORMAT_R32G32_SFLOAT ]: FloatType, - [ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType, - [ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType, - [ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType, + }; - [ VK_FORMAT_R32_SFLOAT ]: FloatType, - [ VK_FORMAT_R16_SFLOAT ]: HalfFloatType, - [ VK_FORMAT_R8_SRGB ]: UnsignedByteType, - [ VK_FORMAT_R8_UNORM ]: UnsignedByteType, + const TYPE_MAP = { - [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType, - [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType, + [ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType, + [ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType, + [ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType, -}; + [ VK_FORMAT_R32G32_SFLOAT ]: FloatType, + [ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType, + [ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType, -async function createRawTexture( container ) { + [ VK_FORMAT_R32_SFLOAT ]: FloatType, + [ VK_FORMAT_R16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R8_SRGB ]: UnsignedByteType, + [ VK_FORMAT_R8_UNORM ]: UnsignedByteType, - const { vkFormat } = container; + [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType, + [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType, - if ( FORMAT_MAP[ vkFormat ] === undefined ) { + }; - throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' ); + async function createRawTexture( container ) { - } + const { vkFormat } = container; - // + if ( FORMAT_MAP[ vkFormat ] === undefined ) { - let zstd; + throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' ); - if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { + } - if ( ! _zstd ) { + // - _zstd = new Promise( async ( resolve ) => { + let zstd; - const zstd = new ZSTDDecoder(); - await zstd.init(); - resolve( zstd ); + if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { - } ); + if ( ! _zstd ) { - } + _zstd = new Promise( async ( resolve ) => { - zstd = await _zstd; + const zstd = new ZSTDDecoder(); + await zstd.init(); + resolve( zstd ); - } + } ); - // + } - const mipmaps = []; + zstd = await _zstd; + } - for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) { + // - const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex ); - const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex ); - const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0; + const mipmaps = []; - const level = container.levels[ levelIndex ]; - let levelData; + for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) { - if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) { + const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex ); + const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex ); + const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0; - levelData = level.levelData; + const level = container.levels[ levelIndex ]; - } else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { + let levelData; - levelData = zstd.decode( level.levelData, level.uncompressedByteLength ); + if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) { - } else { + levelData = level.levelData; - throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' ); + } else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { - } + levelData = zstd.decode( level.levelData, level.uncompressedByteLength ); - let data; + } else { - if ( TYPE_MAP[ vkFormat ] === FloatType ) { + throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' ); - data = new Float32Array( + } - levelData.buffer, - levelData.byteOffset, - levelData.byteLength / Float32Array.BYTES_PER_ELEMENT + let data; - ); + if ( TYPE_MAP[ vkFormat ] === FloatType ) { - } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { + data = new Float32Array( - data = new Uint16Array( + levelData.buffer, + levelData.byteOffset, + levelData.byteLength / Float32Array.BYTES_PER_ELEMENT - levelData.buffer, - levelData.byteOffset, - levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT + ); - ); + } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { - } else { + data = new Uint16Array( - data = levelData; + levelData.buffer, + levelData.byteOffset, + levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT - } + ); - mipmaps.push( { + } else { - data: data, - width: levelWidth, - height: levelHeight, - depth: levelDepth, + data = levelData; - } ); + } - } + mipmaps.push( { + + data: data, + width: levelWidth, + height: levelHeight, + depth: levelDepth, - let texture; + } ); - if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) { + } - texture = container.pixelDepth === 0 - ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) - : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); + let texture; - } else { + if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) { - if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' ); + texture = container.pixelDepth === 0 + ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) + : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); - texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight ); + } else { - } + if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' ); - texture.mipmaps = mipmaps; + texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight ); - texture.type = TYPE_MAP[ vkFormat ]; - texture.format = FORMAT_MAP[ vkFormat ]; - texture.colorSpace = parseColorSpace( container ); - texture.needsUpdate = true; + } - // + texture.mipmaps = mipmaps; - return Promise.resolve( texture ); + texture.type = TYPE_MAP[ vkFormat ]; + texture.format = FORMAT_MAP[ vkFormat ]; + texture.colorSpace = parseColorSpace( container ); + texture.needsUpdate = true; -} + // + + return Promise.resolve( texture ); + + } -function parseColorSpace( container ) { + function parseColorSpace( container ) { - const dfd = container.dataFormatDescriptor[ 0 ]; + const dfd = container.dataFormatDescriptor[ 0 ]; - if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_BT709 ) { + if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_BT709 ) { - return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : LinearSRGBColorSpace; + return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : LinearSRGBColorSpace; - } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_DISPLAYP3 ) { + } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_DISPLAYP3 ) { - return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? DisplayP3ColorSpace : LinearDisplayP3ColorSpace; + return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? DisplayP3ColorSpace : LinearDisplayP3ColorSpace; - } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_UNSPECIFIED ) { + } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_UNSPECIFIED ) { - return NoColorSpace; + return NoColorSpace; - } else { + } else { + + console.warn( `THREE.KTX2Loader: Unsupported color primaries, "${ dfd.colorPrimaries }"` ); + return NoColorSpace; - console.warn( `THREE.KTX2Loader: Unsupported color primaries, "${ dfd.colorPrimaries }"` ); - return NoColorSpace; + } } -} + return KTX2Loader; + +} )(); export { KTX2Loader }; diff --git a/examples/jsm/loaders/LogLuvLoader.js b/examples/jsm/loaders/LogLuvLoader.js index 60b6ae400e75e7..ddf6d3d737669f 100644 --- a/examples/jsm/loaders/LogLuvLoader.js +++ b/examples/jsm/loaders/LogLuvLoader.js @@ -44,566 +44,568 @@ class LogLuvLoader extends DataTextureLoader { // from https://github.com/photopea/UTIF.js (MIT License) -const UTIF = {}; +const UTIF = /* @__PURE__ */ ( () => { -/* @__PURE__ */ ( () => { + const UTIF = {}; -UTIF.decode = function ( buff, prm ) { + UTIF.decode = function ( buff, prm ) { - if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug - var data = new Uint8Array( buff ), offset = 0; + if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug + var data = new Uint8Array( buff ), offset = 0; - var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; - var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; - bin.readUshort( data, offset ); offset += 2; + var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; + var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; + bin.readUshort( data, offset ); offset += 2; - var ifdo = bin.readUint( data, offset ); - var ifds = []; - while ( true ) { + var ifdo = bin.readUint( data, offset ); + var ifds = []; + while ( true ) { - var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { + var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { - console.log( 'error in TIFF' ); break; + console.log( 'error in TIFF' ); break; - } + } - UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); + UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); - ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); - if ( ifdo == 0 ) break; + ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); + if ( ifdo == 0 ) break; - } + } - return ifds; + return ifds; -}; + }; -UTIF.decodeImage = function ( buff, img, ifds ) { + UTIF.decodeImage = function ( buff, img, ifds ) { - if ( img.data ) return; - var data = new Uint8Array( buff ); - var id = UTIF._binBE.readASCII( data, 0, 2 ); + if ( img.data ) return; + var data = new Uint8Array( buff ); + var id = UTIF._binBE.readASCII( data, 0, 2 ); - if ( img[ 't256' ] == null ) return; // No width => probably not an image - img.isLE = id == 'II'; - img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; - img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; + if ( img[ 't256' ] == null ) return; // No width => probably not an image + img.isLE = id == 'II'; + img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; + img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; - var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; - var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; - if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); - if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); + var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; + var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; + if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); + if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); - var bipp; // bits per pixel - if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; - else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { + var bipp; // bits per pixel + if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; + else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { - bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); + bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); - } + } - var bipl = Math.ceil( img.width * bipp / 8 ) * 8; - var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; - var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; + var bipl = Math.ceil( img.width * bipp / 8 ) * 8; + var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; + var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; - if ( img[ 't322' ] != null ) { + if ( img[ 't322' ] != null ) { - var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; - var tx = Math.floor( ( img.width + tw - 1 ) / tw ); - var ty = Math.floor( ( img.height + th - 1 ) / th ); - var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); - for ( var y = 0; y < ty; y ++ ) - for ( var x = 0; x < tx; x ++ ) { + var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; + var tx = Math.floor( ( img.width + tw - 1 ) / tw ); + var ty = Math.floor( ( img.height + th - 1 ) / th ); + var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); + for ( var y = 0; y < ty; y ++ ) + for ( var x = 0; x < tx; x ++ ) { - var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); - // Might be required for 7 too. Need to check - if ( cmpr == 6 ) bytes = tbuff; - else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); + var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); + // Might be required for 7 too. Need to check + if ( cmpr == 6 ) bytes = tbuff; + else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); - } + } - bilen = bytes.length * 8; + bilen = bytes.length * 8; - } else { + } else { - var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); - for ( var i = 0; i < soff.length; i ++ ) { + var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); + for ( var i = 0; i < soff.length; i ++ ) { - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); - bilen += bipl * rps; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); + bilen += bipl * rps; - } + } - bilen = Math.min( bilen, bytes.length * 8 ); + bilen = Math.min( bilen, bytes.length * 8 ); - } + } - img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); + img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); -}; + }; -UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { + UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { - //console.log("compression", cmpr); - //var time = Date.now(); - if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); - else console.log( 'Unsupported compression', cmpr ); + //console.log("compression", cmpr); + //var time = Date.now(); + if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); + else console.log( 'Unsupported compression', cmpr ); - //console.log(Date.now()-time); + //console.log(Date.now()-time); - var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); - var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); + var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); + var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); - // convert to Little Endian /* - if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG - for ( var y = 0; y < h; y ++ ) { + // convert to Little Endian /* + if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG + for ( var y = 0; y < h; y ++ ) { - //console.log("fixing endianity"); - var roff = toff + y * bpl; - for ( var x = 1; x < bpl; x += 2 ) { + //console.log("fixing endianity"); + var roff = toff + y * bpl; + for ( var x = 1; x < bpl; x += 2 ) { - var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; + var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; - } + } - } //*/ + } //*/ - if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { + if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { - for ( var y = 0; y < h; y ++ ) { + for ( var y = 0; y < h; y ++ ) { - var ntoff = toff + y * bpl; - if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { + var ntoff = toff + y * bpl; + if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { - var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); - tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; + var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); + tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; - } - else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + } + else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { - tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; - tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; - tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; + tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; + tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + + } + else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; } - else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; } - } + }; -}; + UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { -UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { + var w = img.width, qw = w * 4; + var io = 0, out = new Uint8Array( qw ); - var w = img.width, qw = w * 4; - var io = 0, out = new Uint8Array( qw ); + while ( io < len ) { - while ( io < len ) { + var oo = 0; + while ( oo < qw ) { - var oo = 0; - while ( oo < qw ) { + var c = data[ off + io ]; io ++; + if ( c < 128 ) { - var c = data[ off + io ]; io ++; - if ( c < 128 ) { + for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; - for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; + } else { - } else { + c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; - c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; + } } - } + for ( var x = 0; x < w; x ++ ) { - for ( var x = 0; x < w; x ++ ) { + tgt[ toff + 0 ] = out[ x ]; + tgt[ toff + 1 ] = out[ x + w ]; + tgt[ toff + 2 ] = out[ x + w * 2 ]; + tgt[ toff + 4 ] = out[ x + w * 3 ]; + toff += 6; - tgt[ toff + 0 ] = out[ x ]; - tgt[ toff + 1 ] = out[ x + w ]; - tgt[ toff + 2 ] = out[ x + w * 2 ]; - tgt[ toff + 4 ] = out[ x + w * 3 ]; - toff += 6; + } } - } + }; -}; + UTIF.tags = {}; + //UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; + // start at tag 250 + UTIF._types = function () { -UTIF.tags = {}; -//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; -// start at tag 250 -UTIF._types = function () { + var main = new Array( 250 ); main.fill( 0 ); + main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); + var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; + return { + basic: { + main: main, + rest: rest + }, + gps: { + main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], + rest: { 18: 2, 29: 2 } + } + }; - var main = new Array( 250 ); main.fill( 0 ); - main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); - var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; - return { - basic: { - main: main, - rest: rest - }, - gps: { - main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], - rest: { 18: 2, 29: 2 } - } - }; + }(); -}(); + UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { -UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { + var cnt = bin.readUshort( data, offset ); offset += 2; + var ifd = {}; - var cnt = bin.readUshort( data, offset ); offset += 2; - var ifd = {}; + if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); + for ( var i = 0; i < cnt; i ++ ) { - if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); - for ( var i = 0; i < cnt; i ++ ) { + var tag = bin.readUshort( data, offset ); offset += 2; + var type = bin.readUshort( data, offset ); offset += 2; + var num = bin.readUint( data, offset ); offset += 4; + var voff = bin.readUint( data, offset ); offset += 4; - var tag = bin.readUshort( data, offset ); offset += 2; - var type = bin.readUshort( data, offset ); offset += 2; - var num = bin.readUint( data, offset ); offset += 4; - var voff = bin.readUint( data, offset ); offset += 4; + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if ( type == 1 || type == 7 ) { - var arr = []; - //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; - if ( type == 1 || type == 7 ) { + arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); - arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); + } - } + if ( type == 2 ) { - if ( type == 2 ) { + var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); + if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); + else arr = new Uint8Array( data.buffer, o0, len ); - var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); - if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); - else arr = new Uint8Array( data.buffer, o0, len ); + } - } + if ( type == 3 ) { - if ( type == 3 ) { + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + } - } + if ( type == 4 + || type == 13 ) { - if ( type == 4 - || type == 13 ) { + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + } - } + if ( type == 5 || type == 10 ) { - if ( type == 5 || type == 10 ) { + var ri = type == 5 ? bin.readUint : bin.readInt; + for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); - var ri = type == 5 ? bin.readUint : bin.readInt; - for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); + } - } + if ( type == 8 ) { - if ( type == 8 ) { + for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + } - } + if ( type == 9 ) { - if ( type == 9 ) { + for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + } - } + if ( type == 11 ) { - if ( type == 11 ) { + for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); - for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); + } - } + if ( type == 12 ) { - if ( type == 12 ) { + for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); - for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); + } - } + if ( num != 0 && arr.length == 0 ) { - if ( num != 0 && arr.length == 0 ) { + console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; - console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; + } - } + if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); - if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); + ifd[ 't' + tag ] = arr; - ifd[ 't' + tag ] = arr; + if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { - if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { + var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; + var subfd = []; + for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); + if ( tag == 330 ) ifd.subIFD = subfd; + if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; + if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } + if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; + if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; - var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; - var subfd = []; - for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); - if ( tag == 330 ) ifd.subIFD = subfd; - if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; - if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } - if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; - if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; + } - } + if ( tag == 37500 && prm.parseMN ) { - if ( tag == 37500 && prm.parseMN ) { + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; + else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { - var mn = arr; - //console.log(bin.readASCII(mn,0,mn.length), mn); - if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; - else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { + var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); + ifd.makerNote = subsub[ 0 ]; - var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); - ifd.makerNote = subsub[ 0 ]; + } } } - } + ifds.push( ifd ); + if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); + return offset; - ifds.push( ifd ); - if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); - return offset; + }; -}; + UTIF.toRGBA = function ( out, type ) { -UTIF.toRGBA = function ( out, type ) { + const w = out.width, h = out.height, area = w * h, data = out.data; - const w = out.width, h = out.height, area = w * h, data = out.data; + let img; - let img; + switch ( type ) { - switch ( type ) { + case HalfFloatType: - case HalfFloatType: + img = new Uint16Array( area * 4 ); + break; - img = new Uint16Array( area * 4 ); - break; + case FloatType: - case FloatType: + img = new Float32Array( area * 4 ); + break; - img = new Float32Array( area * 4 ); - break; + default: + throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); - default: - throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); + } - } + let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; + const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; - let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; - const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; + if ( out[ 't262' ] == null && bps == 1 ) intp = 0; - if ( out[ 't262' ] == null && bps == 1 ) intp = 0; + if ( intp == 32845 ) { - if ( intp == 32845 ) { + for ( let y = 0; y < h; y ++ ) { - for ( let y = 0; y < h; y ++ ) { + for ( let x = 0; x < w; x ++ ) { - for ( let x = 0; x < w; x ++ ) { + const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; + let L = ( data[ si + 1 ] << 8 ) | data[ si ]; - const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; - let L = ( data[ si + 1 ] << 8 ) | data[ si ]; + L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); + const u = ( data[ si + 3 ] + 0.5 ) / 410; + const v = ( data[ si + 5 ] + 0.5 ) / 410; - L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); - const u = ( data[ si + 3 ] + 0.5 ) / 410; - const v = ( data[ si + 5 ] + 0.5 ) / 410; + // Luv to xyY + const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); + const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); + const bY = L; - // Luv to xyY - const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); - const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); - const bY = L; + // xyY to XYZ + const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; - // xyY to XYZ - const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; + // XYZ to linear RGB + const r = 2.690 * X - 1.276 * Y - 0.414 * Z; + const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; + const b = 0.061 * X - 0.224 * Y + 1.163 * Z; - // XYZ to linear RGB - const r = 2.690 * X - 1.276 * Y - 0.414 * Z; - const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; - const b = 0.061 * X - 0.224 * Y + 1.163 * Z; + if ( type === HalfFloatType ) { - if ( type === HalfFloatType ) { + img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); + img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); + img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); + img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); - img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); - img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); - img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); - img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); + } else { - } else { + img[ qi ] = r; + img[ qi + 1 ] = g; + img[ qi + 2 ] = b; + img[ qi + 3 ] = 1; - img[ qi ] = r; - img[ qi + 1 ] = g; - img[ qi + 2 ] = b; - img[ qi + 3 ] = 1; + } } } - } + } else { - } else { + throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); - throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); + } - } + return img; - return img; + }; -}; + UTIF._binBE = + { + nextZero: function ( data, o ) { -UTIF._binBE = -{ - nextZero: function ( data, o ) { + while ( data[ o ] != 0 ) o ++; return o; - while ( data[ o ] != 0 ) o ++; return o; + }, + readUshort: function ( buff, p ) { - }, - readUshort: function ( buff, p ) { + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + }, + readShort: function ( buff, p ) { - }, - readShort: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; + }, + readInt: function ( buff, p ) { - }, - readInt: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; + }, + readUint: function ( buff, p ) { - }, - readUint: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; + }, + readASCII: function ( buff, p, l ) { - }, - readASCII: function ( buff, p, l ) { + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + }, + readFloat: function ( buff, p ) { - }, - readFloat: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; + }, + readDouble: function ( buff, p ) { - }, - readDouble: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; + }, - }, + writeUshort: function ( buff, p, n ) { - writeUshort: function ( buff, p, n ) { + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + }, + writeInt: function ( buff, p, n ) { - }, - writeInt: function ( buff, p, n ) { + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; + }, + writeUint: function ( buff, p, n ) { - }, - writeUint: function ( buff, p, n ) { + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; + }, + writeASCII: function ( buff, p, s ) { - }, - writeASCII: function ( buff, p, s ) { + for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); - for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); + }, + writeDouble: function ( buff, p, n ) { - }, - writeDouble: function ( buff, p, n ) { + UTIF._binBE.fl64[ 0 ] = n; + for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; - UTIF._binBE.fl64[ 0 ] = n; - for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; + } + }; + UTIF._binBE.ui8 = new Uint8Array( 8 ); + UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); - } -}; -UTIF._binBE.ui8 = new Uint8Array( 8 ); -UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); + UTIF._binLE = + { + nextZero: UTIF._binBE.nextZero, + readUshort: function ( buff, p ) { -UTIF._binLE = -{ - nextZero: UTIF._binBE.nextZero, - readUshort: function ( buff, p ) { + return ( buff[ p + 1 ] << 8 ) | buff[ p ]; - return ( buff[ p + 1 ] << 8 ) | buff[ p ]; + }, + readShort: function ( buff, p ) { - }, - readShort: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; + }, + readInt: function ( buff, p ) { - }, - readInt: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; + }, + readUint: function ( buff, p ) { - }, - readUint: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; + }, + readASCII: UTIF._binBE.readASCII, + readFloat: function ( buff, p ) { - }, - readASCII: UTIF._binBE.readASCII, - readFloat: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; + }, + readDouble: function ( buff, p ) { - }, - readDouble: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; + }, - }, + writeUshort: function ( buff, p, n ) { - writeUshort: function ( buff, p, n ) { + buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; - buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; + }, + writeInt: function ( buff, p, n ) { - }, - writeInt: function ( buff, p, n ) { + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; + }, + writeUint: function ( buff, p, n ) { - }, - writeUint: function ( buff, p, n ) { + buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; - buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; + }, + writeASCII: UTIF._binBE.writeASCII + }; + UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { - }, - writeASCII: UTIF._binBE.writeASCII -}; -UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min( tw, w - xoff ); + var ylim = Math.min( th, h - yoff ); + for ( var y = 0; y < ylim; y ++ ) { - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min( tw, w - xoff ); - var ylim = Math.min( th, h - yoff ); - for ( var y = 0; y < ylim; y ++ ) { + var tof = ( yoff + y ) * w + xoff; + var sof = y * tw; + for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; - var tof = ( yoff + y ) * w + xoff; - var sof = y * tw; - for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; + } - } + }; -}; + return UTIF; } )(); diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index 43496730509f99..c4928ef9289d7c 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -127,942 +127,944 @@ class RGBMLoader extends DataTextureLoader { // from https://github.com/photopea/UPNG.js (MIT License) -var UPNG = {}; +const UPNG = /* @__PURE__ */ ( () => { -/* @__PURE__ */ ( () => { + var UPNG = {}; -UPNG.toRGBA8 = function ( out ) { + UPNG.toRGBA8 = function ( out ) { - var w = out.width, h = out.height; - if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; + var w = out.width, h = out.height; + if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; - var frms = []; - if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; + var frms = []; + if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; - var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); - for ( var i = 0; i < out.frames.length; i ++ ) { + var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); + for ( var i = 0; i < out.frames.length; i ++ ) { - var frm = out.frames[ i ]; - var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; - var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); + var frm = out.frames[ i ]; + var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; + var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); - if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; + if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; - if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); + if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); - frms.push( img.buffer.slice( 0 ) ); + frms.push( img.buffer.slice( 0 ) ); - if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; + if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; - } + } - return frms; + return frms; -}; + }; -UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { + UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { - var area = w * h, bpp = UPNG.decode._getBPP( out ); - var bpl = Math.ceil( w * bpp / 8 ); // bytes per line + var area = w * h, bpp = UPNG.decode._getBPP( out ); + var bpl = Math.ceil( w * bpp / 8 ); // bytes per line - var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); - var ctype = out.ctype, depth = out.depth; - var rs = UPNG._bin.readUshort; + var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); + var ctype = out.ctype, depth = out.depth; + var rs = UPNG._bin.readUshort; - if ( ctype == 6 ) { // RGB + alpha + if ( ctype == 6 ) { // RGB + alpha - var qarea = area << 2; - if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { + var qarea = area << 2; + if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { - bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; + bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; - } + } - if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { + if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { - bf[ i ] = data[ i << 1 ]; + bf[ i ] = data[ i << 1 ]; - } + } - } else if ( ctype == 2 ) { // RGB + } else if ( ctype == 2 ) { // RGB - var ts = out.tabs[ 'tRNS' ]; - if ( ts == null ) { + var ts = out.tabs[ 'tRNS' ]; + if ( ts == null ) { - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - } + } - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - } + } - } else { + } else { - var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; + var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; - } + } - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + + } } - } + } else if ( ctype == 3 ) { // palette - } else if ( ctype == 3 ) { // palette + var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; + //console.log(p, ap); + if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { - var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; - //console.log(p, ap); - if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + } } - } + if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { - if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + } } - } + if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { - if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + } } - } + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + } - } + } else if ( ctype == 4 ) { // gray + alpha - } else if ( ctype == 4 ) { // gray + alpha + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; - var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; + } - } + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; - var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; + } - } + } else if ( ctype == 0 ) { // gray - } else if ( ctype == 0 ) { // gray + var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; + for ( var y = 0; y < h; y ++ ) { - var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; - for ( var y = 0; y < h; y ++ ) { + var off = y * bpl, to = y * w; + if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { - var off = y * bpl, to = y * w; - if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { + var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { + var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { + var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { + var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } } } - } + //console.log(Date.now()-time); + return bf; - //console.log(Date.now()-time); - return bf; + }; -}; + UPNG.decode = function ( buff ) { -UPNG.decode = function ( buff ) { + var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; + var out = { tabs: {}, frames: [] }; + var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it + var fd, foff = 0; // frames + var text, keyw, bfr; - var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; - var out = { tabs: {}, frames: [] }; - var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it - var fd, foff = 0; // frames - var text, keyw, bfr; + var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; + for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); - var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; - for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); + while ( offset < data.length ) { - while ( offset < data.length ) { + var len = bin.readUint( data, offset ); offset += 4; + var type = bin.readASCII( data, offset, 4 ); offset += 4; + //console.log(type,len); - var len = bin.readUint( data, offset ); offset += 4; - var type = bin.readASCII( data, offset, 4 ); offset += 4; - //console.log(type,len); + if ( type == 'IHDR' ) { - if ( type == 'IHDR' ) { + UPNG.decode._IHDR( data, offset, out ); - UPNG.decode._IHDR( data, offset, out ); + } else if ( type == 'CgBI' ) { - } else if ( type == 'CgBI' ) { + out.tabs[ type ] = data.slice( offset, offset + 4 ); - out.tabs[ type ] = data.slice( offset, offset + 4 ); + } else if ( type == 'IDAT' ) { - } else if ( type == 'IDAT' ) { + for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; + doff += len; - for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; - doff += len; + } else if ( type == 'acTL' ) { - } else if ( type == 'acTL' ) { + out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; + fd = new Uint8Array( data.length ); - out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; - fd = new Uint8Array( data.length ); + } else if ( type == 'fcTL' ) { - } else if ( type == 'fcTL' ) { + if ( foff != 0 ) { - if ( foff != 0 ) { + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + } - } + var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; + var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); + var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; + //console.log(frm); + out.frames.push( frm ); - var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; - var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); - var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; - //console.log(frm); - out.frames.push( frm ); + } else if ( type == 'fdAT' ) { - } else if ( type == 'fdAT' ) { + for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; + foff += len - 4; - for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; - foff += len - 4; + } else if ( type == 'pHYs' ) { - } else if ( type == 'pHYs' ) { + out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; - out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; + } else if ( type == 'cHRM' ) { - } else if ( type == 'cHRM' ) { + out.tabs[ type ] = []; + for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); - out.tabs[ type ] = []; - for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); + } else if ( type == 'tEXt' || type == 'zTXt' ) { - } else if ( type == 'tEXt' || type == 'zTXt' ) { + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = bin.nextZero( data, offset ); + keyw = bin.readASCII( data, offset, nz - offset ); + var tl = offset + len - nz - 1; + if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); + else { - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = bin.nextZero( data, offset ); - keyw = bin.readASCII( data, offset, nz - offset ); - var tl = offset + len - nz - 1; - if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); - else { + bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); - bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); + } - } + out.tabs[ type ][ keyw ] = text; - out.tabs[ type ][ keyw ] = text; + } else if ( type == 'iTXt' ) { - } else if ( type == 'iTXt' ) { + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = 0, off = offset; + nz = bin.nextZero( data, off ); + keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; + var cflag = data[ off ]; off += 2; + nz = bin.nextZero( data, off ); + bin.readASCII( data, off, nz - off ); off = nz + 1; + nz = bin.nextZero( data, off ); + bin.readUTF8( data, off, nz - off ); off = nz + 1; + var tl = len - ( off - offset ); + if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); + else { - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = 0, off = offset; - nz = bin.nextZero( data, off ); - keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; - var cflag = data[ off ]; off += 2; - nz = bin.nextZero( data, off ); - bin.readASCII( data, off, nz - off ); off = nz + 1; - nz = bin.nextZero( data, off ); - bin.readUTF8( data, off, nz - off ); off = nz + 1; - var tl = len - ( off - offset ); - if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); - else { + bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); - bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); + } - } + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'PLTE' ) { - out.tabs[ type ][ keyw ] = text; + out.tabs[ type ] = bin.readBytes( data, offset, len ); - } else if ( type == 'PLTE' ) { + } else if ( type == 'hIST' ) { - out.tabs[ type ] = bin.readBytes( data, offset, len ); + var pl = out.tabs[ 'PLTE' ].length / 3; + out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); - } else if ( type == 'hIST' ) { + } else if ( type == 'tRNS' ) { - var pl = out.tabs[ 'PLTE' ].length / 3; - out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); + if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); + else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); + else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + //else console.log("tRNS for unsupported color type",out.ctype, len); - } else if ( type == 'tRNS' ) { + } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; + else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; + else if ( type == 'bKGD' ) { - if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); - else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); - else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - //else console.log("tRNS for unsupported color type",out.ctype, len); + if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; + else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; - } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; - else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; - else if ( type == 'bKGD' ) { + } else if ( type == 'IEND' ) { - if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; - else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; + break; - } else if ( type == 'IEND' ) { + } - break; + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len; + bin.readUint( data, offset ); offset += 4; } - //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } - offset += len; - bin.readUint( data, offset ); offset += 4; + if ( foff != 0 ) { - } + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); - if ( foff != 0 ) { + } - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); + out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); - } + delete out.compress; delete out.interlace; delete out.filter; + return out; - out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); + }; - delete out.compress; delete out.interlace; delete out.filter; - return out; + UPNG.decode._decompress = function ( out, dd, w, h ) { -}; + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); + if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); + else dd = UPNG.decode._inflate( dd, buff ); -UPNG.decode._decompress = function ( out, dd, w, h ) { + if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); + else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); - if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); - else dd = UPNG.decode._inflate( dd, buff ); + return dd; - if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); - else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); + }; - return dd; + UPNG.decode._inflate = function ( data, buff ) { -}; + var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; -UPNG.decode._inflate = function ( data, buff ) { + }; - var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; + UPNG.inflateRaw = function () { -}; + var H = {}; H.H = {}; H.H.N = function ( N, W ) { -UPNG.inflateRaw = function () { + var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; + if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; + if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { - var H = {}; H.H = {}; H.H.N = function ( N, W ) { + i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { - var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; - if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; - if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { + if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); + var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; + w += q; continue + ; - i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { + } - if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); - var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; - w += q; continue - ; + if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { - } + v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; - if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { + } - v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; + if ( m == 2 ) { - } + J = A( N, d, 5 ) + 257; + h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { - if ( m == 2 ) { + b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; - J = A( N, d, 5 ) + 257; - h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { + } - b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; + for ( var c = 0; + c < Q; c ++ ) { - } + var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K + ; - for ( var c = 0; - c < Q; c ++ ) { + } - var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K + d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; + d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); + I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) ; } - d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; - d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); - I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) - ; + while ( ! 0 ) { - } + var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { - while ( ! 0 ) { + W[ w ++ ] = p; - var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { + } else if ( p == 256 ) { - W[ w ++ ] = p; + break; - } else if ( p == 256 ) { + } else { - break; + var z = w + p - 254; + if ( p > 264 ) { - } else { + var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; - var z = w + p - 254; - if ( p > 264 ) { + } - var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; + var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); + d += Y & 15; while ( w < z ) { - } + W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; - var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); - d += Y & 15; while ( w < z ) { + } - W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; + w = z + ; } - w = z - ; - } } - } + return W.length == w ? W : W.slice( 0, w ) + ; - return W.length == w ? W : W.slice( 0, w ) - ; + }; - }; + H.H.W = function ( N, W ) { - H.H.W = function ( N, W ) { + var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; - var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; + }; - }; + H.H.R = function ( N, W, R, V, n, A ) { - H.H.R = function ( N, W, R, V, n, A ) { + var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { - var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { + var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; + if ( b <= 15 ) { - var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; - if ( b <= 15 ) { + A[ I ] = b; I ++; - A[ I ] = b; I ++; + } else { - } else { + var Z = 0, m = 0; if ( b == 16 ) { - var Z = 0, m = 0; if ( b == 16 ) { + m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; - m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; + } else if ( b == 17 ) { - } else if ( b == 17 ) { + m = 3 + l( V, n, 3 ); + n += 3 + ; - m = 3 + l( V, n, 3 ); - n += 3 - ; + } else if ( b == 18 ) { - } else if ( b == 18 ) { + m = 11 + l( V, n, 7 ); n += 7; - m = 11 + l( V, n, 7 ); n += 7; + } - } + var J = I + m; while ( I < J ) { - var J = I + m; while ( I < J ) { + A[ I ] = Z; I ++; - A[ I ] = Z; I ++; + } } } - } + return n + ; - return n - ; + }; - }; + H.H.V = function ( N, W, R, V ) { - H.H.V = function ( N, W, R, V ) { + var n = 0, A = 0, l = V.length >>> 1; + while ( A < R ) { - var n = 0, A = 0, l = V.length >>> 1; - while ( A < R ) { + var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; - var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; + } - } + while ( A < l ) { - while ( A < l ) { + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; - V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + } - } + return n + ; - return n - ; + }; - }; + H.H.n = function ( N, W ) { - H.H.n = function ( N, W ) { + var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; + var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { - var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; - var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { + n = n + e[ A - 1 ] << 1; b[ A ] = n; - n = n + e[ A - 1 ] << 1; b[ A ] = n; + } - } + for ( l = 0; l < V; l += 2 ) { - for ( l = 0; l < V; l += 2 ) { + I = N[ l + 1 ]; if ( I != 0 ) { - I = N[ l + 1 ]; if ( I != 0 ) { + N[ l ] = b[ I ]; + b[ I ] ++ + ; - N[ l ] = b[ I ]; - b[ I ] ++ - ; + } } - } + }; - }; + H.H.A = function ( N, W, R ) { - H.H.A = function ( N, W, R ) { + var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { - var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { + var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); + while ( Z != m ) { - var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); - while ( Z != m ) { + var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; - var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; + } } - } + }; - }; + H.H.l = function ( N, W ) { - H.H.l = function ( N, W ) { + var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; + n += 2 ) { - var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; - n += 2 ) { + var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; - var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; + } - } + }; - }; + H.H.M = function ( N, W, R ) { - H.H.M = function ( N, W, R ) { + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + }; - }; + H.H.I = function ( N, W, R ) { - H.H.I = function ( N, W, R ) { + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; + }; - }; + H.H.e = function ( N, W, R ) { - H.H.e = function ( N, W, R ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + }; - }; + H.H.b = function ( N, W, R ) { - H.H.b = function ( N, W, R ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + }; - }; + H.H.Z = function ( N, W ) { - H.H.Z = function ( N, W ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + }; - }; + H.H.i = function ( N, W ) { - H.H.i = function ( N, W ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); + }; - }; + H.H.m = function () { - H.H.m = function () { + var N = Uint16Array, W = Uint32Array; + return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } + ; - var N = Uint16Array, W = Uint32Array; - return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } - ; + }(); + ( function () { - }(); - ( function () { + var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { - var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { + var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; + V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; + N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 + ; - var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; - V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; - N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 - ; + } - } + function n( A, l, M ) { - function n( A, l, M ) { + while ( l -- != 0 )A.push( 0, M ) + ; - while ( l -- != 0 )A.push( 0, M ) - ; + } - } + for ( var R = 0; R < 32; R ++ ) { + + N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; + N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] + ; - for ( var R = 0; R < 32; R ++ ) { + } - N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; - N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] + n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); + H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); + n( N.D, 30, 0 ); n( N.v, 320, 0 ) ; - } + }() ); - n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); - H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); - n( N.D, 30, 0 ); n( N.v, 320, 0 ) + return H.H.N ; - }() ); + }(); - return H.H.N - ; -}(); + UPNG.decode._readInterlace = function ( data, out ) { + var w = out.width, h = out.height; + var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); + var img = new Uint8Array( h * bpl ); + var di = 0; -UPNG.decode._readInterlace = function ( data, out ) { + var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; + var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; + var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; + var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; - var w = out.width, h = out.height; - var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); - var img = new Uint8Array( h * bpl ); - var di = 0; + var pass = 0; + while ( pass < 7 ) { - var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; - var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; - var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; - var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; + var ri = row_increment[ pass ], ci = col_increment[ pass ]; + var sw = 0, sh = 0; + var cr = starting_row[ pass ]; while ( cr < h ) { - var pass = 0; - while ( pass < 7 ) { + cr += ri; sh ++; - var ri = row_increment[ pass ], ci = col_increment[ pass ]; - var sw = 0, sh = 0; - var cr = starting_row[ pass ]; while ( cr < h ) { + } - cr += ri; sh ++; + var cc = starting_col[ pass ]; while ( cc < w ) { - } + cc += ci; sw ++; - var cc = starting_col[ pass ]; while ( cc < w ) { + } - cc += ci; sw ++; + var bpll = Math.ceil( sw * bpp / 8 ); + UPNG.decode._filterZero( data, out, di, sw, sh ); - } + var y = 0, row = starting_row[ pass ]; + var val; - var bpll = Math.ceil( sw * bpp / 8 ); - UPNG.decode._filterZero( data, out, di, sw, sh ); + while ( row < h ) { - var y = 0, row = starting_row[ pass ]; - var val; + var col = starting_col[ pass ]; + var cdi = ( di + y * bpll ) << 3; - while ( row < h ) { + while ( col < w ) { - var col = starting_col[ pass ]; - var cdi = ( di + y * bpll ) << 3; + if ( bpp == 1 ) { - while ( col < w ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; + img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); - if ( bpp == 1 ) { + } - val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; - img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); + if ( bpp == 2 ) { - } + val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; + img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); - if ( bpp == 2 ) { + } - val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; - img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); + if ( bpp == 4 ) { - } + val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; + img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); - if ( bpp == 4 ) { + } - val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; - img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); + if ( bpp >= 8 ) { - } + var ii = row * bpl + col * cbpp; + for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; - if ( bpp >= 8 ) { + } - var ii = row * bpl + col * cbpp; - for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; + cdi += bpp; col += ci; } - cdi += bpp; col += ci; + y ++; row += ri; } - y ++; row += ri; + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; } - if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); - pass = pass + 1; + return img; - } - - return img; + }; -}; + UPNG.decode._getBPP = function ( out ) { -UPNG.decode._getBPP = function ( out ) { + var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; + return noc * out.depth; - var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; - return noc * out.depth; + }; -}; + UPNG.decode._filterZero = function ( data, out, off, w, h ) { -UPNG.decode._filterZero = function ( data, out, off, w, h ) { + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; + bpp = Math.ceil( bpp / 8 ); - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; - bpp = Math.ceil( bpp / 8 ); + var i, di, type = data[ off ], x = 0; - var i, di, type = data[ off ], x = 0; + if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; + if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; - if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; - if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; + for ( var y = 0; y < h; y ++ ) { - for ( var y = 0; y < h; y ++ ) { + i = off + y * bpl; di = i + y + 1; + type = data[ di - 1 ]; x = 0; - i = off + y * bpl; di = i + y + 1; - type = data[ di - 1 ]; x = 0; + if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; + else if ( type == 1 ) { - if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; - else if ( type == 1 ) { + for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); - for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); + } else if ( type == 2 ) { - } else if ( type == 2 ) { + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); + } else if ( type == 3 ) { - } else if ( type == 3 ) { + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); + } else { - } else { + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); + } } - } + return data; + + }; - return data; + UPNG.decode._paeth = function ( a, b, c ) { -}; + var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); + if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; + else if ( pb * pb <= pc * pc ) return b; + return c; -UPNG.decode._paeth = function ( a, b, c ) { + }; - var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); - if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; - else if ( pb * pb <= pc * pc ) return b; - return c; + UPNG.decode._IHDR = function ( data, offset, out ) { -}; + var bin = UPNG._bin; + out.width = bin.readUint( data, offset ); offset += 4; + out.height = bin.readUint( data, offset ); offset += 4; + out.depth = data[ offset ]; offset ++; + out.ctype = data[ offset ]; offset ++; + out.compress = data[ offset ]; offset ++; + out.filter = data[ offset ]; offset ++; + out.interlace = data[ offset ]; offset ++; -UPNG.decode._IHDR = function ( data, offset, out ) { + }; - var bin = UPNG._bin; - out.width = bin.readUint( data, offset ); offset += 4; - out.height = bin.readUint( data, offset ); offset += 4; - out.depth = data[ offset ]; offset ++; - out.ctype = data[ offset ]; offset ++; - out.compress = data[ offset ]; offset ++; - out.filter = data[ offset ]; offset ++; - out.interlace = data[ offset ]; offset ++; + UPNG._bin = { + nextZero: function ( data, p ) { -}; + while ( data[ p ] != 0 ) p ++; return p; -UPNG._bin = { - nextZero: function ( data, p ) { + }, + readUshort: function ( buff, p ) { - while ( data[ p ] != 0 ) p ++; return p; + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - }, - readUshort: function ( buff, p ) { + }, + writeUshort: function ( buff, p, n ) { - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - }, - writeUshort: function ( buff, p, n ) { + }, + readUint: function ( buff, p ) { - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); - }, - readUint: function ( buff, p ) { + }, + writeUint: function ( buff, p, n ) { - return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; - }, - writeUint: function ( buff, p, n ) { + }, + readASCII: function ( buff, p, l ) { - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - }, - readASCII: function ( buff, p, l ) { + }, + writeASCII: function ( data, p, s ) { - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); - }, - writeASCII: function ( data, p, s ) { + }, + readBytes: function ( buff, p, l ) { - for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); + var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; - }, - readBytes: function ( buff, p, l ) { + }, + pad: function ( n ) { - var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; + return n.length < 2 ? '0' + n : n; - }, - pad: function ( n ) { + }, + readUTF8: function ( buff, p, l ) { - return n.length < 2 ? '0' + n : n; + var s = '', ns; + for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); + try { - }, - readUTF8: function ( buff, p, l ) { + ns = decodeURIComponent( s ); - var s = '', ns; - for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); - try { + } catch ( e ) { - ns = decodeURIComponent( s ); + return UPNG._bin.readASCII( buff, p, l ); - } catch ( e ) { + } - return UPNG._bin.readASCII( buff, p, l ); + return ns; } + }; + UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - return ns; + var w = Math.min( sw, tw ), h = Math.min( sh, th ); + var si = 0, ti = 0; + for ( var y = 0; y < h; y ++ ) + for ( var x = 0; x < w; x ++ ) { - } -}; -UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { + if ( xoff >= 0 && yoff >= 0 ) { - var w = Math.min( sw, tw ), h = Math.min( sh, th ); - var si = 0, ti = 0; - for ( var y = 0; y < h; y ++ ) - for ( var x = 0; x < w; x ++ ) { + si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; - if ( xoff >= 0 && yoff >= 0 ) { - - si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; + } else { - } else { + si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; - si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; + } - } + if ( mode == 0 ) { - if ( mode == 0 ) { + tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; - tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; + } else if ( mode == 1 ) { - } else if ( mode == 1 ) { + var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; + var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; - var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; - var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; + var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); + tb[ ti + 3 ] = 255 * oa; + tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; + tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; + tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; - var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); - tb[ ti + 3 ] = 255 * oa; - tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; - tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; - tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; + } else if ( mode == 2 ) { // copy only differences, otherwise zero - } else if ( mode == 2 ) { // copy only differences, otherwise zero + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) { - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) { + tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; - tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; + } else { - } else { + tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; - tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; + } - } + } else if ( mode == 3 ) { // check if can be blended - } else if ( mode == 3 ) { // check if can be blended + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; + //if(fa!=255 && ba!=0) return false; + if ( fa < 220 && ba > 20 ) return false; - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; - //if(fa!=255 && ba!=0) return false; - if ( fa < 220 && ba > 20 ) return false; + } } - } + return true; - return true; + }; -}; + return UPNG; } )(); diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index 216f62786abbbf..6c7d193ee61131 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -15,384 +15,392 @@ import { Vector4 } from 'three'; -class Lensflare extends Mesh { +const Lensflare = /* @__PURE__ */ ( () => { - constructor() { + class Lensflare extends Mesh { - super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); + constructor() { - this.isLensflare = true; + super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); - this.type = 'Lensflare'; - this.frustumCulled = false; - this.renderOrder = Infinity; + this.isLensflare = true; - // + this.type = 'Lensflare'; + this.frustumCulled = false; + this.renderOrder = Infinity; - const positionScreen = new Vector3(); - const positionView = new Vector3(); + // - // textures + const positionScreen = new Vector3(); + const positionView = new Vector3(); - const tempMap = new FramebufferTexture( 16, 16 ); - const occlusionMap = new FramebufferTexture( 16, 16 ); + // textures - let currentType = UnsignedByteType; + const tempMap = new FramebufferTexture( 16, 16 ); + const occlusionMap = new FramebufferTexture( 16, 16 ); - // material + let currentType = UnsignedByteType; - const geometry = Lensflare.Geometry; + // material - const material1a = new RawShaderMaterial( { - uniforms: { - 'scale': { value: null }, - 'screenPosition': { value: null } - }, - vertexShader: /* glsl */` + const geometry = Lensflare.Geometry; - precision highp float; + const material1a = new RawShaderMaterial( { + uniforms: { + 'scale': { value: null }, + 'screenPosition': { value: null } + }, + vertexShader: /* glsl */` - uniform vec3 screenPosition; - uniform vec2 scale; + precision highp float; - attribute vec3 position; + uniform vec3 screenPosition; + uniform vec2 scale; - void main() { + attribute vec3 position; - gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); + void main() { - }`, + gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); - fragmentShader: /* glsl */` + }`, - precision highp float; + fragmentShader: /* glsl */` - void main() { + precision highp float; - gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 ); + void main() { - }`, - depthTest: true, - depthWrite: false, - transparent: false - } ); + gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 ); - const material1b = new RawShaderMaterial( { - uniforms: { - 'map': { value: tempMap }, - 'scale': { value: null }, - 'screenPosition': { value: null } - }, - vertexShader: /* glsl */` + }`, + depthTest: true, + depthWrite: false, + transparent: false + } ); - precision highp float; + const material1b = new RawShaderMaterial( { + uniforms: { + 'map': { value: tempMap }, + 'scale': { value: null }, + 'screenPosition': { value: null } + }, + vertexShader: /* glsl */` - uniform vec3 screenPosition; - uniform vec2 scale; + precision highp float; - attribute vec3 position; - attribute vec2 uv; + uniform vec3 screenPosition; + uniform vec2 scale; - varying vec2 vUV; + attribute vec3 position; + attribute vec2 uv; - void main() { + varying vec2 vUV; - vUV = uv; + void main() { - gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); + vUV = uv; - }`, + gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); - fragmentShader: /* glsl */` + }`, - precision highp float; + fragmentShader: /* glsl */` - uniform sampler2D map; + precision highp float; - varying vec2 vUV; + uniform sampler2D map; - void main() { + varying vec2 vUV; - gl_FragColor = texture2D( map, vUV ); + void main() { - }`, - depthTest: false, - depthWrite: false, - transparent: false - } ); + gl_FragColor = texture2D( map, vUV ); - // the following object is used for occlusionMap generation + }`, + depthTest: false, + depthWrite: false, + transparent: false + } ); - const mesh1 = new Mesh( geometry, material1a ); + // the following object is used for occlusionMap generation - // + const mesh1 = new Mesh( geometry, material1a ); - const elements = []; + // - const shader = LensflareElement.Shader; + const elements = []; - const material2 = new RawShaderMaterial( { - uniforms: { - 'map': { value: null }, - 'occlusionMap': { value: occlusionMap }, - 'color': { value: new Color( 0xffffff ) }, - 'scale': { value: new Vector2() }, - 'screenPosition': { value: new Vector3() } - }, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - blending: AdditiveBlending, - transparent: true, - depthWrite: false - } ); + const shader = LensflareElement.Shader; - const mesh2 = new Mesh( geometry, material2 ); + const material2 = new RawShaderMaterial( { + uniforms: { + 'map': { value: null }, + 'occlusionMap': { value: occlusionMap }, + 'color': { value: new Color( 0xffffff ) }, + 'scale': { value: new Vector2() }, + 'screenPosition': { value: new Vector3() } + }, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + blending: AdditiveBlending, + transparent: true, + depthWrite: false + } ); - this.addElement = function ( element ) { + const mesh2 = new Mesh( geometry, material2 ); - elements.push( element ); + this.addElement = function ( element ) { - }; + elements.push( element ); - // + }; - const scale = new Vector2(); - const screenPositionPixels = new Vector2(); - const validArea = new Box2(); - const viewport = new Vector4(); + // - this.onBeforeRender = function ( renderer, scene, camera ) { + const scale = new Vector2(); + const screenPositionPixels = new Vector2(); + const validArea = new Box2(); + const viewport = new Vector4(); - renderer.getCurrentViewport( viewport ); + this.onBeforeRender = function ( renderer, scene, camera ) { - const renderTarget = renderer.getRenderTarget(); - const type = ( renderTarget !== null ) ? renderTarget.texture.type : UnsignedByteType; + renderer.getCurrentViewport( viewport ); - if ( currentType !== type ) { + const renderTarget = renderer.getRenderTarget(); + const type = ( renderTarget !== null ) ? renderTarget.texture.type : UnsignedByteType; - tempMap.dispose(); - occlusionMap.dispose(); + if ( currentType !== type ) { - tempMap.type = occlusionMap.type = type; + tempMap.dispose(); + occlusionMap.dispose(); - currentType = type; + tempMap.type = occlusionMap.type = type; - } + currentType = type; - const invAspect = viewport.w / viewport.z; - const halfViewportWidth = viewport.z / 2.0; - const halfViewportHeight = viewport.w / 2.0; + } - let size = 16 / viewport.w; - scale.set( size * invAspect, size ); + const invAspect = viewport.w / viewport.z; + const halfViewportWidth = viewport.z / 2.0; + const halfViewportHeight = viewport.w / 2.0; - validArea.min.set( viewport.x, viewport.y ); - validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); + let size = 16 / viewport.w; + scale.set( size * invAspect, size ); - // calculate position in screen space + validArea.min.set( viewport.x, viewport.y ); + validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); - positionView.setFromMatrixPosition( this.matrixWorld ); - positionView.applyMatrix4( camera.matrixWorldInverse ); + // calculate position in screen space - if ( positionView.z > 0 ) return; // lensflare is behind the camera + positionView.setFromMatrixPosition( this.matrixWorld ); + positionView.applyMatrix4( camera.matrixWorldInverse ); - positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix ); + if ( positionView.z > 0 ) return; // lensflare is behind the camera - // horizontal and vertical coordinate of the lower left corner of the pixels to copy + positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix ); - screenPositionPixels.x = viewport.x + ( positionScreen.x * halfViewportWidth ) + halfViewportWidth - 8; - screenPositionPixels.y = viewport.y + ( positionScreen.y * halfViewportHeight ) + halfViewportHeight - 8; + // horizontal and vertical coordinate of the lower left corner of the pixels to copy - // screen cull + screenPositionPixels.x = viewport.x + ( positionScreen.x * halfViewportWidth ) + halfViewportWidth - 8; + screenPositionPixels.y = viewport.y + ( positionScreen.y * halfViewportHeight ) + halfViewportHeight - 8; - if ( validArea.containsPoint( screenPositionPixels ) ) { + // screen cull - // save current RGB to temp texture + if ( validArea.containsPoint( screenPositionPixels ) ) { - renderer.copyFramebufferToTexture( screenPositionPixels, tempMap ); + // save current RGB to temp texture - // render pink quad + renderer.copyFramebufferToTexture( screenPositionPixels, tempMap ); - let uniforms = material1a.uniforms; - uniforms[ 'scale' ].value = scale; - uniforms[ 'screenPosition' ].value = positionScreen; + // render pink quad - renderer.renderBufferDirect( camera, null, geometry, material1a, mesh1, null ); + let uniforms = material1a.uniforms; + uniforms[ 'scale' ].value = scale; + uniforms[ 'screenPosition' ].value = positionScreen; - // copy result to occlusionMap + renderer.renderBufferDirect( camera, null, geometry, material1a, mesh1, null ); - renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap ); + // copy result to occlusionMap - // restore graphics + renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap ); - uniforms = material1b.uniforms; - uniforms[ 'scale' ].value = scale; - uniforms[ 'screenPosition' ].value = positionScreen; + // restore graphics - renderer.renderBufferDirect( camera, null, geometry, material1b, mesh1, null ); + uniforms = material1b.uniforms; + uniforms[ 'scale' ].value = scale; + uniforms[ 'screenPosition' ].value = positionScreen; - // render elements + renderer.renderBufferDirect( camera, null, geometry, material1b, mesh1, null ); - const vecX = - positionScreen.x * 2; - const vecY = - positionScreen.y * 2; + // render elements - for ( let i = 0, l = elements.length; i < l; i ++ ) { + const vecX = - positionScreen.x * 2; + const vecY = - positionScreen.y * 2; + + for ( let i = 0, l = elements.length; i < l; i ++ ) { - const element = elements[ i ]; + const element = elements[ i ]; - const uniforms = material2.uniforms; + const uniforms = material2.uniforms; - uniforms[ 'color' ].value.copy( element.color ); - uniforms[ 'map' ].value = element.texture; - uniforms[ 'screenPosition' ].value.x = positionScreen.x + vecX * element.distance; - uniforms[ 'screenPosition' ].value.y = positionScreen.y + vecY * element.distance; + uniforms[ 'color' ].value.copy( element.color ); + uniforms[ 'map' ].value = element.texture; + uniforms[ 'screenPosition' ].value.x = positionScreen.x + vecX * element.distance; + uniforms[ 'screenPosition' ].value.y = positionScreen.y + vecY * element.distance; - size = element.size / viewport.w; - const invAspect = viewport.w / viewport.z; + size = element.size / viewport.w; + const invAspect = viewport.w / viewport.z; - uniforms[ 'scale' ].value.set( size * invAspect, size ); + uniforms[ 'scale' ].value.set( size * invAspect, size ); - material2.uniformsNeedUpdate = true; + material2.uniformsNeedUpdate = true; - renderer.renderBufferDirect( camera, null, geometry, material2, mesh2, null ); + renderer.renderBufferDirect( camera, null, geometry, material2, mesh2, null ); + + } } - } + }; - }; + this.dispose = function () { - this.dispose = function () { + material1a.dispose(); + material1b.dispose(); + material2.dispose(); - material1a.dispose(); - material1b.dispose(); - material2.dispose(); + tempMap.dispose(); + occlusionMap.dispose(); - tempMap.dispose(); - occlusionMap.dispose(); + for ( let i = 0, l = elements.length; i < l; i ++ ) { - for ( let i = 0, l = elements.length; i < l; i ++ ) { + elements[ i ].texture.dispose(); - elements[ i ].texture.dispose(); + } - } + }; - }; + } } -} + Lensflare.Geometry = /* @__PURE__ */ ( function () { -// + const geometry = new BufferGeometry(); -class LensflareElement { + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); - constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - this.texture = texture; - this.size = size; - this.distance = distance; - this.color = color; + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - } + return geometry; -} + } )(); -const Shader = { + return Lensflare; - uniforms: { +} )(); - 'map': { value: null }, - 'occlusionMap': { value: null }, - 'color': { value: null }, - 'scale': { value: null }, - 'screenPosition': { value: null } +// - }, +const LensflareElement = /* @__PURE__ */ ( () => { - vertexShader: /* glsl */` + class LensflareElement { - precision highp float; + constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { - uniform vec3 screenPosition; - uniform vec2 scale; + this.texture = texture; + this.size = size; + this.distance = distance; + this.color = color; - uniform sampler2D occlusionMap; + } - attribute vec3 position; - attribute vec2 uv; + } - varying vec2 vUV; - varying float vVisibility; + LensflareElement.Shader = { - void main() { + uniforms: { - vUV = uv; + 'map': { value: null }, + 'occlusionMap': { value: null }, + 'color': { value: null }, + 'scale': { value: null }, + 'screenPosition': { value: null } - vec2 pos = position.xy; + }, - vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); + vertexShader: /* glsl */` - vVisibility = visibility.r / 9.0; - vVisibility *= 1.0 - visibility.g / 9.0; - vVisibility *= visibility.b / 9.0; + precision highp float; - gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); + uniform vec3 screenPosition; + uniform vec2 scale; - }`, + uniform sampler2D occlusionMap; - fragmentShader: /* glsl */` + attribute vec3 position; + attribute vec2 uv; - precision highp float; + varying vec2 vUV; + varying float vVisibility; - uniform sampler2D map; - uniform vec3 color; + void main() { - varying vec2 vUV; - varying float vVisibility; + vUV = uv; - void main() { + vec2 pos = position.xy; - vec4 texture = texture2D( map, vUV ); - texture.a *= vVisibility; - gl_FragColor = texture; - gl_FragColor.rgb *= color; + vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); - }` + vVisibility = visibility.r / 9.0; + vVisibility *= 1.0 - visibility.g / 9.0; + vVisibility *= visibility.b / 9.0; -}; + gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); -/* @__PURE__ */ Object.assign( LensflareElement, { Shader } ); + }`, -const Geometry = /* @__PURE__ */ ( function () { + fragmentShader: /* glsl */` - const geometry = new BufferGeometry(); + precision highp float; - const float32Array = new Float32Array( [ - - 1, - 1, 0, 0, 0, - 1, - 1, 0, 1, 0, - 1, 1, 0, 1, 1, - - 1, 1, 0, 0, 1 - ] ); + uniform sampler2D map; + uniform vec3 color; - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + varying vec2 vUV; + varying float vVisibility; - geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + void main() { - return geometry; + vec4 texture = texture2D( map, vUV ); + texture.a *= vVisibility; + gl_FragColor = texture; + gl_FragColor.rgb *= color; -} )(); + }` + + }; -/* @__PURE__ */ Object.assign( Lensflare, { Geometry } ); + return LensflareElement; + +} )(); export { Lensflare, LensflareElement }; diff --git a/examples/jsm/objects/Reflector.js b/examples/jsm/objects/Reflector.js index 101303f5a829e0..83f49a606a5cb8 100644 --- a/examples/jsm/objects/Reflector.js +++ b/examples/jsm/objects/Reflector.js @@ -12,255 +12,259 @@ import { HalfFloatType } from 'three'; -class Reflector extends Mesh { +const Reflector = /* @__PURE__ */ ( () => { - constructor( geometry, options = {} ) { + class Reflector extends Mesh { - super( geometry ); + constructor( geometry, options = {} ) { - this.isReflector = true; + super( geometry ); - this.type = 'Reflector'; - this.camera = new PerspectiveCamera(); + this.isReflector = true; - const scope = this; + this.type = 'Reflector'; + this.camera = new PerspectiveCamera(); - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const shader = options.shader || Reflector.ReflectorShader; - const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; + const scope = this; - // + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const shader = options.shader || Reflector.ReflectorShader; + const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; - const reflectorPlane = new Plane(); - const normal = new Vector3(); - const reflectorWorldPosition = new Vector3(); - const cameraWorldPosition = new Vector3(); - const rotationMatrix = new Matrix4(); - const lookAtPosition = new Vector3( 0, 0, - 1 ); - const clipPlane = new Vector4(); + // - const view = new Vector3(); - const target = new Vector3(); - const q = new Vector4(); + const reflectorPlane = new Plane(); + const normal = new Vector3(); + const reflectorWorldPosition = new Vector3(); + const cameraWorldPosition = new Vector3(); + const rotationMatrix = new Matrix4(); + const lookAtPosition = new Vector3( 0, 0, - 1 ); + const clipPlane = new Vector4(); - const textureMatrix = new Matrix4(); - const virtualCamera = this.camera; + const view = new Vector3(); + const target = new Vector3(); + const q = new Vector4(); - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); + const textureMatrix = new Matrix4(); + const virtualCamera = this.camera; - const material = new ShaderMaterial( { - name: ( shader.name !== undefined ) ? shader.name : 'unspecified', - uniforms: UniformsUtils.clone( shader.uniforms ), - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader - } ); + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); - material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; - material.uniforms[ 'color' ].value = color; - material.uniforms[ 'textureMatrix' ].value = textureMatrix; + const material = new ShaderMaterial( { + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', + uniforms: UniformsUtils.clone( shader.uniforms ), + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader + } ); - this.material = material; + material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; + material.uniforms[ 'color' ].value = color; + material.uniforms[ 'textureMatrix' ].value = textureMatrix; - this.onBeforeRender = function ( renderer, scene, camera ) { + this.material = material; - reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); - cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + this.onBeforeRender = function ( renderer, scene, camera ) { - rotationMatrix.extractRotation( scope.matrixWorld ); + reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); - normal.set( 0, 0, 1 ); - normal.applyMatrix4( rotationMatrix ); + rotationMatrix.extractRotation( scope.matrixWorld ); - view.subVectors( reflectorWorldPosition, cameraWorldPosition ); + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); - // Avoid rendering when reflector is facing away + view.subVectors( reflectorWorldPosition, cameraWorldPosition ); - if ( view.dot( normal ) > 0 ) return; + // Avoid rendering when reflector is facing away - view.reflect( normal ).negate(); - view.add( reflectorWorldPosition ); + if ( view.dot( normal ) > 0 ) return; - rotationMatrix.extractRotation( camera.matrixWorld ); + view.reflect( normal ).negate(); + view.add( reflectorWorldPosition ); - lookAtPosition.set( 0, 0, - 1 ); - lookAtPosition.applyMatrix4( rotationMatrix ); - lookAtPosition.add( cameraWorldPosition ); + rotationMatrix.extractRotation( camera.matrixWorld ); - target.subVectors( reflectorWorldPosition, lookAtPosition ); - target.reflect( normal ).negate(); - target.add( reflectorWorldPosition ); + lookAtPosition.set( 0, 0, - 1 ); + lookAtPosition.applyMatrix4( rotationMatrix ); + lookAtPosition.add( cameraWorldPosition ); - virtualCamera.position.copy( view ); - virtualCamera.up.set( 0, 1, 0 ); - virtualCamera.up.applyMatrix4( rotationMatrix ); - virtualCamera.up.reflect( normal ); - virtualCamera.lookAt( target ); + target.subVectors( reflectorWorldPosition, lookAtPosition ); + target.reflect( normal ).negate(); + target.add( reflectorWorldPosition ); - virtualCamera.far = camera.far; // Used in WebGLBackground + virtualCamera.position.copy( view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( rotationMatrix ); + virtualCamera.up.reflect( normal ); + virtualCamera.lookAt( target ); - virtualCamera.updateMatrixWorld(); - virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.far = camera.far; // Used in WebGLBackground - // Update the texture matrix - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - textureMatrix.multiply( virtualCamera.projectionMatrix ); - textureMatrix.multiply( virtualCamera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); - // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html - // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf - reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition ); - reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + // Update the texture matrix + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + textureMatrix.multiply( virtualCamera.projectionMatrix ); + textureMatrix.multiply( virtualCamera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant ); + // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html + // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf + reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition ); + reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); - const projectionMatrix = virtualCamera.projectionMatrix; + clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant ); - q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; - q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; - q.z = - 1.0; - q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + const projectionMatrix = virtualCamera.projectionMatrix; - // Calculate the scaled plane vector - clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) ); + q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + q.z = - 1.0; + q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; - // Replacing the third row of the projection matrix - projectionMatrix.elements[ 2 ] = clipPlane.x; - projectionMatrix.elements[ 6 ] = clipPlane.y; - projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; - projectionMatrix.elements[ 14 ] = clipPlane.w; + // Calculate the scaled plane vector + clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) ); - // Render - scope.visible = false; + // Replacing the third row of the projection matrix + projectionMatrix.elements[ 2 ] = clipPlane.x; + projectionMatrix.elements[ 6 ] = clipPlane.y; + projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; + projectionMatrix.elements[ 14 ] = clipPlane.w; - const currentRenderTarget = renderer.getRenderTarget(); + // Render + scope.visible = false; - const currentXrEnabled = renderer.xr.enabled; - const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + const currentRenderTarget = renderer.getRenderTarget(); - renderer.xr.enabled = false; // Avoid camera modification - renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows + const currentXrEnabled = renderer.xr.enabled; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - renderer.setRenderTarget( renderTarget ); + renderer.xr.enabled = false; // Avoid camera modification + renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows - renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 + renderer.setRenderTarget( renderTarget ); - if ( renderer.autoClear === false ) renderer.clear(); - renderer.render( scene, virtualCamera ); + renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 - renderer.xr.enabled = currentXrEnabled; - renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + if ( renderer.autoClear === false ) renderer.clear(); + renderer.render( scene, virtualCamera ); - renderer.setRenderTarget( currentRenderTarget ); + renderer.xr.enabled = currentXrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - // Restore viewport + renderer.setRenderTarget( currentRenderTarget ); - const viewport = camera.viewport; + // Restore viewport - if ( viewport !== undefined ) { + const viewport = camera.viewport; - renderer.state.viewport( viewport ); + if ( viewport !== undefined ) { - } + renderer.state.viewport( viewport ); + + } - scope.visible = true; + scope.visible = true; - }; + }; - this.getRenderTarget = function () { + this.getRenderTarget = function () { - return renderTarget; + return renderTarget; - }; + }; - this.dispose = function () { + this.dispose = function () { - renderTarget.dispose(); - scope.material.dispose(); + renderTarget.dispose(); + scope.material.dispose(); - }; + }; + + } } -} + Reflector.ReflectorShader = { -const ReflectorShader = { + name: 'ReflectorShader', - name: 'ReflectorShader', + uniforms: { - uniforms: { + 'color': { + value: null + }, - 'color': { - value: null - }, + 'tDiffuse': { + value: null + }, - 'tDiffuse': { - value: null - }, + 'textureMatrix': { + value: null + } - 'textureMatrix': { - value: null - } + }, - }, + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; + #include + #include - #include - #include + void main() { - void main() { + vUv = textureMatrix * vec4( position, 1.0 ); - vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + #include - #include + }`, - }`, + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + varying vec4 vUv; - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - varying vec4 vUv; + #include - #include + float blendOverlay( float base, float blend ) { - float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } - } + vec3 blendOverlay( vec3 base, vec3 blend ) { - vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } - } + void main() { - void main() { + #include - #include + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #include + #include - #include - #include + }` + }; - }` -}; + return Reflector; -/* @__PURE__ */ Object.assign( Reflector, { ReflectorShader } ); +} )(); export { Reflector }; diff --git a/examples/jsm/objects/ReflectorForSSRPass.js b/examples/jsm/objects/ReflectorForSSRPass.js index 2d6dda256714f2..7ed7a294f99759 100644 --- a/examples/jsm/objects/ReflectorForSSRPass.js +++ b/examples/jsm/objects/ReflectorForSSRPass.js @@ -15,337 +15,341 @@ import { HalfFloatType } from 'three'; -class ReflectorForSSRPass extends Mesh { +const ReflectorForSSRPass = /* @__PURE__ */ ( () => { - constructor( geometry, options = {} ) { + class ReflectorForSSRPass extends Mesh { - super( geometry ); + constructor( geometry, options = {} ) { - this.isReflectorForSSRPass = true; + super( geometry ); - this.type = 'ReflectorForSSRPass'; + this.isReflectorForSSRPass = true; - const scope = this; + this.type = 'ReflectorForSSRPass'; - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const shader = options.shader || ReflectorForSSRPass.ReflectorShader; - const useDepthTexture = options.useDepthTexture === true; - const yAxis = new Vector3( 0, 1, 0 ); - const vecTemp0 = new Vector3(); - const vecTemp1 = new Vector3(); + const scope = this; - // + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const shader = options.shader || ReflectorForSSRPass.ReflectorShader; + const useDepthTexture = options.useDepthTexture === true; + const yAxis = new Vector3( 0, 1, 0 ); + const vecTemp0 = new Vector3(); + const vecTemp1 = new Vector3(); - scope.needsUpdate = false; - scope.maxDistance = ReflectorForSSRPass.ReflectorShader.uniforms.maxDistance.value; - scope.opacity = ReflectorForSSRPass.ReflectorShader.uniforms.opacity.value; - scope.color = color; - scope.resolution = options.resolution || new Vector2( window.innerWidth, window.innerHeight ); + // + scope.needsUpdate = false; + scope.maxDistance = ReflectorForSSRPass.ReflectorShader.uniforms.maxDistance.value; + scope.opacity = ReflectorForSSRPass.ReflectorShader.uniforms.opacity.value; + scope.color = color; + scope.resolution = options.resolution || new Vector2( window.innerWidth, window.innerHeight ); - scope._distanceAttenuation = ReflectorForSSRPass.ReflectorShader.defines.DISTANCE_ATTENUATION; - Object.defineProperty( scope, 'distanceAttenuation', { - get() { - return scope._distanceAttenuation; + scope._distanceAttenuation = ReflectorForSSRPass.ReflectorShader.defines.DISTANCE_ATTENUATION; + Object.defineProperty( scope, 'distanceAttenuation', { + get() { - }, - set( val ) { + return scope._distanceAttenuation; - if ( scope._distanceAttenuation === val ) return; - scope._distanceAttenuation = val; - scope.material.defines.DISTANCE_ATTENUATION = val; - scope.material.needsUpdate = true; + }, + set( val ) { - } - } ); + if ( scope._distanceAttenuation === val ) return; + scope._distanceAttenuation = val; + scope.material.defines.DISTANCE_ATTENUATION = val; + scope.material.needsUpdate = true; - scope._fresnel = ReflectorForSSRPass.ReflectorShader.defines.FRESNEL; - Object.defineProperty( scope, 'fresnel', { - get() { + } + } ); - return scope._fresnel; + scope._fresnel = ReflectorForSSRPass.ReflectorShader.defines.FRESNEL; + Object.defineProperty( scope, 'fresnel', { + get() { - }, - set( val ) { + return scope._fresnel; - if ( scope._fresnel === val ) return; - scope._fresnel = val; - scope.material.defines.FRESNEL = val; - scope.material.needsUpdate = true; + }, + set( val ) { - } - } ); + if ( scope._fresnel === val ) return; + scope._fresnel = val; + scope.material.defines.FRESNEL = val; + scope.material.needsUpdate = true; - const normal = new Vector3(); - const reflectorWorldPosition = new Vector3(); - const cameraWorldPosition = new Vector3(); - const rotationMatrix = new Matrix4(); - const lookAtPosition = new Vector3( 0, 0, - 1 ); + } + } ); - const view = new Vector3(); - const target = new Vector3(); + const normal = new Vector3(); + const reflectorWorldPosition = new Vector3(); + const cameraWorldPosition = new Vector3(); + const rotationMatrix = new Matrix4(); + const lookAtPosition = new Vector3( 0, 0, - 1 ); - const textureMatrix = new Matrix4(); - const virtualCamera = new PerspectiveCamera(); + const view = new Vector3(); + const target = new Vector3(); - let depthTexture; + const textureMatrix = new Matrix4(); + const virtualCamera = new PerspectiveCamera(); - if ( useDepthTexture ) { + let depthTexture; - depthTexture = new DepthTexture(); - depthTexture.type = UnsignedShortType; - depthTexture.minFilter = NearestFilter; - depthTexture.magFilter = NearestFilter; + if ( useDepthTexture ) { - } + depthTexture = new DepthTexture(); + depthTexture.type = UnsignedShortType; + depthTexture.minFilter = NearestFilter; + depthTexture.magFilter = NearestFilter; - const parameters = { - depthTexture: useDepthTexture ? depthTexture : null, - type: HalfFloatType - }; + } - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters ); + const parameters = { + depthTexture: useDepthTexture ? depthTexture : null, + type: HalfFloatType + }; - const material = new ShaderMaterial( { - transparent: useDepthTexture, - defines: Object.assign( {}, ReflectorForSSRPass.ReflectorShader.defines, { - useDepthTexture - } ), - uniforms: UniformsUtils.clone( shader.uniforms ), - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader - } ); + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters ); - material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; - material.uniforms[ 'color' ].value = scope.color; - material.uniforms[ 'textureMatrix' ].value = textureMatrix; - if ( useDepthTexture ) { + const material = new ShaderMaterial( { + transparent: useDepthTexture, + defines: Object.assign( {}, ReflectorForSSRPass.ReflectorShader.defines, { + useDepthTexture + } ), + uniforms: UniformsUtils.clone( shader.uniforms ), + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader + } ); - material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture; + material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; + material.uniforms[ 'color' ].value = scope.color; + material.uniforms[ 'textureMatrix' ].value = textureMatrix; + if ( useDepthTexture ) { - } + material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture; - this.material = material; + } - const globalPlane = new Plane( new Vector3( 0, 1, 0 ), clipBias ); - const globalPlanes = [ globalPlane ]; + this.material = material; - this.doRender = function ( renderer, scene, camera ) { + const globalPlane = new Plane( new Vector3( 0, 1, 0 ), clipBias ); + const globalPlanes = [ globalPlane ]; - material.uniforms[ 'maxDistance' ].value = scope.maxDistance; - material.uniforms[ 'color' ].value = scope.color; - material.uniforms[ 'opacity' ].value = scope.opacity; + this.doRender = function ( renderer, scene, camera ) { - vecTemp0.copy( camera.position ).normalize(); - vecTemp1.copy( vecTemp0 ).reflect( yAxis ); - material.uniforms[ 'fresnelCoe' ].value = ( vecTemp0.dot( vecTemp1 ) + 1. ) / 2.; // TODO: Also need to use glsl viewPosition and viewNormal per pixel. + material.uniforms[ 'maxDistance' ].value = scope.maxDistance; + material.uniforms[ 'color' ].value = scope.color; + material.uniforms[ 'opacity' ].value = scope.opacity; - reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); - cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + vecTemp0.copy( camera.position ).normalize(); + vecTemp1.copy( vecTemp0 ).reflect( yAxis ); + material.uniforms[ 'fresnelCoe' ].value = ( vecTemp0.dot( vecTemp1 ) + 1. ) / 2.; // TODO: Also need to use glsl viewPosition and viewNormal per pixel. - rotationMatrix.extractRotation( scope.matrixWorld ); + reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); - normal.set( 0, 0, 1 ); - normal.applyMatrix4( rotationMatrix ); + rotationMatrix.extractRotation( scope.matrixWorld ); - view.subVectors( reflectorWorldPosition, cameraWorldPosition ); + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); - // Avoid rendering when reflector is facing away + view.subVectors( reflectorWorldPosition, cameraWorldPosition ); - if ( view.dot( normal ) > 0 ) return; + // Avoid rendering when reflector is facing away - view.reflect( normal ).negate(); - view.add( reflectorWorldPosition ); + if ( view.dot( normal ) > 0 ) return; - rotationMatrix.extractRotation( camera.matrixWorld ); + view.reflect( normal ).negate(); + view.add( reflectorWorldPosition ); - lookAtPosition.set( 0, 0, - 1 ); - lookAtPosition.applyMatrix4( rotationMatrix ); - lookAtPosition.add( cameraWorldPosition ); + rotationMatrix.extractRotation( camera.matrixWorld ); - target.subVectors( reflectorWorldPosition, lookAtPosition ); - target.reflect( normal ).negate(); - target.add( reflectorWorldPosition ); + lookAtPosition.set( 0, 0, - 1 ); + lookAtPosition.applyMatrix4( rotationMatrix ); + lookAtPosition.add( cameraWorldPosition ); - virtualCamera.position.copy( view ); - virtualCamera.up.set( 0, 1, 0 ); - virtualCamera.up.applyMatrix4( rotationMatrix ); - virtualCamera.up.reflect( normal ); - virtualCamera.lookAt( target ); + target.subVectors( reflectorWorldPosition, lookAtPosition ); + target.reflect( normal ).negate(); + target.add( reflectorWorldPosition ); - virtualCamera.far = camera.far; // Used in WebGLBackground + virtualCamera.position.copy( view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( rotationMatrix ); + virtualCamera.up.reflect( normal ); + virtualCamera.lookAt( target ); - virtualCamera.updateMatrixWorld(); - virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.far = camera.far; // Used in WebGLBackground - material.uniforms[ 'virtualCameraNear' ].value = camera.near; - material.uniforms[ 'virtualCameraFar' ].value = camera.far; - material.uniforms[ 'virtualCameraMatrixWorld' ].value = virtualCamera.matrixWorld; - material.uniforms[ 'virtualCameraProjectionMatrix' ].value = camera.projectionMatrix; - material.uniforms[ 'virtualCameraProjectionMatrixInverse' ].value = camera.projectionMatrixInverse; - material.uniforms[ 'resolution' ].value = scope.resolution; + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); - // Update the texture matrix - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - textureMatrix.multiply( virtualCamera.projectionMatrix ); - textureMatrix.multiply( virtualCamera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + material.uniforms[ 'virtualCameraNear' ].value = camera.near; + material.uniforms[ 'virtualCameraFar' ].value = camera.far; + material.uniforms[ 'virtualCameraMatrixWorld' ].value = virtualCamera.matrixWorld; + material.uniforms[ 'virtualCameraProjectionMatrix' ].value = camera.projectionMatrix; + material.uniforms[ 'virtualCameraProjectionMatrixInverse' ].value = camera.projectionMatrixInverse; + material.uniforms[ 'resolution' ].value = scope.resolution; - // scope.visible = false; + // Update the texture matrix + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + textureMatrix.multiply( virtualCamera.projectionMatrix ); + textureMatrix.multiply( virtualCamera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - const currentRenderTarget = renderer.getRenderTarget(); + // scope.visible = false; - const currentXrEnabled = renderer.xr.enabled; - const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - const currentClippingPlanes = renderer.clippingPlanes; + const currentRenderTarget = renderer.getRenderTarget(); - renderer.xr.enabled = false; // Avoid camera modification - renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows - renderer.clippingPlanes = globalPlanes; + const currentXrEnabled = renderer.xr.enabled; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + const currentClippingPlanes = renderer.clippingPlanes; - renderer.setRenderTarget( renderTarget ); + renderer.xr.enabled = false; // Avoid camera modification + renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows + renderer.clippingPlanes = globalPlanes; - renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 + renderer.setRenderTarget( renderTarget ); - if ( renderer.autoClear === false ) renderer.clear(); - renderer.render( scene, virtualCamera ); + renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 - renderer.xr.enabled = currentXrEnabled; - renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.clippingPlanes = currentClippingPlanes; + if ( renderer.autoClear === false ) renderer.clear(); + renderer.render( scene, virtualCamera ); - renderer.setRenderTarget( currentRenderTarget ); + renderer.xr.enabled = currentXrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + renderer.clippingPlanes = currentClippingPlanes; - // Restore viewport + renderer.setRenderTarget( currentRenderTarget ); - const viewport = camera.viewport; + // Restore viewport - if ( viewport !== undefined ) { + const viewport = camera.viewport; - renderer.state.viewport( viewport ); + if ( viewport !== undefined ) { - } + renderer.state.viewport( viewport ); - // scope.visible = true; + } - }; + // scope.visible = true; - this.getRenderTarget = function () { + }; - return renderTarget; + this.getRenderTarget = function () { - }; + return renderTarget; - } + }; -} - -const ReflectorShader = { - - defines: { - DISTANCE_ATTENUATION: true, - FRESNEL: true, - }, - - uniforms: { - - color: { value: null }, - tDiffuse: { value: null }, - tDepth: { value: null }, - textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, - maxDistance: { value: 180 }, - opacity: { value: 0.5 }, - fresnelCoe: { value: null }, - virtualCameraNear: { value: null }, - virtualCameraFar: { value: null }, - virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, - resolution: { value: /* @__PURE__ */ new Vector2() }, - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - uniform sampler2D tDepth; - uniform float maxDistance; - uniform float opacity; - uniform float fresnelCoe; - uniform float virtualCameraNear; - uniform float virtualCameraFar; - uniform mat4 virtualCameraProjectionMatrix; - uniform mat4 virtualCameraProjectionMatrixInverse; - uniform mat4 virtualCameraMatrixWorld; - uniform vec2 resolution; - varying vec4 vUv; - #include - float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } - vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } - float getDepth( const in vec2 uv ) { - return texture2D( tDepth, uv ).x; - } - float getViewZ( const in float depth ) { - return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); } - vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { - vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc - clipPosition *= clipW; //clip - return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view - } - void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - #ifdef useDepthTexture - vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; - uv.x=1.-uv.x; - float depth = texture2DProj( tDepth, vUv ).r; - float viewZ = getViewZ( depth ); - float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; - vec3 viewPosition=getViewPosition( uv, depth, clipW ); - vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; - if(worldPosition.y>maxDistance) discard; - float op=opacity; - #ifdef DISTANCE_ATTENUATION - float ratio=1.-(worldPosition.y/maxDistance); - float attenuation=ratio*ratio; - op=opacity*attenuation; - #endif - #ifdef FRESNEL - op*=fresnelCoe; + + } + + ReflectorForSSRPass.ReflectorShader = { + + defines: { + DISTANCE_ATTENUATION: true, + FRESNEL: true, + }, + + uniforms: { + + color: { value: null }, + tDiffuse: { value: null }, + tDepth: { value: null }, + textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, + maxDistance: { value: 180 }, + opacity: { value: 0.5 }, + fresnelCoe: { value: null }, + virtualCameraNear: { value: null }, + virtualCameraFar: { value: null }, + virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + uniform sampler2D tDepth; + uniform float maxDistance; + uniform float opacity; + uniform float fresnelCoe; + uniform float virtualCameraNear; + uniform float virtualCameraFar; + uniform mat4 virtualCameraProjectionMatrix; + uniform mat4 virtualCameraProjectionMatrixInverse; + uniform mat4 virtualCameraMatrixWorld; + uniform vec2 resolution; + varying vec4 vUv; + #include + float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } + vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } + float getDepth( const in vec2 uv ) { + return texture2D( tDepth, uv ).x; + } + float getViewZ( const in float depth ) { + return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); + } + vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { + vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc + clipPosition *= clipW; //clip + return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view + } + void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + #ifdef useDepthTexture + vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; + uv.x=1.-uv.x; + float depth = texture2DProj( tDepth, vUv ).r; + float viewZ = getViewZ( depth ); + float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; + vec3 viewPosition=getViewPosition( uv, depth, clipW ); + vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; + if(worldPosition.y>maxDistance) discard; + float op=opacity; + #ifdef DISTANCE_ATTENUATION + float ratio=1.-(worldPosition.y/maxDistance); + float attenuation=ratio*ratio; + op=opacity*attenuation; + #endif + #ifdef FRESNEL + op*=fresnelCoe; + #endif + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); + #else + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); #endif - gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); - #else - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #endif - } - `, -}; + } + `, + }; + + return ReflectorForSSRPass; -/* @__PURE__ */ Object.assign( ReflectorForSSRPass, { ReflectorShader } ); +} )(); export { ReflectorForSSRPass }; diff --git a/examples/jsm/objects/Refractor.js b/examples/jsm/objects/Refractor.js index ec2938e2d07ef5..2985702ba4a48d 100644 --- a/examples/jsm/objects/Refractor.js +++ b/examples/jsm/objects/Refractor.js @@ -13,314 +13,318 @@ import { HalfFloatType } from 'three'; -class Refractor extends Mesh { +const Refractor = /* @__PURE__ */ ( () => { - constructor( geometry, options = {} ) { + class Refractor extends Mesh { - super( geometry ); + constructor( geometry, options = {} ) { - this.isRefractor = true; + super( geometry ); - this.type = 'Refractor'; - this.camera = new PerspectiveCamera(); + this.isRefractor = true; - const scope = this; + this.type = 'Refractor'; + this.camera = new PerspectiveCamera(); - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const shader = options.shader || Refractor.RefractorShader; - const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; + const scope = this; - // + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const shader = options.shader || Refractor.RefractorShader; + const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; - const virtualCamera = this.camera; - virtualCamera.matrixAutoUpdate = false; - virtualCamera.userData.refractor = true; + // - // + const virtualCamera = this.camera; + virtualCamera.matrixAutoUpdate = false; + virtualCamera.userData.refractor = true; - const refractorPlane = new Plane(); - const textureMatrix = new Matrix4(); + // - // render target + const refractorPlane = new Plane(); + const textureMatrix = new Matrix4(); - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); + // render target - // material + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); - this.material = new ShaderMaterial( { - uniforms: UniformsUtils.clone( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - transparent: true // ensures, refractors are drawn from farthest to closest - } ); + // material - this.material.uniforms[ 'color' ].value = color; - this.material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; - this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; + this.material = new ShaderMaterial( { + uniforms: UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + transparent: true // ensures, refractors are drawn from farthest to closest + } ); - // functions + this.material.uniforms[ 'color' ].value = color; + this.material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; + this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; - const visible = ( function () { + // functions - const refractorWorldPosition = new Vector3(); - const cameraWorldPosition = new Vector3(); - const rotationMatrix = new Matrix4(); + const visible = ( function () { - const view = new Vector3(); - const normal = new Vector3(); + const refractorWorldPosition = new Vector3(); + const cameraWorldPosition = new Vector3(); + const rotationMatrix = new Matrix4(); - return function visible( camera ) { + const view = new Vector3(); + const normal = new Vector3(); - refractorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); - cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + return function visible( camera ) { - view.subVectors( refractorWorldPosition, cameraWorldPosition ); + refractorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); - rotationMatrix.extractRotation( scope.matrixWorld ); + view.subVectors( refractorWorldPosition, cameraWorldPosition ); - normal.set( 0, 0, 1 ); - normal.applyMatrix4( rotationMatrix ); + rotationMatrix.extractRotation( scope.matrixWorld ); - return view.dot( normal ) < 0; + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); - }; + return view.dot( normal ) < 0; - } )(); + }; - const updateRefractorPlane = ( function () { + } )(); - const normal = new Vector3(); - const position = new Vector3(); - const quaternion = new Quaternion(); - const scale = new Vector3(); + const updateRefractorPlane = ( function () { - return function updateRefractorPlane() { + const normal = new Vector3(); + const position = new Vector3(); + const quaternion = new Quaternion(); + const scale = new Vector3(); - scope.matrixWorld.decompose( position, quaternion, scale ); - normal.set( 0, 0, 1 ).applyQuaternion( quaternion ).normalize(); + return function updateRefractorPlane() { - // flip the normal because we want to cull everything above the plane + scope.matrixWorld.decompose( position, quaternion, scale ); + normal.set( 0, 0, 1 ).applyQuaternion( quaternion ).normalize(); - normal.negate(); + // flip the normal because we want to cull everything above the plane - refractorPlane.setFromNormalAndCoplanarPoint( normal, position ); + normal.negate(); - }; + refractorPlane.setFromNormalAndCoplanarPoint( normal, position ); - } )(); + }; - const updateVirtualCamera = ( function () { + } )(); - const clipPlane = new Plane(); - const clipVector = new Vector4(); - const q = new Vector4(); + const updateVirtualCamera = ( function () { - return function updateVirtualCamera( camera ) { + const clipPlane = new Plane(); + const clipVector = new Vector4(); + const q = new Vector4(); - virtualCamera.matrixWorld.copy( camera.matrixWorld ); - virtualCamera.matrixWorldInverse.copy( virtualCamera.matrixWorld ).invert(); - virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); - virtualCamera.far = camera.far; // used in WebGLBackground + return function updateVirtualCamera( camera ) { - // The following code creates an oblique view frustum for clipping. - // see: Lengyel, Eric. “Oblique View Frustum Depth Projection and Clipping”. - // Journal of Game Development, Vol. 1, No. 2 (2005), Charles River Media, pp. 5–16 + virtualCamera.matrixWorld.copy( camera.matrixWorld ); + virtualCamera.matrixWorldInverse.copy( virtualCamera.matrixWorld ).invert(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.far = camera.far; // used in WebGLBackground - clipPlane.copy( refractorPlane ); - clipPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + // The following code creates an oblique view frustum for clipping. + // see: Lengyel, Eric. “Oblique View Frustum Depth Projection and Clipping”. + // Journal of Game Development, Vol. 1, No. 2 (2005), Charles River Media, pp. 5–16 - clipVector.set( clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant ); + clipPlane.copy( refractorPlane ); + clipPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); - // calculate the clip-space corner point opposite the clipping plane and - // transform it into camera space by multiplying it by the inverse of the projection matrix + clipVector.set( clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant ); - const projectionMatrix = virtualCamera.projectionMatrix; + // calculate the clip-space corner point opposite the clipping plane and + // transform it into camera space by multiplying it by the inverse of the projection matrix - q.x = ( Math.sign( clipVector.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; - q.y = ( Math.sign( clipVector.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; - q.z = - 1.0; - q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + const projectionMatrix = virtualCamera.projectionMatrix; - // calculate the scaled plane vector + q.x = ( Math.sign( clipVector.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + q.y = ( Math.sign( clipVector.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + q.z = - 1.0; + q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; - clipVector.multiplyScalar( 2.0 / clipVector.dot( q ) ); + // calculate the scaled plane vector - // replacing the third row of the projection matrix + clipVector.multiplyScalar( 2.0 / clipVector.dot( q ) ); - projectionMatrix.elements[ 2 ] = clipVector.x; - projectionMatrix.elements[ 6 ] = clipVector.y; - projectionMatrix.elements[ 10 ] = clipVector.z + 1.0 - clipBias; - projectionMatrix.elements[ 14 ] = clipVector.w; + // replacing the third row of the projection matrix - }; + projectionMatrix.elements[ 2 ] = clipVector.x; + projectionMatrix.elements[ 6 ] = clipVector.y; + projectionMatrix.elements[ 10 ] = clipVector.z + 1.0 - clipBias; + projectionMatrix.elements[ 14 ] = clipVector.w; - } )(); + }; - // This will update the texture matrix that is used for projective texture mapping in the shader. - // see: http://developer.download.nvidia.com/assets/gamedev/docs/projective_texture_mapping.pdf + } )(); - function updateTextureMatrix( camera ) { + // This will update the texture matrix that is used for projective texture mapping in the shader. + // see: http://developer.download.nvidia.com/assets/gamedev/docs/projective_texture_mapping.pdf - // this matrix does range mapping to [ 0, 1 ] + function updateTextureMatrix( camera ) { - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + // this matrix does range mapping to [ 0, 1 ] - // we use "Object Linear Texgen", so we need to multiply the texture matrix T - // (matrix above) with the projection and view matrix of the virtual camera - // and the model matrix of the refractor + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); - textureMatrix.multiply( camera.projectionMatrix ); - textureMatrix.multiply( camera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + // we use "Object Linear Texgen", so we need to multiply the texture matrix T + // (matrix above) with the projection and view matrix of the virtual camera + // and the model matrix of the refractor - } + textureMatrix.multiply( camera.projectionMatrix ); + textureMatrix.multiply( camera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - // + } - function render( renderer, scene, camera ) { + // - scope.visible = false; + function render( renderer, scene, camera ) { - const currentRenderTarget = renderer.getRenderTarget(); - const currentXrEnabled = renderer.xr.enabled; - const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + scope.visible = false; - renderer.xr.enabled = false; // avoid camera modification - renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows + const currentRenderTarget = renderer.getRenderTarget(); + const currentXrEnabled = renderer.xr.enabled; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - renderer.setRenderTarget( renderTarget ); - if ( renderer.autoClear === false ) renderer.clear(); - renderer.render( scene, virtualCamera ); + renderer.xr.enabled = false; // avoid camera modification + renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows - renderer.xr.enabled = currentXrEnabled; - renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.setRenderTarget( currentRenderTarget ); + renderer.setRenderTarget( renderTarget ); + if ( renderer.autoClear === false ) renderer.clear(); + renderer.render( scene, virtualCamera ); - // restore viewport + renderer.xr.enabled = currentXrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + renderer.setRenderTarget( currentRenderTarget ); - const viewport = camera.viewport; + // restore viewport - if ( viewport !== undefined ) { + const viewport = camera.viewport; - renderer.state.viewport( viewport ); + if ( viewport !== undefined ) { - } + renderer.state.viewport( viewport ); - scope.visible = true; + } - } + scope.visible = true; + + } - // + // - this.onBeforeRender = function ( renderer, scene, camera ) { + this.onBeforeRender = function ( renderer, scene, camera ) { - // ensure refractors are rendered only once per frame + // ensure refractors are rendered only once per frame - if ( camera.userData.refractor === true ) return; + if ( camera.userData.refractor === true ) return; - // avoid rendering when the refractor is viewed from behind + // avoid rendering when the refractor is viewed from behind - if ( ! visible( camera ) === true ) return; + if ( ! visible( camera ) === true ) return; - // update + // update - updateRefractorPlane(); + updateRefractorPlane(); - updateTextureMatrix( camera ); + updateTextureMatrix( camera ); - updateVirtualCamera( camera ); + updateVirtualCamera( camera ); - render( renderer, scene, camera ); + render( renderer, scene, camera ); - }; + }; - this.getRenderTarget = function () { + this.getRenderTarget = function () { - return renderTarget; + return renderTarget; - }; + }; - this.dispose = function () { + this.dispose = function () { - renderTarget.dispose(); - scope.material.dispose(); + renderTarget.dispose(); + scope.material.dispose(); - }; + }; + + } } -} + Refractor.RefractorShader = { -const RefractorShader = { + uniforms: { - uniforms: { + 'color': { + value: null + }, - 'color': { - value: null - }, + 'tDiffuse': { + value: null + }, - 'tDiffuse': { - value: null - }, + 'textureMatrix': { + value: null + } - 'textureMatrix': { - value: null - } + }, - }, + vertexShader: /* glsl */` - vertexShader: /* glsl */` + uniform mat4 textureMatrix; - uniform mat4 textureMatrix; + varying vec4 vUv; - varying vec4 vUv; + void main() { - void main() { + vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, - }`, + fragmentShader: /* glsl */` - fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; - uniform vec3 color; - uniform sampler2D tDiffuse; + varying vec4 vUv; - varying vec4 vUv; + float blendOverlay( float base, float blend ) { - float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } - } + vec3 blendOverlay( vec3 base, vec3 blend ) { - vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } - } + void main() { - void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #include + #include - #include - #include + }` - }` + }; -}; + return Refractor; -/* @__PURE__ */ Object.assign( Refractor, { RefractorShader } ); +} )(); export { Refractor }; diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index d441ec827d81af..c41cc4bc7b8bab 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -21,199 +21,203 @@ import { * Three.js integration by zz85 http://twitter.com/blurspline */ -class Sky extends Mesh { +const Sky = /* @__PURE__ */ ( () => { - constructor() { + class Sky extends Mesh { - const shader = Sky.SkyShader; + constructor() { - const material = new ShaderMaterial( { - name: 'SkyShader', - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, - uniforms: UniformsUtils.clone( shader.uniforms ), - side: BackSide, - depthWrite: false - } ); + const shader = Sky.SkyShader; - super( new BoxGeometry( 1, 1, 1 ), material ); + const material = new ShaderMaterial( { + name: 'SkyShader', + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: UniformsUtils.clone( shader.uniforms ), + side: BackSide, + depthWrite: false + } ); - this.isSky = true; + super( new BoxGeometry( 1, 1, 1 ), material ); - } + this.isSky = true; -} - -const SkyShader = { - - uniforms: { - 'turbidity': { value: 2 }, - 'rayleigh': { value: 1 }, - 'mieCoefficient': { value: 0.005 }, - 'mieDirectionalG': { value: 0.8 }, - 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, - 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } - }, - - vertexShader: /* glsl */` - uniform vec3 sunPosition; - uniform float rayleigh; - uniform float turbidity; - uniform float mieCoefficient; - uniform vec3 up; - - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; - - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); } - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } + } - void main() { + Sky.SkyShader = { - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; + uniforms: { + 'turbidity': { value: 2 }, + 'rayleigh': { value: 1 }, + 'mieCoefficient': { value: 0.005 }, + 'mieDirectionalG': { value: 0.8 }, + 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, + 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } + }, + + vertexShader: /* glsl */` + uniform vec3 sunPosition; + uniform float rayleigh; + uniform float turbidity; + uniform float mieCoefficient; + uniform vec3 up; + + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; + + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); + + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - vSunDirection = normalize( sunPosition ); + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; - vSunE = sunIntensity( dot( vSunDirection, up ) ); + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); + void main() { - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - }`, + vSunDirection = normalize( sunPosition ); - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; + vSunE = sunIntensity( dot( vSunDirection, up ) ); - uniform float mieDirectionalG; - uniform vec3 up; + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; + }`, - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } + uniform float mieDirectionalG; + uniform vec3 up; + + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; + + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) + + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; + + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; + + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); + } + + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } + + void main() { - void main() { + vec3 direction = normalize( vWorldPosition - cameraPosition ); - vec3 direction = normalize( vWorldPosition - cameraPosition ); + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); + // in scattering + float cosTheta = dot( direction, vSunDirection ); - // in scattering - float cosTheta = dot( direction, vSunDirection ); + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); + gl_FragColor = vec4( retColor, 1.0 ); - gl_FragColor = vec4( retColor, 1.0 ); + #include + #include - #include - #include + }` - }` + }; -}; + return Sky; -/* @__PURE__ */ Object.assign( Sky, { SkyShader } ); +} )(); export { Sky }; diff --git a/examples/jsm/objects/Water2.js b/examples/jsm/objects/Water2.js index de0fd4351ec977..6928c4c289bbcc 100644 --- a/examples/jsm/objects/Water2.js +++ b/examples/jsm/objects/Water2.js @@ -21,340 +21,344 @@ import { Refractor } from '../objects/Refractor.js'; * */ -class Water extends Mesh { +const Water = /* @__PURE__ */ ( () => { - constructor( geometry, options = {} ) { + class Water extends Mesh { - super( geometry ); + constructor( geometry, options = {} ) { - this.isWater = true; + super( geometry ); - this.type = 'Water'; + this.isWater = true; - const scope = this; + this.type = 'Water'; - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const flowDirection = options.flowDirection || new Vector2( 1, 0 ); - const flowSpeed = options.flowSpeed || 0.03; - const reflectivity = options.reflectivity || 0.02; - const scale = options.scale || 1; - const shader = options.shader || Water.WaterShader; + const scope = this; - const textureLoader = new TextureLoader(); + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const flowDirection = options.flowDirection || new Vector2( 1, 0 ); + const flowSpeed = options.flowSpeed || 0.03; + const reflectivity = options.reflectivity || 0.02; + const scale = options.scale || 1; + const shader = options.shader || Water.WaterShader; - const flowMap = options.flowMap || undefined; - const normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' ); - const normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' ); + const textureLoader = new TextureLoader(); - const cycle = 0.15; // a cycle of a flow map phase - const halfCycle = cycle * 0.5; - const textureMatrix = new Matrix4(); - const clock = new Clock(); + const flowMap = options.flowMap || undefined; + const normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' ); + const normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' ); - // internal components + const cycle = 0.15; // a cycle of a flow map phase + const halfCycle = cycle * 0.5; + const textureMatrix = new Matrix4(); + const clock = new Clock(); - if ( Reflector === undefined ) { + // internal components - console.error( 'THREE.Water: Required component Reflector not found.' ); - return; + if ( Reflector === undefined ) { - } + console.error( 'THREE.Water: Required component Reflector not found.' ); + return; - if ( Refractor === undefined ) { + } - console.error( 'THREE.Water: Required component Refractor not found.' ); - return; + if ( Refractor === undefined ) { - } + console.error( 'THREE.Water: Required component Refractor not found.' ); + return; - const reflector = new Reflector( geometry, { - textureWidth: textureWidth, - textureHeight: textureHeight, - clipBias: clipBias - } ); - - const refractor = new Refractor( geometry, { - textureWidth: textureWidth, - textureHeight: textureHeight, - clipBias: clipBias - } ); - - reflector.matrixAutoUpdate = false; - refractor.matrixAutoUpdate = false; - - // material - - this.material = new ShaderMaterial( { - uniforms: UniformsUtils.merge( [ - UniformsLib[ 'fog' ], - shader.uniforms - ] ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - transparent: true, - fog: true - } ); - - if ( flowMap !== undefined ) { - - this.material.defines.USE_FLOWMAP = ''; - this.material.uniforms[ 'tFlowMap' ] = { - type: 't', - value: flowMap - }; + } - } else { + const reflector = new Reflector( geometry, { + textureWidth: textureWidth, + textureHeight: textureHeight, + clipBias: clipBias + } ); + + const refractor = new Refractor( geometry, { + textureWidth: textureWidth, + textureHeight: textureHeight, + clipBias: clipBias + } ); + + reflector.matrixAutoUpdate = false; + refractor.matrixAutoUpdate = false; + + // material + + this.material = new ShaderMaterial( { + uniforms: UniformsUtils.merge( [ + UniformsLib[ 'fog' ], + shader.uniforms + ] ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + transparent: true, + fog: true + } ); + + if ( flowMap !== undefined ) { + + this.material.defines.USE_FLOWMAP = ''; + this.material.uniforms[ 'tFlowMap' ] = { + type: 't', + value: flowMap + }; + + } else { + + this.material.uniforms[ 'flowDirection' ] = { + type: 'v2', + value: flowDirection + }; - this.material.uniforms[ 'flowDirection' ] = { - type: 'v2', - value: flowDirection - }; + } - } + // maps - // maps + normalMap0.wrapS = normalMap0.wrapT = RepeatWrapping; + normalMap1.wrapS = normalMap1.wrapT = RepeatWrapping; - normalMap0.wrapS = normalMap0.wrapT = RepeatWrapping; - normalMap1.wrapS = normalMap1.wrapT = RepeatWrapping; + this.material.uniforms[ 'tReflectionMap' ].value = reflector.getRenderTarget().texture; + this.material.uniforms[ 'tRefractionMap' ].value = refractor.getRenderTarget().texture; + this.material.uniforms[ 'tNormalMap0' ].value = normalMap0; + this.material.uniforms[ 'tNormalMap1' ].value = normalMap1; - this.material.uniforms[ 'tReflectionMap' ].value = reflector.getRenderTarget().texture; - this.material.uniforms[ 'tRefractionMap' ].value = refractor.getRenderTarget().texture; - this.material.uniforms[ 'tNormalMap0' ].value = normalMap0; - this.material.uniforms[ 'tNormalMap1' ].value = normalMap1; + // water - // water + this.material.uniforms[ 'color' ].value = color; + this.material.uniforms[ 'reflectivity' ].value = reflectivity; + this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; - this.material.uniforms[ 'color' ].value = color; - this.material.uniforms[ 'reflectivity' ].value = reflectivity; - this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; + // inital values - // inital values + this.material.uniforms[ 'config' ].value.x = 0; // flowMapOffset0 + this.material.uniforms[ 'config' ].value.y = halfCycle; // flowMapOffset1 + this.material.uniforms[ 'config' ].value.z = halfCycle; // halfCycle + this.material.uniforms[ 'config' ].value.w = scale; // scale - this.material.uniforms[ 'config' ].value.x = 0; // flowMapOffset0 - this.material.uniforms[ 'config' ].value.y = halfCycle; // flowMapOffset1 - this.material.uniforms[ 'config' ].value.z = halfCycle; // halfCycle - this.material.uniforms[ 'config' ].value.w = scale; // scale + // functions - // functions + function updateTextureMatrix( camera ) { - function updateTextureMatrix( camera ) { + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + textureMatrix.multiply( camera.projectionMatrix ); + textureMatrix.multiply( camera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - textureMatrix.multiply( camera.projectionMatrix ); - textureMatrix.multiply( camera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + } - } + function updateFlow() { - function updateFlow() { + const delta = clock.getDelta(); + const config = scope.material.uniforms[ 'config' ]; - const delta = clock.getDelta(); - const config = scope.material.uniforms[ 'config' ]; + config.value.x += flowSpeed * delta; // flowMapOffset0 + config.value.y = config.value.x + halfCycle; // flowMapOffset1 - config.value.x += flowSpeed * delta; // flowMapOffset0 - config.value.y = config.value.x + halfCycle; // flowMapOffset1 + // Important: The distance between offsets should be always the value of "halfCycle". + // Moreover, both offsets should be in the range of [ 0, cycle ]. + // This approach ensures a smooth water flow and avoids "reset" effects. - // Important: The distance between offsets should be always the value of "halfCycle". - // Moreover, both offsets should be in the range of [ 0, cycle ]. - // This approach ensures a smooth water flow and avoids "reset" effects. + if ( config.value.x >= cycle ) { - if ( config.value.x >= cycle ) { + config.value.x = 0; + config.value.y = halfCycle; - config.value.x = 0; - config.value.y = halfCycle; + } else if ( config.value.y >= cycle ) { - } else if ( config.value.y >= cycle ) { + config.value.y = config.value.y - cycle; - config.value.y = config.value.y - cycle; + } } - } + // - // + this.onBeforeRender = function ( renderer, scene, camera ) { - this.onBeforeRender = function ( renderer, scene, camera ) { + updateTextureMatrix( camera ); + updateFlow(); - updateTextureMatrix( camera ); - updateFlow(); + scope.visible = false; - scope.visible = false; + reflector.matrixWorld.copy( scope.matrixWorld ); + refractor.matrixWorld.copy( scope.matrixWorld ); - reflector.matrixWorld.copy( scope.matrixWorld ); - refractor.matrixWorld.copy( scope.matrixWorld ); + reflector.onBeforeRender( renderer, scene, camera ); + refractor.onBeforeRender( renderer, scene, camera ); - reflector.onBeforeRender( renderer, scene, camera ); - refractor.onBeforeRender( renderer, scene, camera ); + scope.visible = true; - scope.visible = true; + }; - }; + } } -} + Water.WaterShader = { -const WaterShader = { + uniforms: { - uniforms: { + 'color': { + type: 'c', + value: null + }, - 'color': { - type: 'c', - value: null - }, + 'reflectivity': { + type: 'f', + value: 0 + }, - 'reflectivity': { - type: 'f', - value: 0 - }, + 'tReflectionMap': { + type: 't', + value: null + }, - 'tReflectionMap': { - type: 't', - value: null - }, + 'tRefractionMap': { + type: 't', + value: null + }, - 'tRefractionMap': { - type: 't', - value: null - }, + 'tNormalMap0': { + type: 't', + value: null + }, - 'tNormalMap0': { - type: 't', - value: null - }, + 'tNormalMap1': { + type: 't', + value: null + }, - 'tNormalMap1': { - type: 't', - value: null - }, + 'textureMatrix': { + type: 'm4', + value: null + }, - 'textureMatrix': { - type: 'm4', - value: null - }, + 'config': { + type: 'v4', + value: /* @__PURE__ */ new Vector4() + } - 'config': { - type: 'v4', - value: /* @__PURE__ */ new Vector4() - } + }, - }, + vertexShader: /* glsl */` - vertexShader: /* glsl */` + #include + #include + #include - #include - #include - #include + uniform mat4 textureMatrix; - uniform mat4 textureMatrix; + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; + void main() { - void main() { + vUv = uv; + vCoord = textureMatrix * vec4( position, 1.0 ); - vUv = uv; - vCoord = textureMatrix * vec4( position, 1.0 ); + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vToEye = cameraPosition - worldPosition.xyz; - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vToEye = cameraPosition - worldPosition.xyz; + vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex + gl_Position = projectionMatrix * mvPosition; - vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex - gl_Position = projectionMatrix * mvPosition; + #include + #include - #include - #include + }`, - }`, + fragmentShader: /* glsl */` - fragmentShader: /* glsl */` + #include + #include + #include - #include - #include - #include + uniform sampler2D tReflectionMap; + uniform sampler2D tRefractionMap; + uniform sampler2D tNormalMap0; + uniform sampler2D tNormalMap1; - uniform sampler2D tReflectionMap; - uniform sampler2D tRefractionMap; - uniform sampler2D tNormalMap0; - uniform sampler2D tNormalMap1; + #ifdef USE_FLOWMAP + uniform sampler2D tFlowMap; + #else + uniform vec2 flowDirection; + #endif - #ifdef USE_FLOWMAP - uniform sampler2D tFlowMap; - #else - uniform vec2 flowDirection; - #endif + uniform vec3 color; + uniform float reflectivity; + uniform vec4 config; - uniform vec3 color; - uniform float reflectivity; - uniform vec4 config; + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; + void main() { - void main() { + #include - #include + float flowMapOffset0 = config.x; + float flowMapOffset1 = config.y; + float halfCycle = config.z; + float scale = config.w; - float flowMapOffset0 = config.x; - float flowMapOffset1 = config.y; - float halfCycle = config.z; - float scale = config.w; + vec3 toEye = normalize( vToEye ); - vec3 toEye = normalize( vToEye ); + // determine flow direction + vec2 flow; + #ifdef USE_FLOWMAP + flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; + #else + flow = flowDirection; + #endif + flow.x *= - 1.0; - // determine flow direction - vec2 flow; - #ifdef USE_FLOWMAP - flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; - #else - flow = flowDirection; - #endif - flow.x *= - 1.0; + // sample normal maps (distort uvs with flowdata) + vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); + vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); - // sample normal maps (distort uvs with flowdata) - vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); - vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); + // linear interpolate to get the final normal color + float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; + vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); - // linear interpolate to get the final normal color - float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; - vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); + // calculate normal vector + vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); - // calculate normal vector - vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); + // calculate the fresnel term to blend reflection and refraction maps + float theta = max( dot( toEye, normal ), 0.0 ); + float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); - // calculate the fresnel term to blend reflection and refraction maps - float theta = max( dot( toEye, normal ), 0.0 ); - float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); + // calculate final uv coords + vec3 coord = vCoord.xyz / vCoord.w; + vec2 uv = coord.xy + coord.z * normal.xz * 0.05; - // calculate final uv coords - vec3 coord = vCoord.xyz / vCoord.w; - vec2 uv = coord.xy + coord.z * normal.xz * 0.05; + vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); + vec4 refractColor = texture2D( tRefractionMap, uv ); - vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); - vec4 refractColor = texture2D( tRefractionMap, uv ); + // multiply water color with the mix of both textures + gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); - // multiply water color with the mix of both textures - gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); + #include + #include + #include - #include - #include - #include + }` - }` + }; -}; + return Water; -/* @__PURE__ */ Object.assign( Water, { WaterShader } ); +} )(); export { Water }; diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index c2e66c5c02736f..19b284ae920f63 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -9,166 +9,170 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { ConvolutionShader } from '../shaders/ConvolutionShader.js'; -class BloomPass extends Pass { +const BloomPass = /* @__PURE__ */ ( () => { - constructor( strength = 1, kernelSize = 25, sigma = 4 ) { + class BloomPass extends Pass { - super(); + constructor( strength = 1, kernelSize = 25, sigma = 4 ) { - // render targets + super(); - this.renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later - this.renderTargetX.texture.name = 'BloomPass.x'; - this.renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later - this.renderTargetY.texture.name = 'BloomPass.y'; + // render targets - // combine material + this.renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this.renderTargetX.texture.name = 'BloomPass.x'; + this.renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this.renderTargetY.texture.name = 'BloomPass.y'; - this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms ); + // combine material - this.combineUniforms[ 'strength' ].value = strength; + this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms ); - this.materialCombine = new ShaderMaterial( { + this.combineUniforms[ 'strength' ].value = strength; - name: CombineShader.name, - uniforms: this.combineUniforms, - vertexShader: CombineShader.vertexShader, - fragmentShader: CombineShader.fragmentShader, - blending: AdditiveBlending, - transparent: true + this.materialCombine = new ShaderMaterial( { - } ); + name: CombineShader.name, + uniforms: this.combineUniforms, + vertexShader: CombineShader.vertexShader, + fragmentShader: CombineShader.fragmentShader, + blending: AdditiveBlending, + transparent: true - // convolution material + } ); - const convolutionShader = ConvolutionShader; + // convolution material - this.convolutionUniforms = UniformsUtils.clone( convolutionShader.uniforms ); + const convolutionShader = ConvolutionShader; - this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; - this.convolutionUniforms[ 'cKernel' ].value = ConvolutionShader.buildKernel( sigma ); + this.convolutionUniforms = UniformsUtils.clone( convolutionShader.uniforms ); - this.materialConvolution = new ShaderMaterial( { + this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; + this.convolutionUniforms[ 'cKernel' ].value = ConvolutionShader.buildKernel( sigma ); - name: convolutionShader.name, - uniforms: this.convolutionUniforms, - vertexShader: convolutionShader.vertexShader, - fragmentShader: convolutionShader.fragmentShader, - defines: { - 'KERNEL_SIZE_FLOAT': kernelSize.toFixed( 1 ), - 'KERNEL_SIZE_INT': kernelSize.toFixed( 0 ) - } + this.materialConvolution = new ShaderMaterial( { - } ); + name: convolutionShader.name, + uniforms: this.convolutionUniforms, + vertexShader: convolutionShader.vertexShader, + fragmentShader: convolutionShader.fragmentShader, + defines: { + 'KERNEL_SIZE_FLOAT': kernelSize.toFixed( 1 ), + 'KERNEL_SIZE_INT': kernelSize.toFixed( 0 ) + } - this.needsSwap = false; + } ); - this.fsQuad = new FullScreenQuad( null ); + this.needsSwap = false; - } + this.fsQuad = new FullScreenQuad( null ); - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + } - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - // Render quad with blured scene into texture (convolution pass 1) + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - this.fsQuad.material = this.materialConvolution; + // Render quad with blured scene into texture (convolution pass 1) - this.convolutionUniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; + this.fsQuad.material = this.materialConvolution; - renderer.setRenderTarget( this.renderTargetX ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.convolutionUniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; + renderer.setRenderTarget( this.renderTargetX ); + renderer.clear(); + this.fsQuad.render( renderer ); - // Render quad with blured scene into texture (convolution pass 2) - this.convolutionUniforms[ 'tDiffuse' ].value = this.renderTargetX.texture; - this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurY; + // Render quad with blured scene into texture (convolution pass 2) - renderer.setRenderTarget( this.renderTargetY ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.convolutionUniforms[ 'tDiffuse' ].value = this.renderTargetX.texture; + this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurY; - // Render original scene with superimposed blur to texture + renderer.setRenderTarget( this.renderTargetY ); + renderer.clear(); + this.fsQuad.render( renderer ); - this.fsQuad.material = this.materialCombine; + // Render original scene with superimposed blur to texture - this.combineUniforms[ 'tDiffuse' ].value = this.renderTargetY.texture; + this.fsQuad.material = this.materialCombine; - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + this.combineUniforms[ 'tDiffuse' ].value = this.renderTargetY.texture; - renderer.setRenderTarget( readBuffer ); - if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - } + renderer.setRenderTarget( readBuffer ); + if ( this.clear ) renderer.clear(); + this.fsQuad.render( renderer ); - setSize( width, height ) { + } - this.renderTargetX.setSize( width, height ); - this.renderTargetY.setSize( width, height ); + setSize( width, height ) { - } + this.renderTargetX.setSize( width, height ); + this.renderTargetY.setSize( width, height ); + + } + + dispose() { - dispose() { + this.renderTargetX.dispose(); + this.renderTargetY.dispose(); - this.renderTargetX.dispose(); - this.renderTargetY.dispose(); + this.materialCombine.dispose(); + this.materialConvolution.dispose(); - this.materialCombine.dispose(); - this.materialConvolution.dispose(); + this.fsQuad.dispose(); - this.fsQuad.dispose(); + } } -} + const CombineShader = { -const CombineShader = { + name: 'CombineShader', - name: 'CombineShader', + uniforms: { - uniforms: { + 'tDiffuse': { value: null }, + 'strength': { value: 1.0 } - 'tDiffuse': { value: null }, - 'strength': { value: 1.0 } + }, - }, + vertexShader: /* glsl */` - vertexShader: /* glsl */` + varying vec2 vUv; - varying vec2 vUv; + void main() { - void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, - }`, + fragmentShader: /* glsl */` - fragmentShader: /* glsl */` + uniform float strength; - uniform float strength; + uniform sampler2D tDiffuse; - uniform sampler2D tDiffuse; + varying vec2 vUv; - varying vec2 vUv; + void main() { - void main() { + vec4 texel = texture2D( tDiffuse, vUv ); + gl_FragColor = strength * texel; - vec4 texel = texture2D( tDiffuse, vUv ); - gl_FragColor = strength * texel; + }` - }` + }; -}; + BloomPass.blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); + BloomPass.blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); -const blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); -const blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); + return BloomPass; -/* @__PURE__ */ Object.assign( BloomPass, { blurX, blurY } ); +} )(); export { BloomPass }; diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index 419b89964ddcf1..df855293233acb 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -16,641 +16,645 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; -class OutlinePass extends Pass { +const OutlinePass = /* @__PURE__ */ ( () => { - constructor( resolution, scene, camera, selectedObjects ) { + class OutlinePass extends Pass { - super(); + constructor( resolution, scene, camera, selectedObjects ) { - this.renderScene = scene; - this.renderCamera = camera; - this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; - this.visibleEdgeColor = new Color( 1, 1, 1 ); - this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 ); - this.edgeGlow = 0.0; - this.usePatternTexture = false; - this.edgeThickness = 1.0; - this.edgeStrength = 3.0; - this.downSampleRatio = 2; - this.pulsePeriod = 0; + super(); - this._visibilityCache = new Map(); + this.renderScene = scene; + this.renderCamera = camera; + this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; + this.visibleEdgeColor = new Color( 1, 1, 1 ); + this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 ); + this.edgeGlow = 0.0; + this.usePatternTexture = false; + this.edgeThickness = 1.0; + this.edgeStrength = 3.0; + this.downSampleRatio = 2; + this.pulsePeriod = 0; + this._visibilityCache = new Map(); - this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - const resx = Math.round( this.resolution.x / this.downSampleRatio ); - const resy = Math.round( this.resolution.y / this.downSampleRatio ); + this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - this.renderTargetMaskBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y ); - this.renderTargetMaskBuffer.texture.name = 'OutlinePass.mask'; - this.renderTargetMaskBuffer.texture.generateMipmaps = false; + const resx = Math.round( this.resolution.x / this.downSampleRatio ); + const resy = Math.round( this.resolution.y / this.downSampleRatio ); - this.depthMaterial = new MeshDepthMaterial(); - this.depthMaterial.side = DoubleSide; - this.depthMaterial.depthPacking = RGBADepthPacking; - this.depthMaterial.blending = NoBlending; + this.renderTargetMaskBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y ); + this.renderTargetMaskBuffer.texture.name = 'OutlinePass.mask'; + this.renderTargetMaskBuffer.texture.generateMipmaps = false; - this.prepareMaskMaterial = this.getPrepareMaskMaterial(); - this.prepareMaskMaterial.side = DoubleSide; - this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); + this.depthMaterial = new MeshDepthMaterial(); + this.depthMaterial.side = DoubleSide; + this.depthMaterial.depthPacking = RGBADepthPacking; + this.depthMaterial.blending = NoBlending; - this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); - this.renderTargetDepthBuffer.texture.name = 'OutlinePass.depth'; - this.renderTargetDepthBuffer.texture.generateMipmaps = false; + this.prepareMaskMaterial = this.getPrepareMaskMaterial(); + this.prepareMaskMaterial.side = DoubleSide; + this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); - this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetMaskDownSampleBuffer.texture.name = 'OutlinePass.depthDownSample'; - this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; + this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); + this.renderTargetDepthBuffer.texture.name = 'OutlinePass.depth'; + this.renderTargetDepthBuffer.texture.generateMipmaps = false; - this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1'; - this.renderTargetBlurBuffer1.texture.generateMipmaps = false; - this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); - this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2'; - this.renderTargetBlurBuffer2.texture.generateMipmaps = false; + this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetMaskDownSampleBuffer.texture.name = 'OutlinePass.depthDownSample'; + this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; - this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); - this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1'; - this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; - this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); - this.renderTargetEdgeBuffer2.texture.name = 'OutlinePass.edge2'; - this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; + this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1'; + this.renderTargetBlurBuffer1.texture.generateMipmaps = false; + this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); + this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2'; + this.renderTargetBlurBuffer2.texture.generateMipmaps = false; - const MAX_EDGE_THICKNESS = 4; - const MAX_EDGE_GLOW = 4; + this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); + this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1'; + this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; + this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); + this.renderTargetEdgeBuffer2.texture.name = 'OutlinePass.edge2'; + this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; - this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); - this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); - this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1; - this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); - this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) ); - this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW; + const MAX_EDGE_THICKNESS = 4; + const MAX_EDGE_GLOW = 4; - // Overlay material - this.overlayMaterial = this.getOverlayMaterial(); + this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); + this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); + this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1; + this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); + this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) ); + this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW; - // copy material + // Overlay material + this.overlayMaterial = this.getOverlayMaterial(); - const copyShader = CopyShader; + // copy material - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + const copyShader = CopyShader; - this.materialCopy = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: NoBlending, - depthTest: false, - depthWrite: false - } ); + this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - this.enabled = true; - this.needsSwap = false; + this.materialCopy = new ShaderMaterial( { + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + blending: NoBlending, + depthTest: false, + depthWrite: false + } ); - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this.enabled = true; + this.needsSwap = false; - this.fsQuad = new FullScreenQuad( null ); + this._oldClearColor = new Color(); + this.oldClearAlpha = 1; - this.tempPulseColor1 = new Color(); - this.tempPulseColor2 = new Color(); - this.textureMatrix = new Matrix4(); + this.fsQuad = new FullScreenQuad( null ); - function replaceDepthToViewZ( string, camera ) { + this.tempPulseColor1 = new Color(); + this.tempPulseColor2 = new Color(); + this.textureMatrix = new Matrix4(); - const type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic'; + function replaceDepthToViewZ( string, camera ) { - return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' ); + const type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic'; + + return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' ); + + } } - } + dispose() { - dispose() { + this.renderTargetMaskBuffer.dispose(); + this.renderTargetDepthBuffer.dispose(); + this.renderTargetMaskDownSampleBuffer.dispose(); + this.renderTargetBlurBuffer1.dispose(); + this.renderTargetBlurBuffer2.dispose(); + this.renderTargetEdgeBuffer1.dispose(); + this.renderTargetEdgeBuffer2.dispose(); - this.renderTargetMaskBuffer.dispose(); - this.renderTargetDepthBuffer.dispose(); - this.renderTargetMaskDownSampleBuffer.dispose(); - this.renderTargetBlurBuffer1.dispose(); - this.renderTargetBlurBuffer2.dispose(); - this.renderTargetEdgeBuffer1.dispose(); - this.renderTargetEdgeBuffer2.dispose(); + this.depthMaterial.dispose(); + this.prepareMaskMaterial.dispose(); + this.edgeDetectionMaterial.dispose(); + this.separableBlurMaterial1.dispose(); + this.separableBlurMaterial2.dispose(); + this.overlayMaterial.dispose(); + this.materialCopy.dispose(); - this.depthMaterial.dispose(); - this.prepareMaskMaterial.dispose(); - this.edgeDetectionMaterial.dispose(); - this.separableBlurMaterial1.dispose(); - this.separableBlurMaterial2.dispose(); - this.overlayMaterial.dispose(); - this.materialCopy.dispose(); + this.fsQuad.dispose(); - this.fsQuad.dispose(); + } - } + setSize( width, height ) { - setSize( width, height ) { + this.renderTargetMaskBuffer.setSize( width, height ); + this.renderTargetDepthBuffer.setSize( width, height ); - this.renderTargetMaskBuffer.setSize( width, height ); - this.renderTargetDepthBuffer.setSize( width, height ); + let resx = Math.round( width / this.downSampleRatio ); + let resy = Math.round( height / this.downSampleRatio ); + this.renderTargetMaskDownSampleBuffer.setSize( resx, resy ); + this.renderTargetBlurBuffer1.setSize( resx, resy ); + this.renderTargetEdgeBuffer1.setSize( resx, resy ); + this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); - let resx = Math.round( width / this.downSampleRatio ); - let resy = Math.round( height / this.downSampleRatio ); - this.renderTargetMaskDownSampleBuffer.setSize( resx, resy ); - this.renderTargetBlurBuffer1.setSize( resx, resy ); - this.renderTargetEdgeBuffer1.setSize( resx, resy ); - this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); - resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); + this.renderTargetBlurBuffer2.setSize( resx, resy ); + this.renderTargetEdgeBuffer2.setSize( resx, resy ); - this.renderTargetBlurBuffer2.setSize( resx, resy ); - this.renderTargetEdgeBuffer2.setSize( resx, resy ); + this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( resx, resy ); - this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( resx, resy ); + } - } + changeVisibilityOfSelectedObjects( bVisible ) { - changeVisibilityOfSelectedObjects( bVisible ) { + const cache = this._visibilityCache; - const cache = this._visibilityCache; + function gatherSelectedMeshesCallBack( object ) { - function gatherSelectedMeshesCallBack( object ) { + if ( object.isMesh ) { - if ( object.isMesh ) { + if ( bVisible === true ) { - if ( bVisible === true ) { + object.visible = cache.get( object ); - object.visible = cache.get( object ); + } else { - } else { + cache.set( object, object.visible ); + object.visible = bVisible; - cache.set( object, object.visible ); - object.visible = bVisible; + } } } - } + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( gatherSelectedMeshesCallBack ); - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); + } } - } + changeVisibilityOfNonSelectedObjects( bVisible ) { - changeVisibilityOfNonSelectedObjects( bVisible ) { + const cache = this._visibilityCache; + const selectedMeshes = []; - const cache = this._visibilityCache; - const selectedMeshes = []; + function gatherSelectedMeshesCallBack( object ) { - function gatherSelectedMeshesCallBack( object ) { + if ( object.isMesh ) selectedMeshes.push( object ); - if ( object.isMesh ) selectedMeshes.push( object ); + } - } + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( gatherSelectedMeshesCallBack ); - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); + } - } + function VisibilityChangeCallBack( object ) { - function VisibilityChangeCallBack( object ) { + if ( object.isMesh || object.isSprite ) { - if ( object.isMesh || object.isSprite ) { + // only meshes and sprites are supported by OutlinePass - // only meshes and sprites are supported by OutlinePass + let bFound = false; - let bFound = false; + for ( let i = 0; i < selectedMeshes.length; i ++ ) { - for ( let i = 0; i < selectedMeshes.length; i ++ ) { + const selectedObjectId = selectedMeshes[ i ].id; - const selectedObjectId = selectedMeshes[ i ].id; + if ( selectedObjectId === object.id ) { - if ( selectedObjectId === object.id ) { + bFound = true; + break; - bFound = true; - break; + } } - } + if ( bFound === false ) { - if ( bFound === false ) { + const visibility = object.visible; - const visibility = object.visible; + if ( bVisible === false || cache.get( object ) === true ) { - if ( bVisible === false || cache.get( object ) === true ) { + object.visible = bVisible; - object.visible = bVisible; + } - } + cache.set( object, visibility ); - cache.set( object, visibility ); + } - } + } else if ( object.isPoints || object.isLine ) { - } else if ( object.isPoints || object.isLine ) { + // the visibilty of points and lines is always set to false in order to + // not affect the outline computation - // the visibilty of points and lines is always set to false in order to - // not affect the outline computation + if ( bVisible === true ) { - if ( bVisible === true ) { + object.visible = cache.get( object ); // restore - object.visible = cache.get( object ); // restore + } else { - } else { + cache.set( object, object.visible ); + object.visible = bVisible; - cache.set( object, object.visible ); - object.visible = bVisible; + } } } + this.renderScene.traverse( VisibilityChangeCallBack ); + } - this.renderScene.traverse( VisibilityChangeCallBack ); + updateTextureMatrix() { - } + this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 ); + this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); + this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); - updateTextureMatrix() { + } - this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 ); - this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); - this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - } + if ( this.selectedObjects.length > 0 ) { - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + renderer.getClearColor( this._oldClearColor ); + this.oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; - if ( this.selectedObjects.length > 0 ) { + renderer.autoClear = false; - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - renderer.autoClear = false; + renderer.setClearColor( 0xffffff, 1 ); - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + // Make selected objects invisible + this.changeVisibilityOfSelectedObjects( false ); - renderer.setClearColor( 0xffffff, 1 ); + const currentBackground = this.renderScene.background; + this.renderScene.background = null; - // Make selected objects invisible - this.changeVisibilityOfSelectedObjects( false ); + // 1. Draw Non Selected objects in the depth buffer + this.renderScene.overrideMaterial = this.depthMaterial; + renderer.setRenderTarget( this.renderTargetDepthBuffer ); + renderer.clear(); + renderer.render( this.renderScene, this.renderCamera ); - const currentBackground = this.renderScene.background; - this.renderScene.background = null; + // Make selected objects visible + this.changeVisibilityOfSelectedObjects( true ); + this._visibilityCache.clear(); - // 1. Draw Non Selected objects in the depth buffer - this.renderScene.overrideMaterial = this.depthMaterial; - renderer.setRenderTarget( this.renderTargetDepthBuffer ); - renderer.clear(); - renderer.render( this.renderScene, this.renderCamera ); + // Update Texture Matrix for Depth compare + this.updateTextureMatrix(); - // Make selected objects visible - this.changeVisibilityOfSelectedObjects( true ); - this._visibilityCache.clear(); + // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects + this.changeVisibilityOfNonSelectedObjects( false ); + this.renderScene.overrideMaterial = this.prepareMaskMaterial; + this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far ); + this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture; + this.prepareMaskMaterial.uniforms[ 'textureMatrix' ].value = this.textureMatrix; + renderer.setRenderTarget( this.renderTargetMaskBuffer ); + renderer.clear(); + renderer.render( this.renderScene, this.renderCamera ); + this.renderScene.overrideMaterial = null; + this.changeVisibilityOfNonSelectedObjects( true ); + this._visibilityCache.clear(); - // Update Texture Matrix for Depth compare - this.updateTextureMatrix(); + this.renderScene.background = currentBackground; - // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects - this.changeVisibilityOfNonSelectedObjects( false ); - this.renderScene.overrideMaterial = this.prepareMaskMaterial; - this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far ); - this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture; - this.prepareMaskMaterial.uniforms[ 'textureMatrix' ].value = this.textureMatrix; - renderer.setRenderTarget( this.renderTargetMaskBuffer ); - renderer.clear(); - renderer.render( this.renderScene, this.renderCamera ); - this.renderScene.overrideMaterial = null; - this.changeVisibilityOfNonSelectedObjects( true ); - this._visibilityCache.clear(); + // 2. Downsample to Half resolution + this.fsQuad.material = this.materialCopy; + this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture; + renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer ); + renderer.clear(); + this.fsQuad.render( renderer ); - this.renderScene.background = currentBackground; + this.tempPulseColor1.copy( this.visibleEdgeColor ); + this.tempPulseColor2.copy( this.hiddenEdgeColor ); - // 2. Downsample to Half resolution - this.fsQuad.material = this.materialCopy; - this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture; - renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer ); - renderer.clear(); - this.fsQuad.render( renderer ); + if ( this.pulsePeriod > 0 ) { - this.tempPulseColor1.copy( this.visibleEdgeColor ); - this.tempPulseColor2.copy( this.hiddenEdgeColor ); + const scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2; + this.tempPulseColor1.multiplyScalar( scalar ); + this.tempPulseColor2.multiplyScalar( scalar ); - if ( this.pulsePeriod > 0 ) { + } - const scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2; - this.tempPulseColor1.multiplyScalar( scalar ); - this.tempPulseColor2.multiplyScalar( scalar ); + // 3. Apply Edge Detection Pass + this.fsQuad.material = this.edgeDetectionMaterial; + this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture; + this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); + this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1; + this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2; + renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); + renderer.clear(); + this.fsQuad.render( renderer ); + + // 4. Apply Blur on Half res + this.fsQuad.material = this.separableBlurMaterial1; + this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; + this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; + this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness; + renderer.setRenderTarget( this.renderTargetBlurBuffer1 ); + renderer.clear(); + this.fsQuad.render( renderer ); + this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture; + this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); + renderer.clear(); + this.fsQuad.render( renderer ); + + // Apply Blur on quarter res + this.fsQuad.material = this.separableBlurMaterial2; + this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; + this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; + renderer.setRenderTarget( this.renderTargetBlurBuffer2 ); + renderer.clear(); + this.fsQuad.render( renderer ); + this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture; + this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetEdgeBuffer2 ); + renderer.clear(); + this.fsQuad.render( renderer ); + + // Blend it additively over the input texture + this.fsQuad.material = this.overlayMaterial; + this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture; + this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture; + this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture; + this.overlayMaterial.uniforms[ 'patternTexture' ].value = this.patternTexture; + this.overlayMaterial.uniforms[ 'edgeStrength' ].value = this.edgeStrength; + this.overlayMaterial.uniforms[ 'edgeGlow' ].value = this.edgeGlow; + this.overlayMaterial.uniforms[ 'usePatternTexture' ].value = this.usePatternTexture; + + + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + + renderer.setRenderTarget( readBuffer ); + this.fsQuad.render( renderer ); + + renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.autoClear = oldAutoClear; } - // 3. Apply Edge Detection Pass - this.fsQuad.material = this.edgeDetectionMaterial; - this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture; - this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); - this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1; - this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2; - renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); - renderer.clear(); - this.fsQuad.render( renderer ); - - // 4. Apply Blur on Half res - this.fsQuad.material = this.separableBlurMaterial1; - this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; - this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; - this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness; - renderer.setRenderTarget( this.renderTargetBlurBuffer1 ); - renderer.clear(); - this.fsQuad.render( renderer ); - this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture; - this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); - renderer.clear(); - this.fsQuad.render( renderer ); - - // Apply Blur on quarter res - this.fsQuad.material = this.separableBlurMaterial2; - this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; - this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; - renderer.setRenderTarget( this.renderTargetBlurBuffer2 ); - renderer.clear(); - this.fsQuad.render( renderer ); - this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture; - this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetEdgeBuffer2 ); - renderer.clear(); - this.fsQuad.render( renderer ); - - // Blend it additively over the input texture - this.fsQuad.material = this.overlayMaterial; - this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture; - this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture; - this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture; - this.overlayMaterial.uniforms[ 'patternTexture' ].value = this.patternTexture; - this.overlayMaterial.uniforms[ 'edgeStrength' ].value = this.edgeStrength; - this.overlayMaterial.uniforms[ 'edgeGlow' ].value = this.edgeGlow; - this.overlayMaterial.uniforms[ 'usePatternTexture' ].value = this.usePatternTexture; - - - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - - renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); - - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; - - } + if ( this.renderToScreen ) { - if ( this.renderToScreen ) { + this.fsQuad.material = this.materialCopy; + this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture; + renderer.setRenderTarget( null ); + this.fsQuad.render( renderer ); - this.fsQuad.material = this.materialCopy; - this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture; - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + } } - } + getPrepareMaskMaterial() { - getPrepareMaskMaterial() { + return new ShaderMaterial( { - return new ShaderMaterial( { + uniforms: { + 'depthTexture': { value: null }, + 'cameraNearFar': { value: new Vector2( 0.5, 0.5 ) }, + 'textureMatrix': { value: null } + }, - uniforms: { - 'depthTexture': { value: null }, - 'cameraNearFar': { value: new Vector2( 0.5, 0.5 ) }, - 'textureMatrix': { value: null } - }, + vertexShader: + `#include + #include - vertexShader: - `#include - #include + varying vec4 projTexCoord; + varying vec4 vPosition; + uniform mat4 textureMatrix; - varying vec4 projTexCoord; - varying vec4 vPosition; - uniform mat4 textureMatrix; + void main() { - void main() { + #include + #include + #include + #include + #include - #include - #include - #include - #include - #include + vPosition = mvPosition; - vPosition = mvPosition; + vec4 worldPosition = vec4( transformed, 1.0 ); - vec4 worldPosition = vec4( transformed, 1.0 ); + #ifdef USE_INSTANCING - #ifdef USE_INSTANCING + worldPosition = instanceMatrix * worldPosition; - worldPosition = instanceMatrix * worldPosition; + #endif + + worldPosition = modelMatrix * worldPosition; - #endif - - worldPosition = modelMatrix * worldPosition; + projTexCoord = textureMatrix * worldPosition; - projTexCoord = textureMatrix * worldPosition; + }`, - }`, + fragmentShader: + `#include + varying vec4 vPosition; + varying vec4 projTexCoord; + uniform sampler2D depthTexture; + uniform vec2 cameraNearFar; - fragmentShader: - `#include - varying vec4 vPosition; - varying vec4 projTexCoord; - uniform sampler2D depthTexture; - uniform vec2 cameraNearFar; + void main() { - void main() { + float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord )); + float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y ); + float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0; + gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0); - float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord )); - float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y ); - float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0; - gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0); + }` - }` + } ); - } ); + } - } + getEdgeDetectionMaterial() { + + return new ShaderMaterial( { + + uniforms: { + 'maskTexture': { value: null }, + 'texSize': { value: new Vector2( 0.5, 0.5 ) }, + 'visibleEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, + 'hiddenEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, + }, + + vertexShader: + `varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `varying vec2 vUv; + + uniform sampler2D maskTexture; + uniform vec2 texSize; + uniform vec3 visibleEdgeColor; + uniform vec3 hiddenEdgeColor; + + void main() { + vec2 invSize = 1.0 / texSize; + vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize); + vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy); + vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy); + vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw); + vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw); + float diff1 = (c1.r - c2.r)*0.5; + float diff2 = (c3.r - c4.r)*0.5; + float d = length( vec2(diff1, diff2) ); + float a1 = min(c1.g, c2.g); + float a2 = min(c3.g, c4.g); + float visibilityFactor = min(a1, a2); + vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor; + gl_FragColor = vec4(edgeColor, 1.0) * vec4(d); + }` + } ); - getEdgeDetectionMaterial() { - - return new ShaderMaterial( { - - uniforms: { - 'maskTexture': { value: null }, - 'texSize': { value: new Vector2( 0.5, 0.5 ) }, - 'visibleEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, - 'hiddenEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, - }, - - vertexShader: - `varying vec2 vUv; - - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `varying vec2 vUv; - - uniform sampler2D maskTexture; - uniform vec2 texSize; - uniform vec3 visibleEdgeColor; - uniform vec3 hiddenEdgeColor; - - void main() { - vec2 invSize = 1.0 / texSize; - vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize); - vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy); - vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy); - vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw); - vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw); - float diff1 = (c1.r - c2.r)*0.5; - float diff2 = (c3.r - c4.r)*0.5; - float d = length( vec2(diff1, diff2) ); - float a1 = min(c1.g, c2.g); - float a2 = min(c3.g, c4.g); - float visibilityFactor = min(a1, a2); - vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor; - gl_FragColor = vec4(edgeColor, 1.0) * vec4(d); - }` - } ); + } - } + getSeperableBlurMaterial( maxRadius ) { - getSeperableBlurMaterial( maxRadius ) { + return new ShaderMaterial( { - return new ShaderMaterial( { + defines: { + 'MAX_RADIUS': maxRadius, + }, - defines: { - 'MAX_RADIUS': maxRadius, - }, + uniforms: { + 'colorTexture': { value: null }, + 'texSize': { value: new Vector2( 0.5, 0.5 ) }, + 'direction': { value: new Vector2( 0.5, 0.5 ) }, + 'kernelRadius': { value: 1.0 } + }, - uniforms: { - 'colorTexture': { value: null }, - 'texSize': { value: new Vector2( 0.5, 0.5 ) }, - 'direction': { value: new Vector2( 0.5, 0.5 ) }, - 'kernelRadius': { value: 1.0 } - }, + vertexShader: + `varying vec2 vUv; - vertexShader: - `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, + fragmentShader: + `#include + varying vec2 vUv; + uniform sampler2D colorTexture; + uniform vec2 texSize; + uniform vec2 direction; + uniform float kernelRadius; - fragmentShader: - `#include - varying vec2 vUv; - uniform sampler2D colorTexture; - uniform vec2 texSize; - uniform vec2 direction; - uniform float kernelRadius; + float gaussianPdf(in float x, in float sigma) { + return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; + } - float gaussianPdf(in float x, in float sigma) { - return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; - } + void main() { + vec2 invSize = 1.0 / texSize; + float sigma = kernelRadius/2.0; + float weightSum = gaussianPdf(0.0, sigma); + vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum; + vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS); + vec2 uvOffset = delta; + for( int i = 1; i <= MAX_RADIUS; i ++ ) { + float x = kernelRadius * float(i) / float(MAX_RADIUS); + float w = gaussianPdf(x, sigma); + vec4 sample1 = texture2D( colorTexture, vUv + uvOffset); + vec4 sample2 = texture2D( colorTexture, vUv - uvOffset); + diffuseSum += ((sample1 + sample2) * w); + weightSum += (2.0 * w); + uvOffset += delta; + } + gl_FragColor = diffuseSum/weightSum; + }` + } ); - void main() { - vec2 invSize = 1.0 / texSize; - float sigma = kernelRadius/2.0; - float weightSum = gaussianPdf(0.0, sigma); - vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum; - vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS); - vec2 uvOffset = delta; - for( int i = 1; i <= MAX_RADIUS; i ++ ) { - float x = kernelRadius * float(i) / float(MAX_RADIUS); - float w = gaussianPdf(x, sigma); - vec4 sample1 = texture2D( colorTexture, vUv + uvOffset); - vec4 sample2 = texture2D( colorTexture, vUv - uvOffset); - diffuseSum += ((sample1 + sample2) * w); - weightSum += (2.0 * w); - uvOffset += delta; - } - gl_FragColor = diffuseSum/weightSum; - }` - } ); + } - } + getOverlayMaterial() { + + return new ShaderMaterial( { + + uniforms: { + 'maskTexture': { value: null }, + 'edgeTexture1': { value: null }, + 'edgeTexture2': { value: null }, + 'patternTexture': { value: null }, + 'edgeStrength': { value: 1.0 }, + 'edgeGlow': { value: 1.0 }, + 'usePatternTexture': { value: 0.0 } + }, + + vertexShader: + `varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `varying vec2 vUv; + + uniform sampler2D maskTexture; + uniform sampler2D edgeTexture1; + uniform sampler2D edgeTexture2; + uniform sampler2D patternTexture; + uniform float edgeStrength; + uniform float edgeGlow; + uniform bool usePatternTexture; + + void main() { + vec4 edgeValue1 = texture2D(edgeTexture1, vUv); + vec4 edgeValue2 = texture2D(edgeTexture2, vUv); + vec4 maskColor = texture2D(maskTexture, vUv); + vec4 patternColor = texture2D(patternTexture, 6.0 * vUv); + float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5; + vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow; + vec4 finalColor = edgeStrength * maskColor.r * edgeValue; + if(usePatternTexture) + finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r); + gl_FragColor = finalColor; + }`, + blending: AdditiveBlending, + depthTest: false, + depthWrite: false, + transparent: true + } ); - getOverlayMaterial() { - - return new ShaderMaterial( { - - uniforms: { - 'maskTexture': { value: null }, - 'edgeTexture1': { value: null }, - 'edgeTexture2': { value: null }, - 'patternTexture': { value: null }, - 'edgeStrength': { value: 1.0 }, - 'edgeGlow': { value: 1.0 }, - 'usePatternTexture': { value: 0.0 } - }, - - vertexShader: - `varying vec2 vUv; - - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `varying vec2 vUv; - - uniform sampler2D maskTexture; - uniform sampler2D edgeTexture1; - uniform sampler2D edgeTexture2; - uniform sampler2D patternTexture; - uniform float edgeStrength; - uniform float edgeGlow; - uniform bool usePatternTexture; - - void main() { - vec4 edgeValue1 = texture2D(edgeTexture1, vUv); - vec4 edgeValue2 = texture2D(edgeTexture2, vUv); - vec4 maskColor = texture2D(maskTexture, vUv); - vec4 patternColor = texture2D(patternTexture, 6.0 * vUv); - float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5; - vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow; - vec4 finalColor = edgeStrength * maskColor.r * edgeValue; - if(usePatternTexture) - finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r); - gl_FragColor = finalColor; - }`, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true - } ); + } } -} + OutlinePass.BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); + OutlinePass.BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); -const BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); -const BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); + return OutlinePass; -/* @__PURE__ */ Object.assign( OutlinePass, { BlurDirectionX, BlurDirectionY } ); +} )(); export { OutlinePass }; diff --git a/examples/jsm/postprocessing/Pass.js b/examples/jsm/postprocessing/Pass.js index 4d001f5692727b..c94852c0fb7fab 100644 --- a/examples/jsm/postprocessing/Pass.js +++ b/examples/jsm/postprocessing/Pass.js @@ -43,9 +43,15 @@ const _camera = /* @__PURE__ */ new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); // https://github.com/mrdoob/three.js/pull/21358 -const _geometry = /* @__PURE__ */ new BufferGeometry(); -/* @__PURE__ */ _geometry.setAttribute( 'position', /* @__PURE__ */ new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); -/* @__PURE__ */ _geometry.setAttribute( 'uv', /* @__PURE__ */ new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); +const _geometry = /* @__PURE__ */ ( () => { + + const _geometry = new BufferGeometry(); + _geometry.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); + _geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); + + return _geometry; + +} )(); class FullScreenQuad { diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index 921af6a24bd879..3906dd888e2662 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -27,312 +27,316 @@ import { CopyShader } from '../shaders/CopyShader.js'; * SAO implementation inspired from bhouston previous SAO work */ -class SAOPass extends Pass { - - constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { - - super(); - - this.scene = scene; - this.camera = camera; - - this.clear = true; - this.needsSwap = false; - - this.originalClearColor = new Color(); - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; - - this.params = { - output: 0, - saoBias: 0.5, - saoIntensity: 0.18, - saoScale: 1, - saoKernelRadius: 100, - saoMinResolution: 0, - saoBlur: true, - saoBlurRadius: 8, - saoBlurStdDev: 4, - saoBlurDepthCutoff: 0.01 - }; - - this.resolution = new Vector2( resolution.x, resolution.y ); - - this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); - this.blurIntermediateRenderTarget = this.saoRenderTarget.clone(); - - const depthTexture = new DepthTexture(); - depthTexture.format = DepthStencilFormat; - depthTexture.type = UnsignedInt248Type; - - this.normalRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - depthTexture: depthTexture - } ); - - this.normalMaterial = new MeshNormalMaterial(); - this.normalMaterial.blending = NoBlending; - - this.saoMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SAOShader.defines ), - fragmentShader: SAOShader.fragmentShader, - vertexShader: SAOShader.vertexShader, - uniforms: UniformsUtils.clone( SAOShader.uniforms ) - } ); - this.saoMaterial.extensions.derivatives = true; - this.saoMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.saoMaterial.uniforms[ 'tDepth' ].value = depthTexture; - this.saoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; - this.saoMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); - this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; - this.saoMaterial.blending = NoBlending; - - this.vBlurMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), - defines: Object.assign( {}, DepthLimitedBlurShader.defines ), - vertexShader: DepthLimitedBlurShader.vertexShader, - fragmentShader: DepthLimitedBlurShader.fragmentShader - } ); - this.vBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; - this.vBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.vBlurMaterial.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; - this.vBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; - this.vBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); - this.vBlurMaterial.blending = NoBlending; - - this.hBlurMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), - defines: Object.assign( {}, DepthLimitedBlurShader.defines ), - vertexShader: DepthLimitedBlurShader.vertexShader, - fragmentShader: DepthLimitedBlurShader.fragmentShader - } ); - this.hBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; - this.hBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.hBlurMaterial.uniforms[ 'tDiffuse' ].value = this.blurIntermediateRenderTarget.texture; - this.hBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; - this.hBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); - this.hBlurMaterial.blending = NoBlending; - - this.materialCopy = new ShaderMaterial( { - uniforms: UniformsUtils.clone( CopyShader.uniforms ), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - blending: NoBlending - } ); - this.materialCopy.transparent = true; - this.materialCopy.depthTest = false; - this.materialCopy.depthWrite = false; - this.materialCopy.blending = CustomBlending; - this.materialCopy.blendSrc = DstColorFactor; - this.materialCopy.blendDst = ZeroFactor; - this.materialCopy.blendEquation = AddEquation; - this.materialCopy.blendSrcAlpha = DstAlphaFactor; - this.materialCopy.blendDstAlpha = ZeroFactor; - this.materialCopy.blendEquationAlpha = AddEquation; - - this.fsQuad = new FullScreenQuad( null ); +const SAOPass = /* @__PURE__ */ ( () => { + + class SAOPass extends Pass { + + constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { + + super(); + + this.scene = scene; + this.camera = camera; + + this.clear = true; + this.needsSwap = false; + + this.originalClearColor = new Color(); + this._oldClearColor = new Color(); + this.oldClearAlpha = 1; + + this.params = { + output: 0, + saoBias: 0.5, + saoIntensity: 0.18, + saoScale: 1, + saoKernelRadius: 100, + saoMinResolution: 0, + saoBlur: true, + saoBlurRadius: 8, + saoBlurStdDev: 4, + saoBlurDepthCutoff: 0.01 + }; + + this.resolution = new Vector2( resolution.x, resolution.y ); + + this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); + this.blurIntermediateRenderTarget = this.saoRenderTarget.clone(); + + const depthTexture = new DepthTexture(); + depthTexture.format = DepthStencilFormat; + depthTexture.type = UnsignedInt248Type; + + this.normalRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture + } ); + + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; + + this.saoMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SAOShader.defines ), + fragmentShader: SAOShader.fragmentShader, + vertexShader: SAOShader.vertexShader, + uniforms: UniformsUtils.clone( SAOShader.uniforms ) + } ); + this.saoMaterial.extensions.derivatives = true; + this.saoMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; + this.saoMaterial.uniforms[ 'tDepth' ].value = depthTexture; + this.saoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; + this.saoMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); + this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; + this.saoMaterial.blending = NoBlending; + + this.vBlurMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), + defines: Object.assign( {}, DepthLimitedBlurShader.defines ), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader + } ); + this.vBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; + this.vBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; + this.vBlurMaterial.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; + this.vBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; + this.vBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); + this.vBlurMaterial.blending = NoBlending; + + this.hBlurMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), + defines: Object.assign( {}, DepthLimitedBlurShader.defines ), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader + } ); + this.hBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; + this.hBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; + this.hBlurMaterial.uniforms[ 'tDiffuse' ].value = this.blurIntermediateRenderTarget.texture; + this.hBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; + this.hBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); + this.hBlurMaterial.blending = NoBlending; + + this.materialCopy = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: NoBlending + } ); + this.materialCopy.transparent = true; + this.materialCopy.depthTest = false; + this.materialCopy.depthWrite = false; + this.materialCopy.blending = CustomBlending; + this.materialCopy.blendSrc = DstColorFactor; + this.materialCopy.blendDst = ZeroFactor; + this.materialCopy.blendEquation = AddEquation; + this.materialCopy.blendSrcAlpha = DstAlphaFactor; + this.materialCopy.blendDstAlpha = ZeroFactor; + this.materialCopy.blendEquationAlpha = AddEquation; + + this.fsQuad = new FullScreenQuad( null ); - } + } - render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { + render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { - // Rendering readBuffer first when rendering to screen - if ( this.renderToScreen ) { + // Rendering readBuffer first when rendering to screen + if ( this.renderToScreen ) { - this.materialCopy.blending = NoBlending; - this.materialCopy.uniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.materialCopy.needsUpdate = true; - this.renderPass( renderer, this.materialCopy, null ); + this.materialCopy.blending = NoBlending; + this.materialCopy.uniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.materialCopy.needsUpdate = true; + this.renderPass( renderer, this.materialCopy, null ); - } + } - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; - - this.saoMaterial.uniforms[ 'bias' ].value = this.params.saoBias; - this.saoMaterial.uniforms[ 'intensity' ].value = this.params.saoIntensity; - this.saoMaterial.uniforms[ 'scale' ].value = this.params.saoScale; - this.saoMaterial.uniforms[ 'kernelRadius' ].value = this.params.saoKernelRadius; - this.saoMaterial.uniforms[ 'minResolution' ].value = this.params.saoMinResolution; - this.saoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.saoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); - - const depthCutoff = this.params.saoBlurDepthCutoff * ( this.camera.far - this.camera.near ); - this.vBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; - this.hBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; - - this.vBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.vBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.hBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.hBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - - this.params.saoBlurRadius = Math.floor( this.params.saoBlurRadius ); - if ( ( this.prevStdDev !== this.params.saoBlurStdDev ) || ( this.prevNumSamples !== this.params.saoBlurRadius ) ) { - - BlurShaderUtils.configure( this.vBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 0, 1 ) ); - BlurShaderUtils.configure( this.hBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 1, 0 ) ); - this.prevStdDev = this.params.saoBlurStdDev; - this.prevNumSamples = this.params.saoBlurRadius; + renderer.getClearColor( this._oldClearColor ); + this.oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; - } + this.saoMaterial.uniforms[ 'bias' ].value = this.params.saoBias; + this.saoMaterial.uniforms[ 'intensity' ].value = this.params.saoIntensity; + this.saoMaterial.uniforms[ 'scale' ].value = this.params.saoScale; + this.saoMaterial.uniforms[ 'kernelRadius' ].value = this.params.saoKernelRadius; + this.saoMaterial.uniforms[ 'minResolution' ].value = this.params.saoMinResolution; + this.saoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.saoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); - // render normal and depth - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + const depthCutoff = this.params.saoBlurDepthCutoff * ( this.camera.far - this.camera.near ); + this.vBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; + this.hBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; - // Rendering SAO texture - this.renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + this.vBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.vBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.hBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.hBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - // Blurring SAO texture - if ( this.params.saoBlur ) { + this.params.saoBlurRadius = Math.floor( this.params.saoBlurRadius ); + if ( ( this.prevStdDev !== this.params.saoBlurStdDev ) || ( this.prevNumSamples !== this.params.saoBlurRadius ) ) { - this.renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); - this.renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + BlurShaderUtils.configure( this.vBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 0, 1 ) ); + BlurShaderUtils.configure( this.hBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 1, 0 ) ); + this.prevStdDev = this.params.saoBlurStdDev; + this.prevNumSamples = this.params.saoBlurRadius; - } + } - const outputMaterial = this.materialCopy; + // render normal and depth + this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - // Setting up SAO rendering - if ( this.params.output === SAOPass.OUTPUT.Normal ) { + // Rendering SAO texture + this.renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); - this.materialCopy.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; - this.materialCopy.needsUpdate = true; + // Blurring SAO texture + if ( this.params.saoBlur ) { - } else { + this.renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); + this.renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); - this.materialCopy.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; - this.materialCopy.needsUpdate = true; + } - } + const outputMaterial = this.materialCopy; - // Blending depends on output - if ( this.params.output === SAOPass.OUTPUT.Default ) { + // Setting up SAO rendering + if ( this.params.output === SAOPass.OUTPUT.Normal ) { - outputMaterial.blending = CustomBlending; + this.materialCopy.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; + this.materialCopy.needsUpdate = true; - } else { + } else { - outputMaterial.blending = NoBlending; + this.materialCopy.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; + this.materialCopy.needsUpdate = true; - } - - // Rendering SAOPass result on top of previous pass - this.renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); + } - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; + // Blending depends on output + if ( this.params.output === SAOPass.OUTPUT.Default ) { - } + outputMaterial.blending = CustomBlending; - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + } else { - // save original state - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + outputMaterial.blending = NoBlending; - renderer.setRenderTarget( renderTarget ); + } - // setup pass state - renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + // Rendering SAOPass result on top of previous pass + this.renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.autoClear = oldAutoClear; } - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + // save original state + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + // setup pass state + renderer.autoClear = false; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + } - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + this.fsQuad.material = passMaterial; + this.fsQuad.render( renderer ); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); } - this.scene.overrideMaterial = overrideMaterial; - renderer.render( this.scene, this.camera ); - this.scene.overrideMaterial = null; + renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - setSize( width, height ) { + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - this.saoRenderTarget.setSize( width, height ); - this.blurIntermediateRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); - this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; - this.saoMaterial.needsUpdate = true; + } - this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.vBlurMaterial.needsUpdate = true; + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; - this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.hBlurMaterial.needsUpdate = true; + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - } + } + + setSize( width, height ) { - dispose() { + this.saoRenderTarget.setSize( width, height ); + this.blurIntermediateRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); - this.saoRenderTarget.dispose(); - this.blurIntermediateRenderTarget.dispose(); - this.normalRenderTarget.dispose(); + this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); + this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; + this.saoMaterial.needsUpdate = true; - this.depthMaterial.dispose(); - this.normalMaterial.dispose(); - this.saoMaterial.dispose(); - this.vBlurMaterial.dispose(); - this.hBlurMaterial.dispose(); - this.materialCopy.dispose(); + this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.vBlurMaterial.needsUpdate = true; + + this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.hBlurMaterial.needsUpdate = true; + + } - this.fsQuad.dispose(); + dispose() { + + this.saoRenderTarget.dispose(); + this.blurIntermediateRenderTarget.dispose(); + this.normalRenderTarget.dispose(); + + this.depthMaterial.dispose(); + this.normalMaterial.dispose(); + this.saoMaterial.dispose(); + this.vBlurMaterial.dispose(); + this.hBlurMaterial.dispose(); + this.materialCopy.dispose(); + + this.fsQuad.dispose(); + + } } -} + SAOPass.OUTPUT = { + 'Default': 0, + 'SAO': 1, + 'Normal': 2 + }; -const OUTPUT = { - 'Default': 0, - 'SAO': 1, - 'Normal': 2 -}; + return SAOPass; -/* @__PURE__ */ Object.assign( SAOPass, { OUTPUT } ); +} )(); export { SAOPass }; diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index 5e9c427d0ea708..b4d08acf02ebc0 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -30,393 +30,397 @@ import { SSAOBlurShader } from '../shaders/SSAOShader.js'; import { SSAODepthShader } from '../shaders/SSAOShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; -class SSAOPass extends Pass { +const SSAOPass = /* @__PURE__ */ ( () => { - constructor( scene, camera, width, height, kernelSize = 32 ) { + class SSAOPass extends Pass { - super(); + constructor( scene, camera, width, height, kernelSize = 32 ) { - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + super(); - this.clear = true; + this.width = ( width !== undefined ) ? width : 512; + this.height = ( height !== undefined ) ? height : 512; - this.camera = camera; - this.scene = scene; + this.clear = true; - this.kernelRadius = 8; - this.kernel = []; - this.noiseTexture = null; - this.output = 0; + this.camera = camera; + this.scene = scene; - this.minDistance = 0.005; - this.maxDistance = 0.1; + this.kernelRadius = 8; + this.kernel = []; + this.noiseTexture = null; + this.output = 0; - this._visibilityCache = new Map(); + this.minDistance = 0.005; + this.maxDistance = 0.1; - // + this._visibilityCache = new Map(); - this.generateSampleKernel( kernelSize ); - this.generateRandomKernelRotations(); + // - // depth texture + this.generateSampleKernel( kernelSize ); + this.generateRandomKernelRotations(); - const depthTexture = new DepthTexture(); - depthTexture.format = DepthStencilFormat; - depthTexture.type = UnsignedInt248Type; + // depth texture - // normal render target with depth buffer + const depthTexture = new DepthTexture(); + depthTexture.format = DepthStencilFormat; + depthTexture.type = UnsignedInt248Type; - this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - depthTexture: depthTexture - } ); + // normal render target with depth buffer - // ssao render target + this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture + } ); - this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); + // ssao render target - this.blurRenderTarget = this.ssaoRenderTarget.clone(); + this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); - // ssao material + this.blurRenderTarget = this.ssaoRenderTarget.clone(); - this.ssaoMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSAOShader.defines ), - uniforms: UniformsUtils.clone( SSAOShader.uniforms ), - vertexShader: SSAOShader.vertexShader, - fragmentShader: SSAOShader.fragmentShader, - blending: NoBlending - } ); + // ssao material - this.ssaoMaterial.defines[ 'KERNEL_SIZE' ] = kernelSize; + this.ssaoMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSAOShader.defines ), + uniforms: UniformsUtils.clone( SSAOShader.uniforms ), + vertexShader: SSAOShader.vertexShader, + fragmentShader: SSAOShader.fragmentShader, + blending: NoBlending + } ); - this.ssaoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; - this.ssaoMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; - this.ssaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture; - this.ssaoMaterial.uniforms[ 'kernel' ].value = this.kernel; - this.ssaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.ssaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.ssaoMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.ssaoMaterial.defines[ 'KERNEL_SIZE' ] = kernelSize; - // normal material + this.ssaoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; + this.ssaoMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; + this.ssaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture; + this.ssaoMaterial.uniforms[ 'kernel' ].value = this.kernel; + this.ssaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.ssaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.ssaoMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.normalMaterial = new MeshNormalMaterial(); - this.normalMaterial.blending = NoBlending; + // normal material - // blur material + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; - this.blurMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSAOBlurShader.defines ), - uniforms: UniformsUtils.clone( SSAOBlurShader.uniforms ), - vertexShader: SSAOBlurShader.vertexShader, - fragmentShader: SSAOBlurShader.fragmentShader - } ); - this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; - this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + // blur material - // material for rendering the depth + this.blurMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSAOBlurShader.defines ), + uniforms: UniformsUtils.clone( SSAOBlurShader.uniforms ), + vertexShader: SSAOBlurShader.vertexShader, + fragmentShader: SSAOBlurShader.fragmentShader + } ); + this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; + this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - this.depthRenderMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSAODepthShader.defines ), - uniforms: UniformsUtils.clone( SSAODepthShader.uniforms ), - vertexShader: SSAODepthShader.vertexShader, - fragmentShader: SSAODepthShader.fragmentShader, - blending: NoBlending - } ); - this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; - this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + // material for rendering the depth - // material for rendering the content of a render target + this.depthRenderMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSAODepthShader.defines ), + uniforms: UniformsUtils.clone( SSAODepthShader.uniforms ), + vertexShader: SSAODepthShader.vertexShader, + fragmentShader: SSAODepthShader.fragmentShader, + blending: NoBlending + } ); + this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; + this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.copyMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( CopyShader.uniforms ), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - transparent: true, - depthTest: false, - depthWrite: false, - blendSrc: DstColorFactor, - blendDst: ZeroFactor, - blendEquation: AddEquation, - blendSrcAlpha: DstAlphaFactor, - blendDstAlpha: ZeroFactor, - blendEquationAlpha: AddEquation - } ); + // material for rendering the content of a render target - this.fsQuad = new FullScreenQuad( null ); + this.copyMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blendSrc: DstColorFactor, + blendDst: ZeroFactor, + blendEquation: AddEquation, + blendSrcAlpha: DstAlphaFactor, + blendDstAlpha: ZeroFactor, + blendEquationAlpha: AddEquation + } ); - this.originalClearColor = new Color(); + this.fsQuad = new FullScreenQuad( null ); - } + this.originalClearColor = new Color(); - dispose() { + } - // dispose render targets + dispose() { - this.normalRenderTarget.dispose(); - this.ssaoRenderTarget.dispose(); - this.blurRenderTarget.dispose(); + // dispose render targets - // dispose materials + this.normalRenderTarget.dispose(); + this.ssaoRenderTarget.dispose(); + this.blurRenderTarget.dispose(); - this.normalMaterial.dispose(); - this.blurMaterial.dispose(); - this.copyMaterial.dispose(); - this.depthRenderMaterial.dispose(); + // dispose materials - // dipsose full screen quad + this.normalMaterial.dispose(); + this.blurMaterial.dispose(); + this.copyMaterial.dispose(); + this.depthRenderMaterial.dispose(); - this.fsQuad.dispose(); + // dipsose full screen quad - } + this.fsQuad.dispose(); + + } + + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + if ( renderer.capabilities.isWebGL2 === false ) this.noiseTexture.format = LuminanceFormat; - if ( renderer.capabilities.isWebGL2 === false ) this.noiseTexture.format = LuminanceFormat; + // render normals and depth (honor only meshes, points and lines do not contribute to SSAO) - // render normals and depth (honor only meshes, points and lines do not contribute to SSAO) + this.overrideVisibility(); + this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this.restoreVisibility(); - this.overrideVisibility(); - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - this.restoreVisibility(); + // render SSAO - // render SSAO + this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius; + this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance; + this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; + this.renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); - this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius; - this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance; - this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; - this.renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); + // render blur - // render blur + this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + // output result to screen - // output result to screen + switch ( this.output ) { - switch ( this.output ) { + case SSAOPass.OUTPUT.SSAO: - case SSAOPass.OUTPUT.SSAO: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSAOPass.OUTPUT.Blur: - case SSAOPass.OUTPUT.Blur: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSAOPass.OUTPUT.Depth: - case SSAOPass.OUTPUT.Depth: + this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSAOPass.OUTPUT.Normal: - case SSAOPass.OUTPUT.Normal: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSAOPass.OUTPUT.Default: - case SSAOPass.OUTPUT.Default: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; + this.copyMaterial.blending = CustomBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; - this.copyMaterial.blending = CustomBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + default: + console.warn( 'THREE.SSAOPass: Unknown output type.' ); - default: - console.warn( 'THREE.SSAOPass: Unknown output type.' ); + } } - } + renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + // save original state + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - // save original state - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + renderer.setRenderTarget( renderTarget ); - renderer.setRenderTarget( renderTarget ); + // setup pass state + renderer.autoClear = false; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - // setup pass state - renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + } - } + this.fsQuad.material = passMaterial; + this.fsQuad.render( renderer ); - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + } - } + renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + } - } + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; - this.scene.overrideMaterial = overrideMaterial; - renderer.render( this.scene, this.camera ); - this.scene.overrideMaterial = null; + // restore original state - // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + } - } + setSize( width, height ) { - setSize( width, height ) { + this.width = width; + this.height = height; - this.width = width; - this.height = height; + this.ssaoRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); - this.ssaoRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); + this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + } - } + generateSampleKernel( kernelSize ) { - generateSampleKernel( kernelSize ) { + const kernel = this.kernel; - const kernel = this.kernel; + for ( let i = 0; i < kernelSize; i ++ ) { - for ( let i = 0; i < kernelSize; i ++ ) { + const sample = new Vector3(); + sample.x = ( Math.random() * 2 ) - 1; + sample.y = ( Math.random() * 2 ) - 1; + sample.z = Math.random(); - const sample = new Vector3(); - sample.x = ( Math.random() * 2 ) - 1; - sample.y = ( Math.random() * 2 ) - 1; - sample.z = Math.random(); + sample.normalize(); - sample.normalize(); + let scale = i / kernelSize; + scale = MathUtils.lerp( 0.1, 1, scale * scale ); + sample.multiplyScalar( scale ); - let scale = i / kernelSize; - scale = MathUtils.lerp( 0.1, 1, scale * scale ); - sample.multiplyScalar( scale ); + kernel.push( sample ); - kernel.push( sample ); + } } - } + generateRandomKernelRotations() { - generateRandomKernelRotations() { + const width = 4, height = 4; - const width = 4, height = 4; + const simplex = new SimplexNoise(); - const simplex = new SimplexNoise(); + const size = width * height; + const data = new Float32Array( size ); - const size = width * height; - const data = new Float32Array( size ); + for ( let i = 0; i < size; i ++ ) { - for ( let i = 0; i < size; i ++ ) { + const x = ( Math.random() * 2 ) - 1; + const y = ( Math.random() * 2 ) - 1; + const z = 0; - const x = ( Math.random() * 2 ) - 1; - const y = ( Math.random() * 2 ) - 1; - const z = 0; + data[ i ] = simplex.noise3d( x, y, z ); - data[ i ] = simplex.noise3d( x, y, z ); + } - } + this.noiseTexture = new DataTexture( data, width, height, RedFormat, FloatType ); + this.noiseTexture.wrapS = RepeatWrapping; + this.noiseTexture.wrapT = RepeatWrapping; + this.noiseTexture.needsUpdate = true; - this.noiseTexture = new DataTexture( data, width, height, RedFormat, FloatType ); - this.noiseTexture.wrapS = RepeatWrapping; - this.noiseTexture.wrapT = RepeatWrapping; - this.noiseTexture.needsUpdate = true; + } - } + overrideVisibility() { - overrideVisibility() { + const scene = this.scene; + const cache = this._visibilityCache; - const scene = this.scene; - const cache = this._visibilityCache; + scene.traverse( function ( object ) { - scene.traverse( function ( object ) { + cache.set( object, object.visible ); - cache.set( object, object.visible ); + if ( object.isPoints || object.isLine ) object.visible = false; - if ( object.isPoints || object.isLine ) object.visible = false; + } ); - } ); + } - } + restoreVisibility() { - restoreVisibility() { + const scene = this.scene; + const cache = this._visibilityCache; - const scene = this.scene; - const cache = this._visibilityCache; + scene.traverse( function ( object ) { - scene.traverse( function ( object ) { + const visible = cache.get( object ); + object.visible = visible; - const visible = cache.get( object ); - object.visible = visible; + } ); - } ); + cache.clear(); - cache.clear(); + } } -} + SSAOPass.OUTPUT = { + 'Default': 0, + 'SSAO': 1, + 'Blur': 2, + 'Depth': 3, + 'Normal': 4 + }; -const OUTPUT = { - 'Default': 0, - 'SSAO': 1, - 'Blur': 2, - 'Depth': 3, - 'Normal': 4 -}; + return SSAOPass; -/* @__PURE__ */ Object.assign( SSAOPass, { OUTPUT } ); +} )(); export { SSAOPass }; diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index 3596325cb94141..5609bfee0b3dc2 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -21,623 +21,627 @@ import { SSRBlurShader } from '../shaders/SSRShader.js'; import { SSRDepthShader } from '../shaders/SSRShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; -class SSRPass extends Pass { +const SSRPass = /* @__PURE__ */ ( () => { - constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { + class SSRPass extends Pass { - super(); + constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + super(); - this.clear = true; + this.width = ( width !== undefined ) ? width : 512; + this.height = ( height !== undefined ) ? height : 512; - this.renderer = renderer; - this.scene = scene; - this.camera = camera; - this.groundReflector = groundReflector; + this.clear = true; - this.opacity = SSRShader.uniforms.opacity.value; - this.output = 0; + this.renderer = renderer; + this.scene = scene; + this.camera = camera; + this.groundReflector = groundReflector; - this.maxDistance = SSRShader.uniforms.maxDistance.value; - this.thickness = SSRShader.uniforms.thickness.value; + this.opacity = SSRShader.uniforms.opacity.value; + this.output = 0; - this.tempColor = new Color(); + this.maxDistance = SSRShader.uniforms.maxDistance.value; + this.thickness = SSRShader.uniforms.thickness.value; - this._selects = selects; - this.selective = Array.isArray( this._selects ); - Object.defineProperty( this, 'selects', { - get() { + this.tempColor = new Color(); - return this._selects; + this._selects = selects; + this.selective = Array.isArray( this._selects ); + Object.defineProperty( this, 'selects', { + get() { - }, - set( val ) { + return this._selects; - if ( this._selects === val ) return; - this._selects = val; - if ( Array.isArray( val ) ) { + }, + set( val ) { - this.selective = true; - this.ssrMaterial.defines.SELECTIVE = true; - this.ssrMaterial.needsUpdate = true; + if ( this._selects === val ) return; + this._selects = val; + if ( Array.isArray( val ) ) { - } else { + this.selective = true; + this.ssrMaterial.defines.SELECTIVE = true; + this.ssrMaterial.needsUpdate = true; - this.selective = false; - this.ssrMaterial.defines.SELECTIVE = false; - this.ssrMaterial.needsUpdate = true; + } else { + + this.selective = false; + this.ssrMaterial.defines.SELECTIVE = false; + this.ssrMaterial.needsUpdate = true; + + } } + } ); - } - } ); + this._bouncing = bouncing; + Object.defineProperty( this, 'bouncing', { + get() { - this._bouncing = bouncing; - Object.defineProperty( this, 'bouncing', { - get() { + return this._bouncing; - return this._bouncing; + }, + set( val ) { - }, - set( val ) { + if ( this._bouncing === val ) return; + this._bouncing = val; + if ( val ) { - if ( this._bouncing === val ) return; - this._bouncing = val; - if ( val ) { + this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; + } else { - } else { + this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + } } + } ); - } - } ); + this.blur = true; - this.blur = true; + this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION; + Object.defineProperty( this, 'distanceAttenuation', { + get() { - this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION; - Object.defineProperty( this, 'distanceAttenuation', { - get() { + return this._distanceAttenuation; - return this._distanceAttenuation; + }, + set( val ) { - }, - set( val ) { + if ( this._distanceAttenuation === val ) return; + this._distanceAttenuation = val; + this.ssrMaterial.defines.DISTANCE_ATTENUATION = val; + this.ssrMaterial.needsUpdate = true; - if ( this._distanceAttenuation === val ) return; - this._distanceAttenuation = val; - this.ssrMaterial.defines.DISTANCE_ATTENUATION = val; - this.ssrMaterial.needsUpdate = true; + } + } ); - } - } ); + this._fresnel = SSRShader.defines.FRESNEL; + Object.defineProperty( this, 'fresnel', { + get() { - this._fresnel = SSRShader.defines.FRESNEL; - Object.defineProperty( this, 'fresnel', { - get() { + return this._fresnel; - return this._fresnel; + }, + set( val ) { - }, - set( val ) { + if ( this._fresnel === val ) return; + this._fresnel = val; + this.ssrMaterial.defines.FRESNEL = val; + this.ssrMaterial.needsUpdate = true; - if ( this._fresnel === val ) return; - this._fresnel = val; - this.ssrMaterial.defines.FRESNEL = val; - this.ssrMaterial.needsUpdate = true; + } + } ); - } - } ); + this._infiniteThick = SSRShader.defines.INFINITE_THICK; + Object.defineProperty( this, 'infiniteThick', { + get() { - this._infiniteThick = SSRShader.defines.INFINITE_THICK; - Object.defineProperty( this, 'infiniteThick', { - get() { + return this._infiniteThick; - return this._infiniteThick; + }, + set( val ) { - }, - set( val ) { + if ( this._infiniteThick === val ) return; + this._infiniteThick = val; + this.ssrMaterial.defines.INFINITE_THICK = val; + this.ssrMaterial.needsUpdate = true; - if ( this._infiniteThick === val ) return; - this._infiniteThick = val; - this.ssrMaterial.defines.INFINITE_THICK = val; - this.ssrMaterial.needsUpdate = true; + } + } ); + + // beauty render target with depth buffer + + const depthTexture = new DepthTexture(); + depthTexture.type = UnsignedShortType; + depthTexture.minFilter = NearestFilter; + depthTexture.magFilter = NearestFilter; + + this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture, + depthBuffer: true + } ); + + //for bouncing + this.prevRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter + } ); + + // normal render target + + this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + } ); + + // metalness render target + + this.metalnessRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + } ); + + + + // ssr render target + + this.ssrRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter + } ); + + this.blurRenderTarget = this.ssrRenderTarget.clone(); + this.blurRenderTarget2 = this.ssrRenderTarget.clone(); + // this.blurRenderTarget3 = this.ssrRenderTarget.clone(); + + // ssr material + + this.ssrMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSRShader.defines, { + MAX_STEP: Math.sqrt( this.width * this.width + this.height * this.height ) + } ), + uniforms: UniformsUtils.clone( SSRShader.uniforms ), + vertexShader: SSRShader.vertexShader, + fragmentShader: SSRShader.fragmentShader, + blending: NoBlending + } ); + + this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.ssrMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; + this.ssrMaterial.defines.SELECTIVE = this.selective; + this.ssrMaterial.needsUpdate = true; + this.ssrMaterial.uniforms[ 'tMetalness' ].value = this.metalnessRenderTarget.texture; + this.ssrMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; + this.ssrMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.ssrMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; + this.ssrMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + // normal material + + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; + + // metalnessOn material + + this.metalnessOnMaterial = new MeshBasicMaterial( { + color: 'white' + } ); + + // metalnessOff material + + this.metalnessOffMaterial = new MeshBasicMaterial( { + color: 'black' + } ); + + // blur material + + this.blurMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSRBlurShader.defines ), + uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), + vertexShader: SSRBlurShader.vertexShader, + fragmentShader: SSRBlurShader.fragmentShader + } ); + this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + + // blur material 2 + + this.blurMaterial2 = new ShaderMaterial( { + defines: Object.assign( {}, SSRBlurShader.defines ), + uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), + vertexShader: SSRBlurShader.vertexShader, + fragmentShader: SSRBlurShader.fragmentShader + } ); + this.blurMaterial2.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; + this.blurMaterial2.uniforms[ 'resolution' ].value.set( this.width, this.height ); + + // // blur material 3 + + // this.blurMaterial3 = new ShaderMaterial({ + // defines: Object.assign({}, SSRBlurShader.defines), + // uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), + // vertexShader: SSRBlurShader.vertexShader, + // fragmentShader: SSRBlurShader.fragmentShader + // }); + // this.blurMaterial3.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture; + // this.blurMaterial3.uniforms['resolution'].value.set(this.width, this.height); + + // material for rendering the depth + + this.depthRenderMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSRDepthShader.defines ), + uniforms: UniformsUtils.clone( SSRDepthShader.uniforms ), + vertexShader: SSRDepthShader.vertexShader, + fragmentShader: SSRDepthShader.fragmentShader, + blending: NoBlending + } ); + this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; + this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + + // material for rendering the content of a render target + + this.copyMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blendSrc: SrcAlphaFactor, + blendDst: OneMinusSrcAlphaFactor, + blendEquation: AddEquation, + blendSrcAlpha: SrcAlphaFactor, + blendDstAlpha: OneMinusSrcAlphaFactor, + blendEquationAlpha: AddEquation, + // premultipliedAlpha:true, + } ); + + this.fsQuad = new FullScreenQuad( null ); + + this.originalClearColor = new Color(); - } - } ); - - // beauty render target with depth buffer - - const depthTexture = new DepthTexture(); - depthTexture.type = UnsignedShortType; - depthTexture.minFilter = NearestFilter; - depthTexture.magFilter = NearestFilter; - - this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - depthTexture: depthTexture, - depthBuffer: true - } ); - - //for bouncing - this.prevRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter - } ); - - // normal render target - - this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - } ); - - // metalness render target - - this.metalnessRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - } ); - - - - // ssr render target - - this.ssrRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter - } ); - - this.blurRenderTarget = this.ssrRenderTarget.clone(); - this.blurRenderTarget2 = this.ssrRenderTarget.clone(); - // this.blurRenderTarget3 = this.ssrRenderTarget.clone(); - - // ssr material - - this.ssrMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSRShader.defines, { - MAX_STEP: Math.sqrt( this.width * this.width + this.height * this.height ) - } ), - uniforms: UniformsUtils.clone( SSRShader.uniforms ), - vertexShader: SSRShader.vertexShader, - fragmentShader: SSRShader.fragmentShader, - blending: NoBlending - } ); - - this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; - this.ssrMaterial.defines.SELECTIVE = this.selective; - this.ssrMaterial.needsUpdate = true; - this.ssrMaterial.uniforms[ 'tMetalness' ].value = this.metalnessRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; - this.ssrMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.ssrMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; - this.ssrMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - - // normal material - - this.normalMaterial = new MeshNormalMaterial(); - this.normalMaterial.blending = NoBlending; - - // metalnessOn material - - this.metalnessOnMaterial = new MeshBasicMaterial( { - color: 'white' - } ); - - // metalnessOff material - - this.metalnessOffMaterial = new MeshBasicMaterial( { - color: 'black' - } ); - - // blur material - - this.blurMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSRBlurShader.defines ), - uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), - vertexShader: SSRBlurShader.vertexShader, - fragmentShader: SSRBlurShader.fragmentShader - } ); - this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - - // blur material 2 - - this.blurMaterial2 = new ShaderMaterial( { - defines: Object.assign( {}, SSRBlurShader.defines ), - uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), - vertexShader: SSRBlurShader.vertexShader, - fragmentShader: SSRBlurShader.fragmentShader - } ); - this.blurMaterial2.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; - this.blurMaterial2.uniforms[ 'resolution' ].value.set( this.width, this.height ); - - // // blur material 3 - - // this.blurMaterial3 = new ShaderMaterial({ - // defines: Object.assign({}, SSRBlurShader.defines), - // uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), - // vertexShader: SSRBlurShader.vertexShader, - // fragmentShader: SSRBlurShader.fragmentShader - // }); - // this.blurMaterial3.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture; - // this.blurMaterial3.uniforms['resolution'].value.set(this.width, this.height); - - // material for rendering the depth - - this.depthRenderMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSRDepthShader.defines ), - uniforms: UniformsUtils.clone( SSRDepthShader.uniforms ), - vertexShader: SSRDepthShader.vertexShader, - fragmentShader: SSRDepthShader.fragmentShader, - blending: NoBlending - } ); - this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; - this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - - // material for rendering the content of a render target - - this.copyMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( CopyShader.uniforms ), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - transparent: true, - depthTest: false, - depthWrite: false, - blendSrc: SrcAlphaFactor, - blendDst: OneMinusSrcAlphaFactor, - blendEquation: AddEquation, - blendSrcAlpha: SrcAlphaFactor, - blendDstAlpha: OneMinusSrcAlphaFactor, - blendEquationAlpha: AddEquation, - // premultipliedAlpha:true, - } ); - - this.fsQuad = new FullScreenQuad( null ); - - this.originalClearColor = new Color(); + } - } + dispose() { - dispose() { + // dispose render targets - // dispose render targets + this.beautyRenderTarget.dispose(); + this.prevRenderTarget.dispose(); + this.normalRenderTarget.dispose(); + this.metalnessRenderTarget.dispose(); + this.ssrRenderTarget.dispose(); + this.blurRenderTarget.dispose(); + this.blurRenderTarget2.dispose(); + // this.blurRenderTarget3.dispose(); - this.beautyRenderTarget.dispose(); - this.prevRenderTarget.dispose(); - this.normalRenderTarget.dispose(); - this.metalnessRenderTarget.dispose(); - this.ssrRenderTarget.dispose(); - this.blurRenderTarget.dispose(); - this.blurRenderTarget2.dispose(); - // this.blurRenderTarget3.dispose(); + // dispose materials - // dispose materials + this.normalMaterial.dispose(); + this.metalnessOnMaterial.dispose(); + this.metalnessOffMaterial.dispose(); + this.blurMaterial.dispose(); + this.blurMaterial2.dispose(); + this.copyMaterial.dispose(); + this.depthRenderMaterial.dispose(); - this.normalMaterial.dispose(); - this.metalnessOnMaterial.dispose(); - this.metalnessOffMaterial.dispose(); - this.blurMaterial.dispose(); - this.blurMaterial2.dispose(); - this.copyMaterial.dispose(); - this.depthRenderMaterial.dispose(); + // dipsose full screen quad - // dipsose full screen quad + this.fsQuad.dispose(); - this.fsQuad.dispose(); + } - } + render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { - render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { + // render beauty and depth - // render beauty and depth + renderer.setRenderTarget( this.beautyRenderTarget ); + renderer.clear(); + if ( this.groundReflector ) { - renderer.setRenderTarget( this.beautyRenderTarget ); - renderer.clear(); - if ( this.groundReflector ) { + this.groundReflector.visible = false; + this.groundReflector.doRender( this.renderer, this.scene, this.camera ); + this.groundReflector.visible = true; - this.groundReflector.visible = false; - this.groundReflector.doRender( this.renderer, this.scene, this.camera ); - this.groundReflector.visible = true; + } - } + renderer.render( this.scene, this.camera ); + if ( this.groundReflector ) this.groundReflector.visible = false; - renderer.render( this.scene, this.camera ); - if ( this.groundReflector ) this.groundReflector.visible = false; + // render normals - // render normals + this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); + // render metalnesses - // render metalnesses + if ( this.selective ) { - if ( this.selective ) { + this.renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); - this.renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); + } - } + // render SSR - // render SSR + this.ssrMaterial.uniforms[ 'opacity' ].value = this.opacity; + this.ssrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; + this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; + this.renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); - this.ssrMaterial.uniforms[ 'opacity' ].value = this.opacity; - this.ssrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; - this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; - this.renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); + // render blur - // render blur + if ( this.blur ) { - if ( this.blur ) { + this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + this.renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); + // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); - this.renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); - // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); + } - } + // output result to screen - // output result to screen + switch ( this.output ) { - switch ( this.output ) { + case SSRPass.OUTPUT.Default: - case SSRPass.OUTPUT.Default: + if ( this.bouncing ) { - if ( this.bouncing ) { + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + if ( this.blur ) + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; + else + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.copyMaterial.blending = NormalBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - if ( this.blur ) - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; - else - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + } else { - } else { + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + if ( this.blur ) + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; + else + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.copyMaterial.blending = NormalBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + + } + + break; + case SSRPass.OUTPUT.SSR: if ( this.blur ) this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NormalBlending; + this.copyMaterial.blending = NoBlending; this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - } - - break; - case SSRPass.OUTPUT.SSR: - - if ( this.blur ) - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; - else - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + if ( this.bouncing ) { - if ( this.bouncing ) { + if ( this.blur ) + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; + else + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - if ( this.blur ) - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; - else - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.copyMaterial.blending = NormalBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + } - } + break; - break; + case SSRPass.OUTPUT.Beauty: - case SSRPass.OUTPUT.Beauty: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSRPass.OUTPUT.Depth: - case SSRPass.OUTPUT.Depth: + this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSRPass.OUTPUT.Normal: - case SSRPass.OUTPUT.Normal: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + case SSRPass.OUTPUT.Metalness: - case SSRPass.OUTPUT.Metalness: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.metalnessRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.metalnessRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + break; - break; + default: + console.warn( 'THREE.SSRPass: Unknown output type.' ); - default: - console.warn( 'THREE.SSRPass: Unknown output type.' ); + } } - } + renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + // save original state + this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); + const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); + const originalAutoClear = renderer.autoClear; - // save original state - this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); - const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); - const originalAutoClear = renderer.autoClear; + renderer.setRenderTarget( renderTarget ); - renderer.setRenderTarget( renderTarget ); + // setup pass state + renderer.autoClear = false; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - // setup pass state - renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + } - } + this.fsQuad.material = passMaterial; + this.fsQuad.render( renderer ); - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + } - } + renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); + const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); + const originalAutoClear = renderer.autoClear; - this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); - const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); - const originalAutoClear = renderer.autoClear; + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + } - } + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; - this.scene.overrideMaterial = overrideMaterial; - renderer.render( this.scene, this.camera ); - this.scene.overrideMaterial = null; + // restore original state - // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + } - } + renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); + const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); + const originalAutoClear = renderer.autoClear; - this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); - const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); - const originalAutoClear = renderer.autoClear; + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + } - } + this.scene.traverseVisible( child => { - this.scene.traverseVisible( child => { + child._SSRPassBackupMaterial = child.material; + if ( this._selects.includes( child ) ) { - child._SSRPassBackupMaterial = child.material; - if ( this._selects.includes( child ) ) { + child.material = this.metalnessOnMaterial; - child.material = this.metalnessOnMaterial; + } else { - } else { + child.material = this.metalnessOffMaterial; - child.material = this.metalnessOffMaterial; + } - } + } ); + renderer.render( this.scene, this.camera ); + this.scene.traverseVisible( child => { - } ); - renderer.render( this.scene, this.camera ); - this.scene.traverseVisible( child => { + child.material = child._SSRPassBackupMaterial; - child.material = child._SSRPassBackupMaterial; + } ); - } ); + // restore original state - // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + } - } + setSize( width, height ) { - setSize( width, height ) { + this.width = width; + this.height = height; - this.width = width; - this.height = height; + this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); + this.ssrMaterial.needsUpdate = true; + this.beautyRenderTarget.setSize( width, height ); + this.prevRenderTarget.setSize( width, height ); + this.ssrRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.metalnessRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); + this.blurRenderTarget2.setSize( width, height ); + // this.blurRenderTarget3.setSize(width, height); - this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); - this.ssrMaterial.needsUpdate = true; - this.beautyRenderTarget.setSize( width, height ); - this.prevRenderTarget.setSize( width, height ); - this.ssrRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.metalnessRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); - this.blurRenderTarget2.setSize( width, height ); - // this.blurRenderTarget3.setSize(width, height); + this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); + } } -} + SSRPass.OUTPUT = { + 'Default': 0, + 'SSR': 1, + 'Beauty': 3, + 'Depth': 4, + 'Normal': 5, + 'Metalness': 7, + }; -const OUTPUT = { - 'Default': 0, - 'SSR': 1, - 'Beauty': 3, - 'Depth': 4, - 'Normal': 5, - 'Metalness': 7, -}; + return SSRPass; -/* @__PURE__ */ Object.assign( SSRPass, { OUTPUT } ); +} )(); export { SSRPass }; diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index 1fffa33f80579c..d097dbcaa207ce 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -22,396 +22,401 @@ import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js * Reference: * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ */ -class UnrealBloomPass extends Pass { - constructor( resolution, strength, radius, threshold ) { +const UnrealBloomPass = /* @__PURE__ */ ( () => { - super(); + class UnrealBloomPass extends Pass { - this.strength = ( strength !== undefined ) ? strength : 1; - this.radius = radius; - this.threshold = threshold; - this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); + constructor( resolution, strength, radius, threshold ) { - // create color only once here, reuse it later inside the render function - this.clearColor = new Color( 0, 0, 0 ); + super(); - // render targets - this.renderTargetsHorizontal = []; - this.renderTargetsVertical = []; - this.nMips = 5; - let resx = Math.round( this.resolution.x / 2 ); - let resy = Math.round( this.resolution.y / 2 ); + this.strength = ( strength !== undefined ) ? strength : 1; + this.radius = radius; + this.threshold = threshold; + this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; - this.renderTargetBright.texture.generateMipmaps = false; + // create color only once here, reuse it later inside the render function + this.clearColor = new Color( 0, 0, 0 ); - for ( let i = 0; i < this.nMips; i ++ ) { + // render targets + this.renderTargetsHorizontal = []; + this.renderTargetsVertical = []; + this.nMips = 5; + let resx = Math.round( this.resolution.x / 2 ); + let resy = Math.round( this.resolution.y / 2 ); - const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; + this.renderTargetBright.texture.generateMipmaps = false; - renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; - renderTargetHorizonal.texture.generateMipmaps = false; + for ( let i = 0; i < this.nMips; i ++ ) { - this.renderTargetsHorizontal.push( renderTargetHorizonal ); + const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; + renderTargetHorizonal.texture.generateMipmaps = false; - renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; - renderTargetVertical.texture.generateMipmaps = false; + this.renderTargetsHorizontal.push( renderTargetHorizonal ); - this.renderTargetsVertical.push( renderTargetVertical ); + const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - resx = Math.round( resx / 2 ); + renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; + renderTargetVertical.texture.generateMipmaps = false; - resy = Math.round( resy / 2 ); + this.renderTargetsVertical.push( renderTargetVertical ); - } - - // luminosity high pass material + resx = Math.round( resx / 2 ); - const highPassShader = LuminosityHighPassShader; - this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); + resy = Math.round( resy / 2 ); - this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; - this.highPassUniforms[ 'smoothWidth' ].value = 0.01; + } - this.materialHighPassFilter = new ShaderMaterial( { - uniforms: this.highPassUniforms, - vertexShader: highPassShader.vertexShader, - fragmentShader: highPassShader.fragmentShader - } ); + // luminosity high pass material - // gaussian blur materials + const highPassShader = LuminosityHighPassShader; + this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); - this.separableBlurMaterials = []; - const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; - resx = Math.round( this.resolution.x / 2 ); - resy = Math.round( this.resolution.y / 2 ); + this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; + this.highPassUniforms[ 'smoothWidth' ].value = 0.01; - for ( let i = 0; i < this.nMips; i ++ ) { + this.materialHighPassFilter = new ShaderMaterial( { + uniforms: this.highPassUniforms, + vertexShader: highPassShader.vertexShader, + fragmentShader: highPassShader.fragmentShader + } ); - this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); + // gaussian blur materials - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); + this.separableBlurMaterials = []; + const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; + resx = Math.round( this.resolution.x / 2 ); + resy = Math.round( this.resolution.y / 2 ); - resx = Math.round( resx / 2 ); + for ( let i = 0; i < this.nMips; i ++ ) { - resy = Math.round( resy / 2 ); + this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); - } + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - // composite material + resx = Math.round( resx / 2 ); - this.compositeMaterial = this.getCompositeMaterial( this.nMips ); - this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; + resy = Math.round( resy / 2 ); - const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; - this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; - this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; + } - // blend material + // composite material - const copyShader = CopyShader; + this.compositeMaterial = this.getCompositeMaterial( this.nMips ); + this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; + this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; + this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; + this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; + this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; + this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - this.blendMaterial = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true - } ); + // blend material - this.enabled = true; - this.needsSwap = false; + const copyShader = CopyShader; - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - this.basic = new MeshBasicMaterial(); + this.blendMaterial = new ShaderMaterial( { + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + blending: AdditiveBlending, + depthTest: false, + depthWrite: false, + transparent: true + } ); - this.fsQuad = new FullScreenQuad( null ); + this.enabled = true; + this.needsSwap = false; - } + this._oldClearColor = new Color(); + this.oldClearAlpha = 1; - dispose() { + this.basic = new MeshBasicMaterial(); - for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { - - this.renderTargetsHorizontal[ i ].dispose(); + this.fsQuad = new FullScreenQuad( null ); } - for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { - - this.renderTargetsVertical[ i ].dispose(); + dispose() { - } + for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { - this.renderTargetBright.dispose(); + this.renderTargetsHorizontal[ i ].dispose(); - // + } - for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { + for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { - this.separableBlurMaterials[ i ].dispose(); + this.renderTargetsVertical[ i ].dispose(); - } + } - this.compositeMaterial.dispose(); - this.blendMaterial.dispose(); - this.basic.dispose(); + this.renderTargetBright.dispose(); - // + // - this.fsQuad.dispose(); + for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { - } + this.separableBlurMaterials[ i ].dispose(); - setSize( width, height ) { + } - let resx = Math.round( width / 2 ); - let resy = Math.round( height / 2 ); + this.compositeMaterial.dispose(); + this.blendMaterial.dispose(); + this.basic.dispose(); - this.renderTargetBright.setSize( resx, resy ); + // - for ( let i = 0; i < this.nMips; i ++ ) { + this.fsQuad.dispose(); - this.renderTargetsHorizontal[ i ].setSize( resx, resy ); - this.renderTargetsVertical[ i ].setSize( resx, resy ); + } - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); + setSize( width, height ) { - resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); + let resx = Math.round( width / 2 ); + let resy = Math.round( height / 2 ); - } + this.renderTargetBright.setSize( resx, resy ); - } + for ( let i = 0; i < this.nMips; i ++ ) { - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + this.renderTargetsHorizontal[ i ].setSize( resx, resy ); + this.renderTargetsVertical[ i ].setSize( resx, resy ); - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - renderer.setClearColor( this.clearColor, 0 ); + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + } - // Render input to screen + } - if ( this.renderToScreen ) { + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - this.fsQuad.material = this.basic; - this.basic.map = readBuffer.texture; + renderer.getClearColor( this._oldClearColor ); + this.oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; - renderer.setRenderTarget( null ); - renderer.clear(); - this.fsQuad.render( renderer ); + renderer.setClearColor( this.clearColor, 0 ); - } + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - // 1. Extract Bright Areas + // Render input to screen - this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this.fsQuad.material = this.materialHighPassFilter; + if ( this.renderToScreen ) { - renderer.setRenderTarget( this.renderTargetBright ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.fsQuad.material = this.basic; + this.basic.map = readBuffer.texture; - // 2. Blur All the mips progressively + renderer.setRenderTarget( null ); + renderer.clear(); + this.fsQuad.render( renderer ); - let inputRenderTarget = this.renderTargetBright; + } - for ( let i = 0; i < this.nMips; i ++ ) { + // 1. Extract Bright Areas - this.fsQuad.material = this.separableBlurMaterials[ i ]; + this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; + this.fsQuad.material = this.materialHighPassFilter; - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; - renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); + renderer.setRenderTarget( this.renderTargetBright ); renderer.clear(); this.fsQuad.render( renderer ); - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + // 2. Blur All the mips progressively - inputRenderTarget = this.renderTargetsVertical[ i ]; + let inputRenderTarget = this.renderTargetBright; - } + for ( let i = 0; i < this.nMips; i ++ ) { - // Composite All the mips + this.fsQuad.material = this.separableBlurMaterials[ i ]; - this.fsQuad.material = this.compositeMaterial; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; + this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; + this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; + renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); + renderer.clear(); + this.fsQuad.render( renderer ); - renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; + this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); + renderer.clear(); + this.fsQuad.render( renderer ); - // Blend it additively over the input texture + inputRenderTarget = this.renderTargetsVertical[ i ]; - this.fsQuad.material = this.blendMaterial; - this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; + } - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + // Composite All the mips - if ( this.renderToScreen ) { + this.fsQuad.material = this.compositeMaterial; + this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; + this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; + this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - renderer.setRenderTarget( null ); + renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); + renderer.clear(); this.fsQuad.render( renderer ); - } else { + // Blend it additively over the input texture - renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + this.fsQuad.material = this.blendMaterial; + this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; - } + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - // Restore renderer settings + if ( this.renderToScreen ) { - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; + renderer.setRenderTarget( null ); + this.fsQuad.render( renderer ); - } + } else { - getSeperableBlurMaterial( kernelRadius ) { + renderer.setRenderTarget( readBuffer ); + this.fsQuad.render( renderer ); - const coefficients = []; + } - for ( let i = 0; i < kernelRadius; i ++ ) { + // Restore renderer settings - coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.autoClear = oldAutoClear; } - return new ShaderMaterial( { - - defines: { - 'KERNEL_RADIUS': kernelRadius - }, - - uniforms: { - 'colorTexture': { value: null }, - 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size - 'direction': { value: new Vector2( 0.5, 0.5 ) }, - 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients - }, - - vertexShader: - `varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `#include - varying vec2 vUv; - uniform sampler2D colorTexture; - uniform vec2 invSize; - uniform vec2 direction; - uniform float gaussianCoefficients[KERNEL_RADIUS]; - - void main() { - float weightSum = gaussianCoefficients[0]; - vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; - for( int i = 1; i < KERNEL_RADIUS; i ++ ) { - float x = float(i); - float w = gaussianCoefficients[i]; - vec2 uvOffset = direction * invSize * x; - vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; - vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; - diffuseSum += (sample1 + sample2) * w; - weightSum += 2.0 * w; + getSeperableBlurMaterial( kernelRadius ) { + + const coefficients = []; + + for ( let i = 0; i < kernelRadius; i ++ ) { + + coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + + } + + return new ShaderMaterial( { + + defines: { + 'KERNEL_RADIUS': kernelRadius + }, + + uniforms: { + 'colorTexture': { value: null }, + 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size + 'direction': { value: new Vector2( 0.5, 0.5 ) }, + 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients + }, + + vertexShader: + `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `#include + varying vec2 vUv; + uniform sampler2D colorTexture; + uniform vec2 invSize; + uniform vec2 direction; + uniform float gaussianCoefficients[KERNEL_RADIUS]; + + void main() { + float weightSum = gaussianCoefficients[0]; + vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; + for( int i = 1; i < KERNEL_RADIUS; i ++ ) { + float x = float(i); + float w = gaussianCoefficients[i]; + vec2 uvOffset = direction * invSize * x; + vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; + vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; + diffuseSum += (sample1 + sample2) * w; + weightSum += 2.0 * w; + } + gl_FragColor = vec4(diffuseSum/weightSum, 1.0); + }` + } ); + + } + + getCompositeMaterial( nMips ) { + + return new ShaderMaterial( { + + defines: { + 'NUM_MIPS': nMips + }, + + uniforms: { + 'blurTexture1': { value: null }, + 'blurTexture2': { value: null }, + 'blurTexture3': { value: null }, + 'blurTexture4': { value: null }, + 'blurTexture5': { value: null }, + 'bloomStrength': { value: 1.0 }, + 'bloomFactors': { value: null }, + 'bloomTintColors': { value: null }, + 'bloomRadius': { value: 0.0 } + }, + + vertexShader: + `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `varying vec2 vUv; + uniform sampler2D blurTexture1; + uniform sampler2D blurTexture2; + uniform sampler2D blurTexture3; + uniform sampler2D blurTexture4; + uniform sampler2D blurTexture5; + uniform float bloomStrength; + uniform float bloomRadius; + uniform float bloomFactors[NUM_MIPS]; + uniform vec3 bloomTintColors[NUM_MIPS]; + + float lerpBloomFactor(const in float factor) { + float mirrorFactor = 1.2 - factor; + return mix(factor, mirrorFactor, bloomRadius); } - gl_FragColor = vec4(diffuseSum/weightSum, 1.0); - }` - } ); - } + void main() { + gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + + lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + + lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + + lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + + lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); + }` + } ); - getCompositeMaterial( nMips ) { - - return new ShaderMaterial( { - - defines: { - 'NUM_MIPS': nMips - }, - - uniforms: { - 'blurTexture1': { value: null }, - 'blurTexture2': { value: null }, - 'blurTexture3': { value: null }, - 'blurTexture4': { value: null }, - 'blurTexture5': { value: null }, - 'bloomStrength': { value: 1.0 }, - 'bloomFactors': { value: null }, - 'bloomTintColors': { value: null }, - 'bloomRadius': { value: 0.0 } - }, - - vertexShader: - `varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `varying vec2 vUv; - uniform sampler2D blurTexture1; - uniform sampler2D blurTexture2; - uniform sampler2D blurTexture3; - uniform sampler2D blurTexture4; - uniform sampler2D blurTexture5; - uniform float bloomStrength; - uniform float bloomRadius; - uniform float bloomFactors[NUM_MIPS]; - uniform vec3 bloomTintColors[NUM_MIPS]; - - float lerpBloomFactor(const in float factor) { - float mirrorFactor = 1.2 - factor; - return mix(factor, mirrorFactor, bloomRadius); - } - - void main() { - gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + - lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + - lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + - lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + - lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); - }` - } ); + } } -} + UnrealBloomPass.BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); + UnrealBloomPass.BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); -const BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); -const BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); + return UnrealBloomPass; -/* @__PURE__ */ Object.assign( UnrealBloomPass, { BlurDirectionX, BlurDirectionY } ); +} )(); export { UnrealBloomPass }; diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index 98be56e2d5841e..281047322074d5 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -1,201 +1,206 @@ -class VRButton { +const VRButton = /* @__PURE__ */ ( () => { - static createButton( renderer ) { + class VRButton { - const button = document.createElement( 'button' ); + static createButton( renderer ) { - function showEnterVR( /*device*/ ) { + const button = document.createElement( 'button' ); - let currentSession = null; + function showEnterVR( /*device*/ ) { - async function onSessionStarted( session ) { + let currentSession = null; - session.addEventListener( 'end', onSessionEnded ); + async function onSessionStarted( session ) { - await renderer.xr.setSession( session ); - button.textContent = 'EXIT VR'; + session.addEventListener( 'end', onSessionEnded ); - currentSession = session; + await renderer.xr.setSession( session ); + button.textContent = 'EXIT VR'; - } + currentSession = session; - function onSessionEnded( /*event*/ ) { + } - currentSession.removeEventListener( 'end', onSessionEnded ); + function onSessionEnded( /*event*/ ) { - button.textContent = 'ENTER VR'; + currentSession.removeEventListener( 'end', onSessionEnded ); - currentSession = null; + button.textContent = 'ENTER VR'; - } + currentSession = null; - // + } - button.style.display = ''; + // - button.style.cursor = 'pointer'; - button.style.left = 'calc(50% - 50px)'; - button.style.width = '100px'; + button.style.display = ''; - button.textContent = 'ENTER VR'; + button.style.cursor = 'pointer'; + button.style.left = 'calc(50% - 50px)'; + button.style.width = '100px'; - button.onmouseenter = function () { + button.textContent = 'ENTER VR'; - button.style.opacity = '1.0'; + button.onmouseenter = function () { - }; + button.style.opacity = '1.0'; - button.onmouseleave = function () { + }; - button.style.opacity = '0.5'; + button.onmouseleave = function () { - }; + button.style.opacity = '0.5'; - button.onclick = function () { + }; - if ( currentSession === null ) { + button.onclick = function () { - // WebXR's requestReferenceSpace only works if the corresponding feature - // was requested at session creation time. For simplicity, just ask for - // the interesting ones as optional features, but be aware that the - // requestReferenceSpace call will fail if it turns out to be unavailable. - // ('local' is always available for immersive sessions and doesn't need to - // be requested separately.) + if ( currentSession === null ) { - const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; - navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); + // WebXR's requestReferenceSpace only works if the corresponding feature + // was requested at session creation time. For simplicity, just ask for + // the interesting ones as optional features, but be aware that the + // requestReferenceSpace call will fail if it turns out to be unavailable. + // ('local' is always available for immersive sessions and doesn't need to + // be requested separately.) - } else { + const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; + navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); - currentSession.end(); + } else { - } + currentSession.end(); - }; + } - } + }; - function disableButton() { + } - button.style.display = ''; + function disableButton() { - button.style.cursor = 'auto'; - button.style.left = 'calc(50% - 75px)'; - button.style.width = '150px'; + button.style.display = ''; - button.onmouseenter = null; - button.onmouseleave = null; + button.style.cursor = 'auto'; + button.style.left = 'calc(50% - 75px)'; + button.style.width = '150px'; - button.onclick = null; + button.onmouseenter = null; + button.onmouseleave = null; - } + button.onclick = null; - function showWebXRNotFound() { + } - disableButton(); + function showWebXRNotFound() { - button.textContent = 'VR NOT SUPPORTED'; + disableButton(); - } + button.textContent = 'VR NOT SUPPORTED'; - function showVRNotAllowed( exception ) { + } - disableButton(); + function showVRNotAllowed( exception ) { - console.warn( 'Exception when trying to call xr.isSessionSupported', exception ); + disableButton(); - button.textContent = 'VR NOT ALLOWED'; + console.warn( 'Exception when trying to call xr.isSessionSupported', exception ); - } + button.textContent = 'VR NOT ALLOWED'; - function stylizeElement( element ) { - - element.style.position = 'absolute'; - element.style.bottom = '20px'; - element.style.padding = '12px 6px'; - element.style.border = '1px solid #fff'; - element.style.borderRadius = '4px'; - element.style.background = 'rgba(0,0,0,0.1)'; - element.style.color = '#fff'; - element.style.font = 'normal 13px sans-serif'; - element.style.textAlign = 'center'; - element.style.opacity = '0.5'; - element.style.outline = 'none'; - element.style.zIndex = '999'; + } - } + function stylizeElement( element ) { + + element.style.position = 'absolute'; + element.style.bottom = '20px'; + element.style.padding = '12px 6px'; + element.style.border = '1px solid #fff'; + element.style.borderRadius = '4px'; + element.style.background = 'rgba(0,0,0,0.1)'; + element.style.color = '#fff'; + element.style.font = 'normal 13px sans-serif'; + element.style.textAlign = 'center'; + element.style.opacity = '0.5'; + element.style.outline = 'none'; + element.style.zIndex = '999'; - if ( 'xr' in navigator ) { + } - button.id = 'VRButton'; - button.style.display = 'none'; + if ( 'xr' in navigator ) { - stylizeElement( button ); + button.id = 'VRButton'; + button.style.display = 'none'; - navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) { + stylizeElement( button ); - supported ? showEnterVR() : showWebXRNotFound(); + navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) { - if ( supported && VRButton.xrSessionIsGranted ) { + supported ? showEnterVR() : showWebXRNotFound(); - button.click(); + if ( supported && VRButton.xrSessionIsGranted ) { - } + button.click(); - } ).catch( showVRNotAllowed ); + } - return button; + } ).catch( showVRNotAllowed ); - } else { + return button; - const message = document.createElement( 'a' ); + } else { - if ( window.isSecureContext === false ) { + const message = document.createElement( 'a' ); - message.href = document.location.href.replace( /^http:/, 'https:' ); - message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message + if ( window.isSecureContext === false ) { - } else { + message.href = document.location.href.replace( /^http:/, 'https:' ); + message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message - message.href = 'https://immersiveweb.dev/'; - message.innerHTML = 'WEBXR NOT AVAILABLE'; + } else { - } + message.href = 'https://immersiveweb.dev/'; + message.innerHTML = 'WEBXR NOT AVAILABLE'; - message.style.left = 'calc(50% - 90px)'; - message.style.width = '180px'; - message.style.textDecoration = 'none'; + } - stylizeElement( message ); + message.style.left = 'calc(50% - 90px)'; + message.style.width = '180px'; + message.style.textDecoration = 'none'; - return message; + stylizeElement( message ); - } + return message; - } + } - static registerSessionGrantedListener() { + } - VRButton.xrSessionIsGranted = false; + static registerSessionGrantedListener() { - if ( typeof navigator !== 'undefined' && 'xr' in navigator ) { + if ( typeof navigator !== 'undefined' && 'xr' in navigator ) { - // WebXRViewer (based on Firefox) has a bug where addEventListener - // throws a silent exception and aborts execution entirely. - if ( /WebXRViewer\//i.test( navigator.userAgent ) ) return; + // WebXRViewer (based on Firefox) has a bug where addEventListener + // throws a silent exception and aborts execution entirely. + if ( /WebXRViewer\//i.test( navigator.userAgent ) ) return; - navigator.xr.addEventListener( 'sessiongranted', () => { + navigator.xr.addEventListener( 'sessiongranted', () => { - VRButton.xrSessionIsGranted = true; + VRButton.xrSessionIsGranted = true; - } ); + } ); + + } } } -} + VRButton.xrSessionIsGranted = false; + VRButton.registerSessionGrantedListener(); + + return VRButton; -/* @__PURE__ */ VRButton.registerSessionGrantedListener(); +} )(); export { VRButton }; From e4fa50b796f2bd218889d8488cc771e93fa75e9e Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 05:09:08 -0500 Subject: [PATCH 37/45] Addons: flatten whitespace in IIFEs Tries to minimize diff from 6d46eb48ae490be6214ce613c64fbdc4ae5f9f4f. --- examples/jsm/exporters/DRACOExporter.js | 270 +- examples/jsm/exporters/GLTFExporter.js | 3945 ++++++++--------- examples/jsm/libs/utif.module.js | 2576 +++++------ examples/jsm/lines/LineMaterial.js | 988 ++--- examples/jsm/loaders/KTX2Loader.js | 1080 ++--- examples/jsm/loaders/LogLuvLoader.js | 698 +-- examples/jsm/loaders/RGBMLoader.js | 1072 ++--- examples/jsm/objects/Lensflare.js | 470 +- examples/jsm/objects/Reflector.js | 326 +- examples/jsm/objects/ReflectorForSSRPass.js | 512 +-- examples/jsm/objects/Refractor.js | 378 +- examples/jsm/objects/Sky.js | 300 +- examples/jsm/objects/Water2.js | 480 +- examples/jsm/postprocessing/BloomPass.js | 192 +- examples/jsm/postprocessing/OutlinePass.js | 940 ++-- examples/jsm/postprocessing/SAOPass.js | 496 +-- examples/jsm/postprocessing/SSAOPass.js | 510 +-- examples/jsm/postprocessing/SSRPass.js | 920 ++-- .../jsm/postprocessing/UnrealBloomPass.js | 558 +-- examples/jsm/webxr/VRButton.js | 230 +- 20 files changed, 8470 insertions(+), 8471 deletions(-) diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index d70548db6edfa9..bbd86143317322 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -19,254 +19,254 @@ import { Color } from 'three'; const DRACOExporter = /* @__PURE__ */ ( () => { - class DRACOExporter { +class DRACOExporter { - parse( object, options = {} ) { + parse( object, options = {} ) { - options = Object.assign( { - decodeSpeed: 5, - encodeSpeed: 5, - encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, - quantization: [ 16, 8, 8, 8, 8 ], - exportUvs: true, - exportNormals: true, - exportColor: false, - }, options ); + options = Object.assign( { + decodeSpeed: 5, + encodeSpeed: 5, + encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, + quantization: [ 16, 8, 8, 8, 8 ], + exportUvs: true, + exportNormals: true, + exportColor: false, + }, options ); - if ( DracoEncoderModule === undefined ) { + if ( DracoEncoderModule === undefined ) { - throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); + throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); - } - - const geometry = object.geometry; - - const dracoEncoder = DracoEncoderModule(); - const encoder = new dracoEncoder.Encoder(); - let builder; - let dracoObject; + } - if ( object.isMesh === true ) { + const geometry = object.geometry; - builder = new dracoEncoder.MeshBuilder(); - dracoObject = new dracoEncoder.Mesh(); + const dracoEncoder = DracoEncoderModule(); + const encoder = new dracoEncoder.Encoder(); + let builder; + let dracoObject; - const vertices = geometry.getAttribute( 'position' ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + if ( object.isMesh === true ) { - const faces = geometry.getIndex(); + builder = new dracoEncoder.MeshBuilder(); + dracoObject = new dracoEncoder.Mesh(); - if ( faces !== null ) { + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); - builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array ); + const faces = geometry.getIndex(); - } else { + if ( faces !== null ) { - const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); + builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array ); - for ( let i = 0; i < faces.length; i ++ ) { + } else { - faces[ i ] = i; + const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); - } + for ( let i = 0; i < faces.length; i ++ ) { - builder.AddFacesToMesh( dracoObject, vertices.count, faces ); + faces[ i ] = i; } - if ( options.exportNormals === true ) { + builder.AddFacesToMesh( dracoObject, vertices.count, faces ); + + } - const normals = geometry.getAttribute( 'normal' ); + if ( options.exportNormals === true ) { - if ( normals !== undefined ) { + const normals = geometry.getAttribute( 'normal' ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array ); + if ( normals !== undefined ) { - } + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array ); } - if ( options.exportUvs === true ) { + } - const uvs = geometry.getAttribute( 'uv' ); + if ( options.exportUvs === true ) { - if ( uvs !== undefined ) { + const uvs = geometry.getAttribute( 'uv' ); - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array ); + if ( uvs !== undefined ) { - } + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array ); } - if ( options.exportColor === true ) { + } - const colors = geometry.getAttribute( 'color' ); + if ( options.exportColor === true ) { - if ( colors !== undefined ) { + const colors = geometry.getAttribute( 'color' ); - const array = createVertexColorSRGBArray( colors ); + if ( colors !== undefined ) { - builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); + const array = createVertexColorSRGBArray( colors ); - } + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); } - } else if ( object.isPoints === true ) { + } - builder = new dracoEncoder.PointCloudBuilder(); - dracoObject = new dracoEncoder.PointCloud(); + } else if ( object.isPoints === true ) { - const vertices = geometry.getAttribute( 'position' ); - builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + builder = new dracoEncoder.PointCloudBuilder(); + dracoObject = new dracoEncoder.PointCloud(); - if ( options.exportColor === true ) { + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); - const colors = geometry.getAttribute( 'color' ); + if ( options.exportColor === true ) { - if ( colors !== undefined ) { + const colors = geometry.getAttribute( 'color' ); - const array = createVertexColorSRGBArray( colors ); + if ( colors !== undefined ) { - builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); + const array = createVertexColorSRGBArray( colors ); - } + builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array ); } - } else { + } - throw new Error( 'DRACOExporter: Unsupported object type.' ); + } else { - } + throw new Error( 'DRACOExporter: Unsupported object type.' ); - //Compress using draco encoder + } - const encodedData = new dracoEncoder.DracoInt8Array(); + //Compress using draco encoder - //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). + const encodedData = new dracoEncoder.DracoInt8Array(); - const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; - const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; + //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). - encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); + const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; + const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; - // Sets the desired encoding method for a given geometry. + encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); - if ( options.encoderMethod !== undefined ) { + // Sets the desired encoding method for a given geometry. - encoder.SetEncodingMethod( options.encoderMethod ); + if ( options.encoderMethod !== undefined ) { - } + encoder.SetEncodingMethod( options.encoderMethod ); - // Sets the quantization (number of bits used to represent) compression options for a named attribute. - // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. - if ( options.quantization !== undefined ) { + } - for ( let i = 0; i < 5; i ++ ) { + // Sets the quantization (number of bits used to represent) compression options for a named attribute. + // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. + if ( options.quantization !== undefined ) { - if ( options.quantization[ i ] !== undefined ) { + for ( let i = 0; i < 5; i ++ ) { - encoder.SetAttributeQuantization( i, options.quantization[ i ] ); + if ( options.quantization[ i ] !== undefined ) { - } + encoder.SetAttributeQuantization( i, options.quantization[ i ] ); } } - let length; + } - if ( object.isMesh === true ) { + let length; - length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); + if ( object.isMesh === true ) { - } else { + length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); - length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); + } else { - } + length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); - dracoEncoder.destroy( dracoObject ); + } - if ( length === 0 ) { + dracoEncoder.destroy( dracoObject ); - throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' ); + if ( length === 0 ) { - } + throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' ); - //Copy encoded data to buffer. - const outputData = new Int8Array( new ArrayBuffer( length ) ); + } - for ( let i = 0; i < length; i ++ ) { + //Copy encoded data to buffer. + const outputData = new Int8Array( new ArrayBuffer( length ) ); - outputData[ i ] = encodedData.GetValue( i ); + for ( let i = 0; i < length; i ++ ) { - } + outputData[ i ] = encodedData.GetValue( i ); - dracoEncoder.destroy( encodedData ); - dracoEncoder.destroy( encoder ); - dracoEncoder.destroy( builder ); + } - return outputData; + dracoEncoder.destroy( encodedData ); + dracoEncoder.destroy( encoder ); + dracoEncoder.destroy( builder ); - } + return outputData; } - function createVertexColorSRGBArray( attribute ) { +} - // While .drc files do not specify colorspace, the only 'official' tooling - // is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected - // for .drc files, but note that Draco buffers embedded in glTF files will - // be Linear-sRGB instead. +function createVertexColorSRGBArray( attribute ) { - const _color = new Color(); + // While .drc files do not specify colorspace, the only 'official' tooling + // is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected + // for .drc files, but note that Draco buffers embedded in glTF files will + // be Linear-sRGB instead. - const count = attribute.count; - const itemSize = attribute.itemSize; - const array = new Float32Array( count * itemSize ); + const _color = new Color(); - for ( let i = 0, il = count; i < il; i ++ ) { + const count = attribute.count; + const itemSize = attribute.itemSize; + const array = new Float32Array( count * itemSize ); - _color.fromBufferAttribute( attribute, i ).convertLinearToSRGB(); + for ( let i = 0, il = count; i < il; i ++ ) { - array[ i * itemSize ] = _color.r; - array[ i * itemSize + 1 ] = _color.g; - array[ i * itemSize + 2 ] = _color.b; + _color.fromBufferAttribute( attribute, i ).convertLinearToSRGB(); - if ( itemSize === 4 ) { + array[ i * itemSize ] = _color.r; + array[ i * itemSize + 1 ] = _color.g; + array[ i * itemSize + 2 ] = _color.b; - array[ i * itemSize + 3 ] = attribute.getW( i ); + if ( itemSize === 4 ) { - } + array[ i * itemSize + 3 ] = attribute.getW( i ); } - return array; - } - // Encoder methods + return array; + +} + +// Encoder methods - DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; - DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; +DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; +DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; - // Geometry type +// Geometry type - DRACOExporter.POINT_CLOUD = 0; - DRACOExporter.TRIANGULAR_MESH = 1; +DRACOExporter.POINT_CLOUD = 0; +DRACOExporter.TRIANGULAR_MESH = 1; - // Attribute type +// Attribute type - DRACOExporter.INVALID = - 1; - DRACOExporter.POSITION = 0; - DRACOExporter.NORMAL = 1; - DRACOExporter.COLOR = 2; - DRACOExporter.TEX_COORD = 3; - DRACOExporter.GENERIC = 4; +DRACOExporter.INVALID = - 1; +DRACOExporter.POSITION = 0; +DRACOExporter.NORMAL = 1; +DRACOExporter.COLOR = 2; +DRACOExporter.TEX_COORD = 3; +DRACOExporter.GENERIC = 4; - return DRACOExporter; +return DRACOExporter; } )(); diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 7a5efe0807905e..904fe51c6ed2aa 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -29,3208 +29,3207 @@ import { decompress } from './../utils/TextureUtils.js'; const GLTFExporter = /* @__PURE__ */ ( () => { +/** + * The KHR_mesh_quantization extension allows these extra attribute component types + * + * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes + */ +const KHR_mesh_quantization_ExtraAttrTypes = { + POSITION: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'unsigned byte normalized', + 'short', + 'short normalized', + 'unsigned short', + 'unsigned short normalized', + ], + NORMAL: [ + 'byte normalized', + 'short normalized', + ], + TANGENT: [ + 'byte normalized', + 'short normalized', + ], + TEXCOORD: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'short', + 'short normalized', + 'unsigned short', + ], +}; + + +class GLTFExporter { + + constructor() { + + this.pluginCallbacks = []; + + this.register( function ( writer ) { + + return new GLTFLightExtension( writer ); - /** - * The KHR_mesh_quantization extension allows these extra attribute component types - * - * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes - */ - const KHR_mesh_quantization_ExtraAttrTypes = { - POSITION: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'unsigned byte normalized', - 'short', - 'short normalized', - 'unsigned short', - 'unsigned short normalized', - ], - NORMAL: [ - 'byte normalized', - 'short normalized', - ], - TANGENT: [ - 'byte normalized', - 'short normalized', - ], - TEXCOORD: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'short', - 'short normalized', - 'unsigned short', - ], - }; - + } ); - class GLTFExporter { + this.register( function ( writer ) { - constructor() { + return new GLTFMaterialsUnlitExtension( writer ); - this.pluginCallbacks = []; + } ); - this.register( function ( writer ) { + this.register( function ( writer ) { - return new GLTFLightExtension( writer ); + return new GLTFMaterialsTransmissionExtension( writer ); - } ); + } ); - this.register( function ( writer ) { + this.register( function ( writer ) { - return new GLTFMaterialsUnlitExtension( writer ); + return new GLTFMaterialsVolumeExtension( writer ); - } ); + } ); - this.register( function ( writer ) { + this.register( function ( writer ) { - return new GLTFMaterialsTransmissionExtension( writer ); + return new GLTFMaterialsIorExtension( writer ); - } ); - - this.register( function ( writer ) { + } ); - return new GLTFMaterialsVolumeExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsSpecularExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsIorExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsClearcoatExtension( writer ); - this.register( function ( writer ) { + } ); - return new GLTFMaterialsSpecularExtension( writer ); + this.register( function ( writer ) { - } ); + return new GLTFMaterialsIridescenceExtension( writer ); - this.register( function ( writer ) { - - return new GLTFMaterialsClearcoatExtension( writer ); + } ); - } ); + this.register( function ( writer ) { - this.register( function ( writer ) { + return new GLTFMaterialsSheenExtension( writer ); - return new GLTFMaterialsIridescenceExtension( writer ); + } ); - } ); + this.register( function ( writer ) { - this.register( function ( writer ) { + return new GLTFMaterialsAnisotropyExtension( writer ); - return new GLTFMaterialsSheenExtension( writer ); + } ); - } ); + this.register( function ( writer ) { - this.register( function ( writer ) { + return new GLTFMaterialsEmissiveStrengthExtension( writer ); - return new GLTFMaterialsAnisotropyExtension( writer ); + } ); - } ); + this.register( function ( writer ) { - this.register( function ( writer ) { + return new GLTFMeshGpuInstancing( writer ); - return new GLTFMaterialsEmissiveStrengthExtension( writer ); + } ); - } ); + } - this.register( function ( writer ) { + register( callback ) { - return new GLTFMeshGpuInstancing( writer ); + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - } ); + this.pluginCallbacks.push( callback ); } - register( callback ) { + return this; - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + } - this.pluginCallbacks.push( callback ); + unregister( callback ) { - } + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - return this; + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); } - unregister( callback ) { + return this; + + } - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Function} onError Callback on errors + * @param {Object} options options + */ + parse( input, onDone, onError, options ) { - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + const writer = new GLTFWriter(); + const plugins = []; - } + for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { - return this; + plugins.push( this.pluginCallbacks[ i ]( writer ) ); } - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Function} onError Callback on errors - * @param {Object} options options - */ - parse( input, onDone, onError, options ) { + writer.setPlugins( plugins ); + writer.write( input, onDone, options ).catch( onError ); - const writer = new GLTFWriter(); - const plugins = []; + } - for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + parseAsync( input, options ) { - plugins.push( this.pluginCallbacks[ i ]( writer ) ); + const scope = this; - } + return new Promise( function ( resolve, reject ) { - writer.setPlugins( plugins ); - writer.write( input, onDone, options ).catch( onError ); + scope.parse( input, resolve, reject, options ); - } + } ); - parseAsync( input, options ) { + } - const scope = this; +} - return new Promise( function ( resolve, reject ) { +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ - scope.parse( input, resolve, reject, options ); +const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + + BYTE: 0x1400, + UNSIGNED_BYTE: 0x1401, + SHORT: 0x1402, + UNSIGNED_SHORT: 0x1403, + INT: 0x1404, + UNSIGNED_INT: 0x1405, + FLOAT: 0x1406, + + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, - } ); + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497 +}; - } +const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; - } +const THREE_TO_WEBGL = {}; - //------------------------------------------------------------------------------ - // Constants - //------------------------------------------------------------------------------ - - const WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - - BYTE: 0x1400, - UNSIGNED_BYTE: 0x1401, - SHORT: 0x1402, - UNSIGNED_SHORT: 0x1403, - INT: 0x1404, - UNSIGNED_INT: 0x1405, - FLOAT: 0x1406, - - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, - - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 - }; +THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; +THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; +THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; +THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; +THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; +THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; - const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; +THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; +THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; +THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; - const THREE_TO_WEBGL = {}; +const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights' +}; - THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; - THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; - THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; - THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; - THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; - THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; +const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color(); - THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; - THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; - THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; +// GLB constants +// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - const PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' - }; +const GLB_HEADER_BYTES = 12; +const GLB_HEADER_MAGIC = 0x46546C67; +const GLB_VERSION = 2; - const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color(); +const GLB_CHUNK_PREFIX_BYTES = 8; +const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; +const GLB_CHUNK_TYPE_BIN = 0x004E4942; - // GLB constants - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification +//------------------------------------------------------------------------------ +// Utility functions +//------------------------------------------------------------------------------ - const GLB_HEADER_BYTES = 12; - const GLB_HEADER_MAGIC = 0x46546C67; - const GLB_VERSION = 2; +/** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ +function equalArray( array1, array2 ) { - const GLB_CHUNK_PREFIX_BYTES = 8; - const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; - const GLB_CHUNK_TYPE_BIN = 0x004E4942; + return ( array1.length === array2.length ) && array1.every( function ( element, index ) { - //------------------------------------------------------------------------------ - // Utility functions - //------------------------------------------------------------------------------ + return element === array2[ index ]; - /** - * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal - */ - function equalArray( array1, array2 ) { + } ); - return ( array1.length === array2.length ) && array1.every( function ( element, index ) { +} - return element === array2[ index ]; +/** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ +function stringToArrayBuffer( text ) { - } ); + return new TextEncoder().encode( text ).buffer; + +} + +/** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ +function isIdentityMatrix( matrix ) { - } + return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - /** - * Converts a string to an ArrayBuffer. - * @param {string} text - * @return {ArrayBuffer} - */ - function stringToArrayBuffer( text ) { +} - return new TextEncoder().encode( text ).buffer; +/** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ +function getMinMax( attribute, start, count ) { - } + const output = { - /** - * Is identity matrix - * - * @param {Matrix4} matrix - * @returns {Boolean} Returns true, if parameter is identity matrix - */ - function isIdentityMatrix( matrix ) { + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - - } + }; - /** - * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ - function getMinMax( attribute, start, count ) { + for ( let i = start; i < start + count; i ++ ) { - const output = { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + let value; - }; + if ( attribute.itemSize > 4 ) { - for ( let i = start; i < start + count; i ++ ) { + // no support for interleaved data for itemSize > 4 - for ( let a = 0; a < attribute.itemSize; a ++ ) { + value = attribute.array[ i * attribute.itemSize + a ]; - let value; + } else { - if ( attribute.itemSize > 4 ) { + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); - // no support for interleaved data for itemSize > 4 + if ( attribute.normalized === true ) { - value = attribute.array[ i * attribute.itemSize + a ]; + value = MathUtils.normalize( value, attribute.array ); - } else { + } - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + } - if ( attribute.normalized === true ) { + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); - value = MathUtils.normalize( value, attribute.array ); + } - } + } - } + return output; - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); +} - } +/** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ +function getPaddedBufferSize( bufferSize ) { - } + return Math.ceil( bufferSize / 4 ) * 4; - return output; +} - } +/** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ +function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { - /** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. - * - */ - function getPaddedBufferSize( bufferSize ) { + const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - return Math.ceil( bufferSize / 4 ) * 4; + if ( paddedLength !== arrayBuffer.byteLength ) { - } + const array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); - /** - * Returns a buffer aligned to 4-byte boundary. - * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ - function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { + if ( paddingByte !== 0 ) { - const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); + for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - if ( paddedLength !== arrayBuffer.byteLength ) { + array[ i ] = paddingByte; - const array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); + } - if ( paddingByte !== 0 ) { + } - for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + return array.buffer; - array[ i ] = paddingByte; + } - } + return arrayBuffer; - } +} - return array.buffer; +function getCanvas() { - } + if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { - return arrayBuffer; + return new OffscreenCanvas( 1, 1 ); } - function getCanvas() { + return document.createElement( 'canvas' ); - if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { +} - return new OffscreenCanvas( 1, 1 ); +function getToBlobPromise( canvas, mimeType ) { - } + if ( canvas.toBlob !== undefined ) { - return document.createElement( 'canvas' ); + return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); } - function getToBlobPromise( canvas, mimeType ) { + let quality; - if ( canvas.toBlob !== undefined ) { + // Blink's implementation of convertToBlob seems to default to a quality level of 100% + // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. + if ( mimeType === 'image/jpeg' ) { - return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); + quality = 0.92; - } + } else if ( mimeType === 'image/webp' ) { + + quality = 0.8; - let quality; + } - // Blink's implementation of convertToBlob seems to default to a quality level of 100% - // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. - if ( mimeType === 'image/jpeg' ) { + return canvas.convertToBlob( { - quality = 0.92; + type: mimeType, + quality: quality - } else if ( mimeType === 'image/webp' ) { + } ); - quality = 0.8; +} - } +/** + * Writer + */ +class GLTFWriter { - return canvas.convertToBlob( { + constructor() { - type: mimeType, - quality: quality + this.plugins = []; - } ); + this.options = {}; + this.pending = []; + this.buffers = []; - } + this.byteOffset = 0; + this.buffers = []; + this.nodeMap = new Map(); + this.skins = []; - /** - * Writer - */ - class GLTFWriter { + this.extensionsUsed = {}; + this.extensionsRequired = {}; - constructor() { + this.uids = new Map(); + this.uid = 0; - this.plugins = []; + this.json = { + asset: { + version: '2.0', + generator: 'THREE.GLTFExporter' + } + }; - this.options = {}; - this.pending = []; - this.buffers = []; + this.cache = { + meshes: new Map(), + attributes: new Map(), + attributesNormalized: new Map(), + materials: new Map(), + textures: new Map(), + images: new Map() + }; - this.byteOffset = 0; - this.buffers = []; - this.nodeMap = new Map(); - this.skins = []; + } - this.extensionsUsed = {}; - this.extensionsRequired = {}; + setPlugins( plugins ) { - this.uids = new Map(); - this.uid = 0; + this.plugins = plugins; - this.json = { - asset: { - version: '2.0', - generator: 'THREE.GLTFExporter' - } - }; + } - this.cache = { - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map() - }; + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + async write( input, onDone, options = {} ) { - } + this.options = Object.assign( { + // default options + binary: false, + trs: false, + onlyVisible: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false + }, options ); - setPlugins( plugins ) { + if ( this.options.animations.length > 0 ) { - this.plugins = plugins; + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true; } - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - async write( input, onDone, options = {} ) { + this.processInput( input ); - this.options = Object.assign( { - // default options - binary: false, - trs: false, - onlyVisible: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false - }, options ); + await Promise.all( this.pending ); - if ( this.options.animations.length > 0 ) { + const writer = this; + const buffers = writer.buffers; + const json = writer.json; + options = writer.options; - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; + const extensionsUsed = writer.extensionsUsed; + const extensionsRequired = writer.extensionsRequired; - } + // Merge buffers. + const blob = new Blob( buffers, { type: 'application/octet-stream' } ); - this.processInput( input ); + // Declare extensions. + const extensionsUsedList = Object.keys( extensionsUsed ); + const extensionsRequiredList = Object.keys( extensionsRequired ); - await Promise.all( this.pending ); + if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; + if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; - const writer = this; - const buffers = writer.buffers; - const json = writer.json; - options = writer.options; + // Update bytelength of the single buffer. + if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; - const extensionsUsed = writer.extensionsUsed; - const extensionsRequired = writer.extensionsRequired; + if ( options.binary === true ) { - // Merge buffers. - const blob = new Blob( buffers, { type: 'application/octet-stream' } ); + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - // Declare extensions. - const extensionsUsedList = Object.keys( extensionsUsed ); - const extensionsRequiredList = Object.keys( extensionsRequired ); + const reader = new FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; - if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer( reader.result ); + const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); + binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); - // Update bytelength of the single buffer. - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + // JSON chunk. + const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); + const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); + jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); - if ( options.binary === true ) { + // GLB header. + const header = new ArrayBuffer( GLB_HEADER_BYTES ); + const headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + const totalByteLength = GLB_HEADER_BYTES + + jsonChunkPrefix.byteLength + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + headerView.setUint32( 8, totalByteLength, true ); - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + const glbBlob = new Blob( [ + header, + jsonChunkPrefix, + jsonChunk, + binaryChunkPrefix, + binaryChunk + ], { type: 'application/octet-stream' } ); - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + const glbReader = new FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { - // Binary chunk. - const binaryChunk = getPaddedArrayBuffer( reader.result ); - const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); - - // JSON chunk. - const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); - - // GLB header. - const header = new ArrayBuffer( GLB_HEADER_BYTES ); - const headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - const totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); - - const glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); - - const glbReader = new FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { - - onDone( glbReader.result ); - - }; + onDone( glbReader.result ); }; - } else { + }; - if ( json.buffers && json.buffers.length > 0 ) { + } else { - const reader = new FileReader(); - reader.readAsDataURL( blob ); - reader.onloadend = function () { + if ( json.buffers && json.buffers.length > 0 ) { - const base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); + const reader = new FileReader(); + reader.readAsDataURL( blob ); + reader.onloadend = function () { - }; + const base64data = reader.result; + json.buffers[ 0 ].uri = base64data; + onDone( json ); - } else { + }; - onDone( json ); + } else { - } + onDone( json ); } - } - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material} object - * @param {Object} objectDef - */ - serializeUserData( object, objectDef ) { - if ( Object.keys( object.userData ).length === 0 ) return; + } - const options = this.options; - const extensionsUsed = this.extensionsUsed; + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ + serializeUserData( object, objectDef ) { - try { + if ( Object.keys( object.userData ).length === 0 ) return; - const json = JSON.parse( JSON.stringify( object.userData ) ); + const options = this.options; + const extensionsUsed = this.extensionsUsed; - if ( options.includeCustomExtensions && json.gltfExtensions ) { + try { - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; + const json = JSON.parse( JSON.stringify( object.userData ) ); - for ( const extensionName in json.gltfExtensions ) { + if ( options.includeCustomExtensions && json.gltfExtensions ) { - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; + if ( objectDef.extensions === undefined ) objectDef.extensions = {}; - } + for ( const extensionName in json.gltfExtensions ) { - delete json.gltfExtensions; + objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; + extensionsUsed[ extensionName ] = true; } - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; - - } catch ( error ) { - - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + delete json.gltfExtensions; } - } + if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + + } catch ( error ) { - /** - * Returns ids for buffer attributes. - * @param {Object} object - * @return {Integer} - */ - getUID( attribute, isRelativeCopy = false ) { + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); - if ( this.uids.has( attribute ) === false ) { + } - const uids = new Map(); + } - uids.set( true, this.uid ++ ); - uids.set( false, this.uid ++ ); + /** + * Returns ids for buffer attributes. + * @param {Object} object + * @return {Integer} + */ + getUID( attribute, isRelativeCopy = false ) { - this.uids.set( attribute, uids ); + if ( this.uids.has( attribute ) === false ) { - } + const uids = new Map(); - const uids = this.uids.get( attribute ); + uids.set( true, this.uid ++ ); + uids.set( false, this.uid ++ ); - return uids.get( isRelativeCopy ); + this.uids.set( attribute, uids ); } - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - */ - isNormalizedNormalAttribute( normal ) { + const uids = this.uids.get( attribute ); + + return uids.get( isRelativeCopy ); - const cache = this.cache; + } - if ( cache.attributesNormalized.has( normal ) ) return false; + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ + isNormalizedNormalAttribute( normal ) { - const v = new Vector3(); + const cache = this.cache; - for ( let i = 0, il = normal.count; i < il; i ++ ) { + if ( cache.attributesNormalized.has( normal ) ) return false; - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; + const v = new Vector3(); - } + for ( let i = 0, il = normal.count; i < il; i ++ ) { - return true; + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; } - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute( normal ) { + return true; - const cache = this.cache; + } - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + createNormalizedNormalAttribute( normal ) { - const attribute = normal.clone(); - const v = new Vector3(); + const cache = this.cache; - for ( let i = 0, il = attribute.count; i < il; i ++ ) { + if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); - v.fromBufferAttribute( attribute, i ); + const attribute = normal.clone(); + const v = new Vector3(); - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + for ( let i = 0, il = attribute.count; i < il; i ++ ) { - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); + v.fromBufferAttribute( attribute, i ); - } else { + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - v.normalize(); + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); - } + } else { - attribute.setXYZ( i, v.x, v.y, v.z ); + v.normalize(); } - cache.attributesNormalized.set( normal, attribute ); - - return attribute; + attribute.setXYZ( i, v.x, v.y, v.z ); } - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform( mapDef, texture ) { - - let didTransform = false; - const transformDef = {}; + cache.attributesNormalized.set( normal, attribute ); - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + return attribute; - transformDef.offset = texture.offset.toArray(); - didTransform = true; + } - } + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ + applyTextureTransform( mapDef, texture ) { - if ( texture.rotation !== 0 ) { + let didTransform = false; + const transformDef = {}; - transformDef.rotation = texture.rotation; - didTransform = true; + if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - } + transformDef.offset = texture.offset.toArray(); + didTransform = true; - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + } - transformDef.scale = texture.repeat.toArray(); - didTransform = true; + if ( texture.rotation !== 0 ) { - } + transformDef.rotation = texture.rotation; + didTransform = true; - if ( didTransform ) { + } - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; + if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { - } + transformDef.scale = texture.repeat.toArray(); + didTransform = true; } - buildMetalRoughTexture( metalnessMap, roughnessMap ) { + if ( didTransform ) { - if ( metalnessMap === roughnessMap ) return metalnessMap; + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + this.extensionsUsed[ 'KHR_texture_transform' ] = true; - function getEncodingConversion( map ) { + } - if ( map.colorSpace === SRGBColorSpace ) { + } - return function SRGBToLinear( c ) { + buildMetalRoughTexture( metalnessMap, roughnessMap ) { - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + if ( metalnessMap === roughnessMap ) return metalnessMap; - }; + function getEncodingConversion( map ) { - } + if ( map.colorSpace === SRGBColorSpace ) { - return function LinearToLinear( c ) { + return function SRGBToLinear( c ) { - return c; + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); }; } - console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); - - if ( metalnessMap instanceof CompressedTexture ) { - - metalnessMap = decompress( metalnessMap ); - - } - - if ( roughnessMap instanceof CompressedTexture ) { + return function LinearToLinear( c ) { - roughnessMap = decompress( roughnessMap ); + return c; - } + }; - const metalness = metalnessMap ? metalnessMap.image : null; - const roughness = roughnessMap ? roughnessMap.image : null; + } - const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); - const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); + console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); - const canvas = getCanvas(); - canvas.width = width; - canvas.height = height; + if ( metalnessMap instanceof CompressedTexture ) { - const context = canvas.getContext( '2d' ); - context.fillStyle = '#00ffff'; - context.fillRect( 0, 0, width, height ); + metalnessMap = decompress( metalnessMap ); - const composite = context.getImageData( 0, 0, width, height ); + } - if ( metalness ) { + if ( roughnessMap instanceof CompressedTexture ) { - context.drawImage( metalness, 0, 0, width, height ); + roughnessMap = decompress( roughnessMap ); - const convert = getEncodingConversion( metalnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; + } - for ( let i = 2; i < data.length; i += 4 ) { + const metalness = metalnessMap ? metalnessMap.image : null; + const roughness = roughnessMap ? roughnessMap.image : null; - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); + const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); - } + const canvas = getCanvas(); + canvas.width = width; + canvas.height = height; - } + const context = canvas.getContext( '2d' ); + context.fillStyle = '#00ffff'; + context.fillRect( 0, 0, width, height ); - if ( roughness ) { + const composite = context.getImageData( 0, 0, width, height ); - context.drawImage( roughness, 0, 0, width, height ); + if ( metalness ) { - const convert = getEncodingConversion( roughnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; + context.drawImage( metalness, 0, 0, width, height ); - for ( let i = 1; i < data.length; i += 4 ) { + const convert = getEncodingConversion( metalnessMap ); + const data = context.getImageData( 0, 0, width, height ).data; - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + for ( let i = 2; i < data.length; i += 4 ) { - } + composite.data[ i ] = convert( data[ i ] / 256 ) * 256; } - context.putImageData( composite, 0, 0 ); - - // + } - const reference = metalnessMap || roughnessMap; + if ( roughness ) { - const texture = reference.clone(); + context.drawImage( roughness, 0, 0, width, height ); - texture.source = new Source( canvas ); - texture.colorSpace = NoColorSpace; - texture.channel = ( metalnessMap || roughnessMap ).channel; + const convert = getEncodingConversion( roughnessMap ); + const data = context.getImageData( 0, 0, width, height ).data; - if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { + for ( let i = 1; i < data.length; i += 4 ) { - console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); + composite.data[ i ] = convert( data[ i ] / 256 ) * 256; } - return texture; - } - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - processBuffer( buffer ) { + context.putImageData( composite, 0, 0 ); + + // + + const reference = metalnessMap || roughnessMap; - const json = this.json; - const buffers = this.buffers; + const texture = reference.clone(); - if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; + texture.source = new Source( canvas ); + texture.colorSpace = NoColorSpace; + texture.channel = ( metalnessMap || roughnessMap ).channel; - // All buffers are merged before export. - buffers.push( buffer ); + if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { - return 0; + console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); } - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - processBufferView( attribute, componentType, start, count, target ) { + return texture; - const json = this.json; + } - if ( ! json.bufferViews ) json.bufferViews = []; + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + processBuffer( buffer ) { - // Create a new dataview and dump the attribute's array into it + const json = this.json; + const buffers = this.buffers; - let componentSize; + if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; - switch ( componentType ) { + // All buffers are merged before export. + buffers.push( buffer ); - case WEBGL_CONSTANTS.BYTE: - case WEBGL_CONSTANTS.UNSIGNED_BYTE: + return 0; - componentSize = 1; + } - break; + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + processBufferView( attribute, componentType, start, count, target ) { - case WEBGL_CONSTANTS.SHORT: - case WEBGL_CONSTANTS.UNSIGNED_SHORT: + const json = this.json; - componentSize = 2; + if ( ! json.bufferViews ) json.bufferViews = []; - break; + // Create a new dataview and dump the attribute's array into it - default: + let componentSize; - componentSize = 4; + switch ( componentType ) { - } + case WEBGL_CONSTANTS.BYTE: + case WEBGL_CONSTANTS.UNSIGNED_BYTE: - const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); - const dataView = new DataView( new ArrayBuffer( byteLength ) ); - let offset = 0; + componentSize = 1; - for ( let i = start; i < start + count; i ++ ) { + break; - for ( let a = 0; a < attribute.itemSize; a ++ ) { + case WEBGL_CONSTANTS.SHORT: + case WEBGL_CONSTANTS.UNSIGNED_SHORT: - let value; + componentSize = 2; - if ( attribute.itemSize > 4 ) { + break; - // no support for interleaved data for itemSize > 4 + default: - value = attribute.array[ i * attribute.itemSize + a ]; + componentSize = 4; - } else { + } - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + const dataView = new DataView( new ArrayBuffer( byteLength ) ); + let offset = 0; - if ( attribute.normalized === true ) { + for ( let i = start; i < start + count; i ++ ) { - value = MathUtils.normalize( value, attribute.array ); + for ( let a = 0; a < attribute.itemSize; a ++ ) { - } + let value; - } + if ( attribute.itemSize > 4 ) { - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + // no support for interleaved data for itemSize > 4 - dataView.setFloat32( offset, value, true ); + value = attribute.array[ i * attribute.itemSize + a ]; - } else if ( componentType === WEBGL_CONSTANTS.INT ) { + } else { - dataView.setInt32( offset, value, true ); + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + if ( attribute.normalized === true ) { - dataView.setUint32( offset, value, true ); + value = MathUtils.normalize( value, attribute.array ); - } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { + } - dataView.setInt16( offset, value, true ); + } - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - dataView.setUint16( offset, value, true ); + dataView.setFloat32( offset, value, true ); - } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { + } else if ( componentType === WEBGL_CONSTANTS.INT ) { - dataView.setInt8( offset, value ); + dataView.setInt32( offset, value, true ); - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - dataView.setUint8( offset, value ); + dataView.setUint32( offset, value, true ); - } + } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { - offset += componentSize; + dataView.setInt16( offset, value, true ); - } + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - } + dataView.setUint16( offset, value, true ); - const bufferViewDef = { + } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength + dataView.setInt8( offset, value ); - }; + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - if ( target !== undefined ) bufferViewDef.target = target; + dataView.setUint8( offset, value ); - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + } - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize; + offset += componentSize; } - this.byteOffset += byteLength; + } - json.bufferViews.push( bufferViewDef ); + const bufferViewDef = { - // @TODO Merge bufferViews where possible. - const output = { + buffer: this.processBuffer( dataView.buffer ), + byteOffset: this.byteOffset, + byteLength: byteLength - id: json.bufferViews.length - 1, - byteLength: 0 + }; - }; + if ( target !== undefined ) bufferViewDef.target = target; - return output; + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize; } - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - processBufferViewImage( blob ) { + this.byteOffset += byteLength; - const writer = this; - const json = writer.json; + json.bufferViews.push( bufferViewDef ); - if ( ! json.bufferViews ) json.bufferViews = []; + // @TODO Merge bufferViews where possible. + const output = { - return new Promise( function ( resolve ) { + id: json.bufferViews.length - 1, + byteLength: 0 - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + }; - const buffer = getPaddedArrayBuffer( reader.result ); + return output; - const bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength - }; + } - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + processBufferViewImage( blob ) { - }; + const writer = this; + const json = writer.json; - } ); + if ( ! json.bufferViews ) json.bufferViews = []; - } + return new Promise( function ( resolve ) { - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array - */ - processAccessor( attribute, geometry, start, count ) { + const reader = new FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - const json = this.json; + const buffer = getPaddedArrayBuffer( reader.result ); - const types = { + const bufferViewDef = { + buffer: writer.processBuffer( buffer ), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength + }; - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 9: 'MAT3', - 16: 'MAT4' + writer.byteOffset += buffer.byteLength; + resolve( json.bufferViews.push( bufferViewDef ) - 1 ); }; - let componentType; + } ); - // Detect the component type of the attribute array - if ( attribute.array.constructor === Float32Array ) { + } - componentType = WEBGL_CONSTANTS.FLOAT; + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ + processAccessor( attribute, geometry, start, count ) { - } else if ( attribute.array.constructor === Int32Array ) { + const json = this.json; - componentType = WEBGL_CONSTANTS.INT; + const types = { - } else if ( attribute.array.constructor === Uint32Array ) { + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 9: 'MAT3', + 16: 'MAT4' - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + }; - } else if ( attribute.array.constructor === Int16Array ) { + let componentType; - componentType = WEBGL_CONSTANTS.SHORT; + // Detect the component type of the attribute array + if ( attribute.array.constructor === Float32Array ) { - } else if ( attribute.array.constructor === Uint16Array ) { + componentType = WEBGL_CONSTANTS.FLOAT; - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + } else if ( attribute.array.constructor === Int32Array ) { - } else if ( attribute.array.constructor === Int8Array ) { + componentType = WEBGL_CONSTANTS.INT; - componentType = WEBGL_CONSTANTS.BYTE; + } else if ( attribute.array.constructor === Uint32Array ) { - } else if ( attribute.array.constructor === Uint8Array ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + } else if ( attribute.array.constructor === Int16Array ) { - } else { + componentType = WEBGL_CONSTANTS.SHORT; - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); + } else if ( attribute.array.constructor === Uint16Array ) { - } + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - if ( start === undefined ) start = 0; - if ( count === undefined || count === Infinity ) count = attribute.count; + } else if ( attribute.array.constructor === Int8Array ) { - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) return null; + componentType = WEBGL_CONSTANTS.BYTE; - const minMax = getMinMax( attribute, start, count ); - let bufferViewTarget; + } else if ( attribute.array.constructor === Uint8Array ) { - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if ( geometry !== undefined ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + } else { - } + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); - const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + } - const accessorDef = { + if ( start === undefined ) start = 0; + if ( count === undefined || count === Infinity ) count = attribute.count; - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] + // Skip creating an accessor if the attribute doesn't have data to export + if ( count === 0 ) return null; - }; + const minMax = getMinMax( attribute, start, count ); + let bufferViewTarget; - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; + // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. + if ( geometry !== undefined ) { - return json.accessors.push( accessorDef ) - 1; + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; } - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (RGBAFormat) - * @param {Boolean} flipY before writing out the image - * @param {String} mimeType export format - * @return {Integer} Index of the processed texture in the "images" array - */ - processImage( image, format, flipY, mimeType = 'image/png' ) { + const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + + const accessorDef = { - if ( image !== null ) { + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] - const writer = this; - const cache = writer.cache; - const json = writer.json; - const options = writer.options; - const pending = writer.pending; + }; - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); + if ( attribute.normalized === true ) accessorDef.normalized = true; + if ( ! json.accessors ) json.accessors = []; - const cachedImages = cache.images.get( image ); + return json.accessors.push( accessorDef ) - 1; - const key = mimeType + ':flipY/' + flipY.toString(); + } - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (RGBAFormat) + * @param {Boolean} flipY before writing out the image + * @param {String} mimeType export format + * @return {Integer} Index of the processed texture in the "images" array + */ + processImage( image, format, flipY, mimeType = 'image/png' ) { - if ( ! json.images ) json.images = []; + if ( image !== null ) { - const imageDef = { mimeType: mimeType }; + const writer = this; + const cache = writer.cache; + const json = writer.json; + const options = writer.options; + const pending = writer.pending; - const canvas = getCanvas(); + if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); + const cachedImages = cache.images.get( image ); - const ctx = canvas.getContext( '2d' ); + const key = mimeType + ':flipY/' + flipY.toString(); - if ( flipY === true ) { + if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); + if ( ! json.images ) json.images = []; - } + const imageDef = { mimeType: mimeType }; - if ( image.data !== undefined ) { // THREE.DataTexture + const canvas = getCanvas(); - if ( format !== RGBAFormat ) { + canvas.width = Math.min( image.width, options.maxTextureSize ); + canvas.height = Math.min( image.height, options.maxTextureSize ); - console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); + const ctx = canvas.getContext( '2d' ); - } + if ( flipY === true ) { - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + } - } + if ( image.data !== undefined ) { // THREE.DataTexture - const data = new Uint8ClampedArray( image.height * image.width * 4 ); + if ( format !== RGBAFormat ) { - for ( let i = 0; i < data.length; i += 4 ) { + console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); - data[ i + 0 ] = image.data[ i + 0 ]; - data[ i + 1 ] = image.data[ i + 1 ]; - data[ i + 2 ] = image.data[ i + 2 ]; - data[ i + 3 ] = image.data[ i + 3 ]; + } - } + if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); + console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); - } else { + } + + const data = new Uint8ClampedArray( image.height * image.width * 4 ); - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + for ( let i = 0; i < data.length; i += 4 ) { + + data[ i + 0 ] = image.data[ i + 0 ]; + data[ i + 1 ] = image.data[ i + 1 ]; + data[ i + 2 ] = image.data[ i + 2 ]; + data[ i + 3 ] = image.data[ i + 3 ]; } - if ( options.binary === true ) { + ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - pending.push( + } else { - getToBlobPromise( canvas, mimeType ) - .then( blob => writer.processBufferViewImage( blob ) ) - .then( bufferViewIndex => { + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - imageDef.bufferView = bufferViewIndex; + } - } ) + if ( options.binary === true ) { - ); + pending.push( - } else { + getToBlobPromise( canvas, mimeType ) + .then( blob => writer.processBufferViewImage( blob ) ) + .then( bufferViewIndex => { - if ( canvas.toDataURL !== undefined ) { + imageDef.bufferView = bufferViewIndex; - imageDef.uri = canvas.toDataURL( mimeType ); + } ) - } else { + ); - pending.push( + } else { - getToBlobPromise( canvas, mimeType ) - .then( blob => new FileReader().readAsDataURL( blob ) ) - .then( dataURL => { + if ( canvas.toDataURL !== undefined ) { - imageDef.uri = dataURL; + imageDef.uri = canvas.toDataURL( mimeType ); - } ) + } else { - ); + pending.push( - } + getToBlobPromise( canvas, mimeType ) + .then( blob => new FileReader().readAsDataURL( blob ) ) + .then( dataURL => { - } + imageDef.uri = dataURL; - const index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; + } ) - } else { + ); - throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); + } } - } + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - processSampler( map ) { + } else { - const json = this.json; + throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); - if ( ! json.samplers ) json.samplers = []; + } - const samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; + } - return json.samplers.push( samplerDef ) - 1; + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + processSampler( map ) { - } + const json = this.json; - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - processTexture( map ) { + if ( ! json.samplers ) json.samplers = []; - const writer = this; - const options = writer.options; - const cache = this.cache; - const json = this.json; + const samplerDef = { + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] + }; - if ( cache.textures.has( map ) ) return cache.textures.get( map ); + return json.samplers.push( samplerDef ) - 1; - if ( ! json.textures ) json.textures = []; + } - // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture - if ( map instanceof CompressedTexture ) { + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + processTexture( map ) { - map = decompress( map, options.maxTextureSize ); + const writer = this; + const options = writer.options; + const cache = this.cache; + const json = this.json; - } + if ( cache.textures.has( map ) ) return cache.textures.get( map ); - let mimeType = map.userData.mimeType; + if ( ! json.textures ) json.textures = []; - if ( mimeType === 'image/webp' ) mimeType = 'image/png'; + // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture + if ( map instanceof CompressedTexture ) { - const textureDef = { - sampler: this.processSampler( map ), - source: this.processImage( map.image, map.format, map.flipY, mimeType ) - }; + map = decompress( map, options.maxTextureSize ); - if ( map.name ) textureDef.name = map.name; + } - this._invokeAll( function ( ext ) { + let mimeType = map.userData.mimeType; - ext.writeTexture && ext.writeTexture( map, textureDef ); + if ( mimeType === 'image/webp' ) mimeType = 'image/png'; - } ); + const textureDef = { + sampler: this.processSampler( map ), + source: this.processImage( map.image, map.format, map.flipY, mimeType ) + }; - const index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; + if ( map.name ) textureDef.name = map.name; - } + this._invokeAll( function ( ext ) { - /** - * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array - */ - processMaterial( material ) { + ext.writeTexture && ext.writeTexture( map, textureDef ); - const cache = this.cache; - const json = this.json; + } ); - if ( cache.materials.has( material ) ) return cache.materials.get( material ); + const index = json.textures.push( textureDef ) - 1; + cache.textures.set( map, index ); + return index; - if ( material.isShaderMaterial ) { + } - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ + processMaterial( material ) { - } + const cache = this.cache; + const json = this.json; - if ( ! json.materials ) json.materials = []; + if ( cache.materials.has( material ) ) return cache.materials.get( material ); - // @QUESTION Should we avoid including any attribute that has the default value? - const materialDef = { pbrMetallicRoughness: {} }; + if ( material.isShaderMaterial ) { - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); + return null; - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + } - } + if ( ! json.materials ) json.materials = []; - // pbrMetallicRoughness.baseColorFactor - const color = material.color.toArray().concat( [ material.opacity ] ); + // @QUESTION Should we avoid including any attribute that has the default value? + const materialDef = { pbrMetallicRoughness: {} }; - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { - materialDef.pbrMetallicRoughness.baseColorFactor = color; + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - } + } - if ( material.isMeshStandardMaterial ) { + // pbrMetallicRoughness.baseColorFactor + const color = material.color.toArray().concat( [ material.opacity ] ); - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - } else { + materialDef.pbrMetallicRoughness.baseColorFactor = color; - materialDef.pbrMetallicRoughness.metallicFactor = 0.5; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + } - } + if ( material.isMeshStandardMaterial ) { - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; - const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); + } else { - const metalRoughMapDef = { - index: this.processTexture( metalRoughTexture ), - channel: metalRoughTexture.channel - }; - this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + materialDef.pbrMetallicRoughness.metallicFactor = 0.5; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; - } + } - // pbrMetallicRoughness.baseColorTexture - if ( material.map ) { + // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.metalnessMap || material.roughnessMap ) { - const baseColorMapDef = { - index: this.processTexture( material.map ), - texCoord: material.map.channel - }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); - } + const metalRoughMapDef = { + index: this.processTexture( metalRoughTexture ), + channel: metalRoughTexture.channel + }; + this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; - if ( material.emissive ) { + } - const emissive = material.emissive; - const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); + // pbrMetallicRoughness.baseColorTexture + if ( material.map ) { - if ( maxEmissiveComponent > 0 ) { + const baseColorMapDef = { + index: this.processTexture( material.map ), + texCoord: material.map.channel + }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - materialDef.emissiveFactor = material.emissive.toArray(); + } - } + if ( material.emissive ) { - // emissiveTexture - if ( material.emissiveMap ) { + const emissive = material.emissive; + const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); - const emissiveMapDef = { - index: this.processTexture( material.emissiveMap ), - texCoord: material.emissiveMap.channel - }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; + if ( maxEmissiveComponent > 0 ) { - } + materialDef.emissiveFactor = material.emissive.toArray(); } - // normalTexture - if ( material.normalMap ) { + // emissiveTexture + if ( material.emissiveMap ) { - const normalMapDef = { - index: this.processTexture( material.normalMap ), - texCoord: material.normalMap.channel + const emissiveMapDef = { + index: this.processTexture( material.emissiveMap ), + texCoord: material.emissiveMap.channel }; + this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); + materialDef.emissiveTexture = emissiveMapDef; - if ( material.normalScale && material.normalScale.x !== 1 ) { + } - // glTF normal scale is univariate. Ignore `y`, which may be flipped. - // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - normalMapDef.scale = material.normalScale.x; + } - } + // normalTexture + if ( material.normalMap ) { + + const normalMapDef = { + index: this.processTexture( material.normalMap ), + texCoord: material.normalMap.channel + }; + + if ( material.normalScale && material.normalScale.x !== 1 ) { - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; + // glTF normal scale is univariate. Ignore `y`, which may be flipped. + // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + normalMapDef.scale = material.normalScale.x; } - // occlusionTexture - if ( material.aoMap ) { + this.applyTextureTransform( normalMapDef, material.normalMap ); + materialDef.normalTexture = normalMapDef; - const occlusionMapDef = { - index: this.processTexture( material.aoMap ), - texCoord: material.aoMap.channel - }; + } - if ( material.aoMapIntensity !== 1.0 ) { + // occlusionTexture + if ( material.aoMap ) { - occlusionMapDef.strength = material.aoMapIntensity; + const occlusionMapDef = { + index: this.processTexture( material.aoMap ), + texCoord: material.aoMap.channel + }; - } + if ( material.aoMapIntensity !== 1.0 ) { - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; + occlusionMapDef.strength = material.aoMapIntensity; } - // alphaMode - if ( material.transparent ) { - - materialDef.alphaMode = 'BLEND'; + this.applyTextureTransform( occlusionMapDef, material.aoMap ); + materialDef.occlusionTexture = occlusionMapDef; - } else { + } - if ( material.alphaTest > 0.0 ) { + // alphaMode + if ( material.transparent ) { - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; + materialDef.alphaMode = 'BLEND'; - } + } else { - } + if ( material.alphaTest > 0.0 ) { - // doubleSided - if ( material.side === DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; + materialDef.alphaMode = 'MASK'; + materialDef.alphaCutoff = material.alphaTest; - this.serializeUserData( material, materialDef ); + } - this._invokeAll( function ( ext ) { + } - ext.writeMaterial && ext.writeMaterial( material, materialDef ); + // doubleSided + if ( material.side === DoubleSide ) materialDef.doubleSided = true; + if ( material.name !== '' ) materialDef.name = material.name; - } ); + this.serializeUserData( material, materialDef ); - const index = json.materials.push( materialDef ) - 1; - cache.materials.set( material, index ); - return index; + this._invokeAll( function ( ext ) { - } + ext.writeMaterial && ext.writeMaterial( material, materialDef ); - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array - */ - processMesh( mesh ) { + } ); - const cache = this.cache; - const json = this.json; + const index = json.materials.push( materialDef ) - 1; + cache.materials.set( material, index ); + return index; - const meshCacheKeyParts = [ mesh.geometry.uuid ]; + } - if ( Array.isArray( mesh.material ) ) { + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ + processMesh( mesh ) { - for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { + const cache = this.cache; + const json = this.json; - meshCacheKeyParts.push( mesh.material[ i ].uuid ); + const meshCacheKeyParts = [ mesh.geometry.uuid ]; - } + if ( Array.isArray( mesh.material ) ) { - } else { + for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { - meshCacheKeyParts.push( mesh.material.uuid ); + meshCacheKeyParts.push( mesh.material[ i ].uuid ); } - const meshCacheKey = meshCacheKeyParts.join( ':' ); + } else { - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + meshCacheKeyParts.push( mesh.material.uuid ); - const geometry = mesh.geometry; + } - let mode; + const meshCacheKey = meshCacheKeyParts.join( ':' ); - // Use the correct mode - if ( mesh.isLineSegments ) { + if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); - mode = WEBGL_CONSTANTS.LINES; + const geometry = mesh.geometry; - } else if ( mesh.isLineLoop ) { + let mode; - mode = WEBGL_CONSTANTS.LINE_LOOP; + // Use the correct mode + if ( mesh.isLineSegments ) { - } else if ( mesh.isLine ) { + mode = WEBGL_CONSTANTS.LINES; - mode = WEBGL_CONSTANTS.LINE_STRIP; + } else if ( mesh.isLineLoop ) { - } else if ( mesh.isPoints ) { + mode = WEBGL_CONSTANTS.LINE_LOOP; - mode = WEBGL_CONSTANTS.POINTS; + } else if ( mesh.isLine ) { - } else { + mode = WEBGL_CONSTANTS.LINE_STRIP; - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + } else if ( mesh.isPoints ) { - } + mode = WEBGL_CONSTANTS.POINTS; - const meshDef = {}; - const attributes = {}; - const primitives = []; - const targets = []; - - // Conversion between attributes names in threejs and gltf spec - const nameConversion = { - uv: 'TEXCOORD_0', - uv1: 'TEXCOORD_1', - uv2: 'TEXCOORD_2', - uv3: 'TEXCOORD_3', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; + } else { - const originalNormal = geometry.getAttribute( 'normal' ); + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + } - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + const meshDef = {}; + const attributes = {}; + const primitives = []; + const targets = []; + + // Conversion between attributes names in threejs and gltf spec + const nameConversion = { + uv: 'TEXCOORD_0', + uv1: 'TEXCOORD_1', + uv2: 'TEXCOORD_2', + uv3: 'TEXCOORD_3', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' + }; - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + const originalNormal = geometry.getAttribute( 'normal' ); - } + if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - let modifiedAttribute = null; + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - for ( let attributeName in geometry.attributes ) { + geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); - // Ignore morph target attributes, which are exported later. - if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; + } - const attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); + // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor + let modifiedAttribute = null; - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - const validVertexAttributes = - /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + for ( let attributeName in geometry.attributes ) { - if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + // Ignore morph target attributes, which are exported later. + if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + const attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. + const validVertexAttributes = + /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; - } + if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - modifiedAttribute = null; - const array = attribute.array; + if ( cache.attributes.has( this.getUID( attribute ) ) ) { - if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { + attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + } - } + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null; + const array = attribute.array; - const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + if ( attributeName === 'JOINTS_0' && + ! ( array instanceof Uint16Array ) && + ! ( array instanceof Uint8Array ) ) { - if ( accessor !== null ) { + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); - if ( ! attributeName.startsWith( '_' ) ) { + } - this.detectMeshQuantization( attributeName, attribute ); + const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); - } + if ( accessor !== null ) { - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); + if ( ! attributeName.startsWith( '_' ) ) { + + this.detectMeshQuantization( attributeName, attribute ); } + attributes[ attributeName ] = accessor; + cache.attributes.set( this.getUID( attribute ), accessor ); + } - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); + } - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) return null; + if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + // Skip if no exportable attributes found + if ( Object.keys( attributes ).length === 0 ) return null; - const weights = []; - const targetNames = []; - const reverseDictionary = {}; + // Morph targets + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - if ( mesh.morphTargetDictionary !== undefined ) { + const weights = []; + const targetNames = []; + const reverseDictionary = {}; - for ( const key in mesh.morphTargetDictionary ) { + if ( mesh.morphTargetDictionary !== undefined ) { - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + for ( const key in mesh.morphTargetDictionary ) { - } + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; } - for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - - const target = {}; - let warned = false; + } - for ( const attributeName in geometry.morphAttributes ) { + for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. + const target = {}; + let warned = false; - if ( attributeName !== 'position' && attributeName !== 'normal' ) { + for ( const attributeName in geometry.morphAttributes ) { - if ( ! warned ) { + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; + if ( attributeName !== 'position' && attributeName !== 'normal' ) { - } + if ( ! warned ) { - continue; + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; } - const attribute = geometry.morphAttributes[ attributeName ][ i ]; - const gltfAttributeName = attributeName.toUpperCase(); + continue; - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + } - const baseAttribute = geometry.attributes[ attributeName ]; + const attribute = geometry.morphAttributes[ attributeName ][ i ]; + const gltfAttributeName = attributeName.toUpperCase(); - if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { + // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); - continue; + const baseAttribute = geometry.attributes[ attributeName ]; - } + if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { - // Clones attribute not to override - const relativeAttribute = attribute.clone(); + target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); + continue; + + } - if ( ! geometry.morphTargetsRelative ) { + // Clones attribute not to override + const relativeAttribute = attribute.clone(); - for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { + if ( ! geometry.morphTargetsRelative ) { - for ( let a = 0; a < attribute.itemSize; a ++ ) { + for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); - if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); - if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); - if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); + for ( let a = 0; a < attribute.itemSize; a ++ ) { - } + if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); + if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); + if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); + if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); } } - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); - } - targets.push( target ); + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); - weights.push( mesh.morphTargetInfluences[ i ] ); + } - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + targets.push( target ); - } + weights.push( mesh.morphTargetInfluences[ i ] ); - meshDef.weights = weights; + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - if ( targetNames.length > 0 ) { + } - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; + meshDef.weights = weights; - } + if ( targetNames.length > 0 ) { - } + meshDef.extras = {}; + meshDef.extras.targetNames = targetNames; - const isMultiMaterial = Array.isArray( mesh.material ); + } - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + } - const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; + const isMultiMaterial = Array.isArray( mesh.material ); - for ( let i = 0, il = groups.length; i < il; i ++ ) { + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - const primitive = { - mode: mode, - attributes: attributes, - }; + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - this.serializeUserData( geometry, primitive ); + for ( let i = 0, il = groups.length; i < il; i ++ ) { - if ( targets.length > 0 ) primitive.targets = targets; + const primitive = { + mode: mode, + attributes: attributes, + }; - if ( geometry.index !== null ) { + this.serializeUserData( geometry, primitive ); - let cacheKey = this.getUID( geometry.index ); + if ( targets.length > 0 ) primitive.targets = targets; - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { + if ( geometry.index !== null ) { - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; + let cacheKey = this.getUID( geometry.index ); - } + if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - if ( cache.attributes.has( cacheKey ) ) { + cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - primitive.indices = cache.attributes.get( cacheKey ); + } - } else { + if ( cache.attributes.has( cacheKey ) ) { - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); + primitive.indices = cache.attributes.get( cacheKey ); - } + } else { - if ( primitive.indices === null ) delete primitive.indices; + primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cache.attributes.set( cacheKey, primitive.indices ); } - const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + if ( primitive.indices === null ) delete primitive.indices; - if ( material !== null ) primitive.material = material; + } - primitives.push( primitive ); + const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); - } + if ( material !== null ) primitive.material = material; + + primitives.push( primitive ); - meshDef.primitives = primitives; + } - if ( ! json.meshes ) json.meshes = []; + meshDef.primitives = primitives; - this._invokeAll( function ( ext ) { + if ( ! json.meshes ) json.meshes = []; - ext.writeMesh && ext.writeMesh( mesh, meshDef ); + this._invokeAll( function ( ext ) { - } ); + ext.writeMesh && ext.writeMesh( mesh, meshDef ); - const index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; + } ); - } + const index = json.meshes.push( meshDef ) - 1; + cache.meshes.set( meshCacheKey, index ); + return index; - /** - * If a vertex attribute with a - * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) - * is used, it is checked whether it is a valid data type according to the - * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) - * extension. - * In this case the extension is automatically added to the list of used extensions. - * - * @param {string} attributeName - * @param {THREE.BufferAttribute} attribute - */ - detectMeshQuantization( attributeName, attribute ) { + } - if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; + /** + * If a vertex attribute with a + * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) + * is used, it is checked whether it is a valid data type according to the + * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) + * extension. + * In this case the extension is automatically added to the list of used extensions. + * + * @param {string} attributeName + * @param {THREE.BufferAttribute} attribute + */ + detectMeshQuantization( attributeName, attribute ) { - let attrType = undefined; + if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; - switch ( attribute.array.constructor ) { + let attrType = undefined; - case Int8Array: + switch ( attribute.array.constructor ) { - attrType = 'byte'; + case Int8Array: - break; + attrType = 'byte'; - case Uint8Array: + break; - attrType = 'unsigned byte'; + case Uint8Array: - break; + attrType = 'unsigned byte'; - case Int16Array: + break; - attrType = 'short'; + case Int16Array: - break; + attrType = 'short'; - case Uint16Array: + break; - attrType = 'unsigned short'; + case Uint16Array: - break; + attrType = 'unsigned short'; - default: + break; - return; + default: - } + return; - if ( attribute.normalized ) attrType += ' normalized'; + } - const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; + if ( attribute.normalized ) attrType += ' normalized'; - if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { + const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; - this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; - this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; + if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { - } + this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; + this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; } - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - processCamera( camera ) { + } - const json = this.json; + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + processCamera( camera ) { - if ( ! json.cameras ) json.cameras = []; + const json = this.json; - const isOrtho = camera.isOrthographicCamera; + if ( ! json.cameras ) json.cameras = []; - const cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; + const isOrtho = camera.isOrthographicCamera; - if ( isOrtho ) { + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective' + }; - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + if ( isOrtho ) { - } else { + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + } else { - } + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: MathUtils.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - // Question: Is saving "type" as name intentional? - if ( camera.name !== '' ) cameraDef.name = camera.type; + } - return json.cameras.push( cameraDef ) - 1; + // Question: Is saving "type" as name intentional? + if ( camera.name !== '' ) cameraDef.name = camera.type; - } + return json.cameras.push( cameraDef ) - 1; - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {number|null} - */ - processAnimation( clip, root ) { + } - const json = this.json; - const nodeMap = this.nodeMap; + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ + processAnimation( clip, root ) { - if ( ! json.animations ) json.animations = []; + const json = this.json; + const nodeMap = this.nodeMap; - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + if ( ! json.animations ) json.animations = []; - const tracks = clip.tracks; - const channels = []; - const samplers = []; + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - for ( let i = 0; i < tracks.length; ++ i ) { + const tracks = clip.tracks; + const channels = []; + const samplers = []; - const track = tracks[ i ]; - const trackBinding = PropertyBinding.parseTrackName( track.name ); - let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; + for ( let i = 0; i < tracks.length; ++ i ) { - if ( trackBinding.objectName === 'bones' ) { + const track = tracks[ i ]; + const trackBinding = PropertyBinding.parseTrackName( track.name ); + let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); + const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - if ( trackNode.isSkinnedMesh === true ) { + if ( trackBinding.objectName === 'bones' ) { - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + if ( trackNode.isSkinnedMesh === true ) { - } else { + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - trackNode = undefined; + } else { - } + trackNode = undefined; } - if ( ! trackNode || ! trackProperty ) { + } - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + if ( ! trackNode || ! trackProperty ) { - } - - const inputItemSize = 1; - let outputItemSize = track.values.length / track.times.length; + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + } - outputItemSize /= trackNode.morphTargetInfluences.length; + const inputItemSize = 1; + let outputItemSize = track.values.length / track.times.length; - } + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - let interpolation; + outputItemSize /= trackNode.morphTargetInfluences.length; - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + } - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { + let interpolation; - interpolation = 'CUBICSPLINE'; + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3; + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). + if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - } else if ( track.getInterpolation() === InterpolateDiscrete ) { + interpolation = 'CUBICSPLINE'; - interpolation = 'STEP'; + // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= 3; - } else { + } else if ( track.getInterpolation() === InterpolateDiscrete ) { - interpolation = 'LINEAR'; + interpolation = 'STEP'; - } + } else { - samplers.push( { - input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), - output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation - } ); - - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } - } ); + interpolation = 'LINEAR'; } - json.animations.push( { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels + samplers.push( { + input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), + output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), + interpolation: interpolation } ); - return json.animations.length - 1; + channels.push( { + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } + } ); } - /** - * @param {THREE.Object3D} object - * @return {number|null} - */ - processSkin( object ) { + json.animations.push( { + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels + } ); - const json = this.json; - const nodeMap = this.nodeMap; + return json.animations.length - 1; - const node = json.nodes[ nodeMap.get( object ) ]; + } - const skeleton = object.skeleton; + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ + processSkin( object ) { - if ( skeleton === undefined ) return null; + const json = this.json; + const nodeMap = this.nodeMap; - const rootJoint = object.skeleton.bones[ 0 ]; + const node = json.nodes[ nodeMap.get( object ) ]; - if ( rootJoint === undefined ) return null; + const skeleton = object.skeleton; - const joints = []; - const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - const temporaryBoneInverse = new Matrix4(); + if ( skeleton === undefined ) return null; - for ( let i = 0; i < skeleton.bones.length; ++ i ) { + const rootJoint = object.skeleton.bones[ 0 ]; - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); + if ( rootJoint === undefined ) return null; - } + const joints = []; + const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + const temporaryBoneInverse = new Matrix4(); - if ( json.skins === undefined ) json.skins = []; + for ( let i = 0; i < skeleton.bones.length; ++ i ) { - json.skins.push( { - inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); + temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - const skinIndex = node.skin = json.skins.length - 1; + } - return skinIndex; + if ( json.skins === undefined ) json.skins = []; - } + json.skins.push( { + inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) + } ); - /** - * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - processNode( object ) { + const skinIndex = node.skin = json.skins.length - 1; - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; + return skinIndex; - if ( ! json.nodes ) json.nodes = []; + } - const nodeDef = {}; + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + processNode( object ) { - if ( options.trs ) { + const json = this.json; + const options = this.options; + const nodeMap = this.nodeMap; - const rotation = object.quaternion.toArray(); - const position = object.position.toArray(); - const scale = object.scale.toArray(); + if ( ! json.nodes ) json.nodes = []; - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + const nodeDef = {}; - nodeDef.rotation = rotation; + if ( options.trs ) { - } + const rotation = object.quaternion.toArray(); + const position = object.position.toArray(); + const scale = object.scale.toArray(); - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - nodeDef.translation = position; + nodeDef.rotation = rotation; - } + } - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - nodeDef.scale = scale; + nodeDef.translation = position; - } + } - } else { + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - if ( object.matrixAutoUpdate ) { + nodeDef.scale = scale; - object.updateMatrix(); + } - } + } else { - if ( isIdentityMatrix( object.matrix ) === false ) { + if ( object.matrixAutoUpdate ) { - nodeDef.matrix = object.matrix.elements; + object.updateMatrix(); - } + } + + if ( isIdentityMatrix( object.matrix ) === false ) { + + nodeDef.matrix = object.matrix.elements; } - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) nodeDef.name = String( object.name ); + } - this.serializeUserData( object, nodeDef ); + // We don't export empty strings name because it represents no-name in Three.js. + if ( object.name !== '' ) nodeDef.name = String( object.name ); - if ( object.isMesh || object.isLine || object.isPoints ) { + this.serializeUserData( object, nodeDef ); - const meshIndex = this.processMesh( object ); + if ( object.isMesh || object.isLine || object.isPoints ) { - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; + const meshIndex = this.processMesh( object ); - } else if ( object.isCamera ) { + if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - nodeDef.camera = this.processCamera( object ); + } else if ( object.isCamera ) { - } + nodeDef.camera = this.processCamera( object ); - if ( object.isSkinnedMesh ) this.skins.push( object ); + } - if ( object.children.length > 0 ) { + if ( object.isSkinnedMesh ) this.skins.push( object ); - const children = []; + if ( object.children.length > 0 ) { - for ( let i = 0, l = object.children.length; i < l; i ++ ) { + const children = []; - const child = object.children[ i ]; + for ( let i = 0, l = object.children.length; i < l; i ++ ) { - if ( child.visible || options.onlyVisible === false ) { + const child = object.children[ i ]; - const nodeIndex = this.processNode( child ); + if ( child.visible || options.onlyVisible === false ) { - if ( nodeIndex !== null ) children.push( nodeIndex ); + const nodeIndex = this.processNode( child ); - } + if ( nodeIndex !== null ) children.push( nodeIndex ); } - if ( children.length > 0 ) nodeDef.children = children; - } - this._invokeAll( function ( ext ) { - - ext.writeNode && ext.writeNode( object, nodeDef ); + if ( children.length > 0 ) nodeDef.children = children; - } ); + } - const nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - return nodeIndex; + this._invokeAll( function ( ext ) { - } + ext.writeNode && ext.writeNode( object, nodeDef ); - /** - * Process Scene - * @param {Scene} node Scene to process - */ - processScene( scene ) { + } ); - const json = this.json; - const options = this.options; + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + return nodeIndex; - if ( ! json.scenes ) { + } - json.scenes = []; - json.scene = 0; + /** + * Process Scene + * @param {Scene} node Scene to process + */ + processScene( scene ) { - } + const json = this.json; + const options = this.options; - const sceneDef = {}; + if ( ! json.scenes ) { - if ( scene.name !== '' ) sceneDef.name = scene.name; + json.scenes = []; + json.scene = 0; - json.scenes.push( sceneDef ); + } - const nodes = []; + const sceneDef = {}; - for ( let i = 0, l = scene.children.length; i < l; i ++ ) { + if ( scene.name !== '' ) sceneDef.name = scene.name; - const child = scene.children[ i ]; + json.scenes.push( sceneDef ); - if ( child.visible || options.onlyVisible === false ) { + const nodes = []; - const nodeIndex = this.processNode( child ); + for ( let i = 0, l = scene.children.length; i < l; i ++ ) { - if ( nodeIndex !== null ) nodes.push( nodeIndex ); + const child = scene.children[ i ]; - } + if ( child.visible || options.onlyVisible === false ) { - } + const nodeIndex = this.processNode( child ); - if ( nodes.length > 0 ) sceneDef.nodes = nodes; + if ( nodeIndex !== null ) nodes.push( nodeIndex ); - this.serializeUserData( scene, sceneDef ); + } } - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process - */ - processObjects( objects ) { + if ( nodes.length > 0 ) sceneDef.nodes = nodes; - const scene = new Scene(); - scene.name = 'AuxScene'; + this.serializeUserData( scene, sceneDef ); + + } - for ( let i = 0; i < objects.length; i ++ ) { + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + processObjects( objects ) { - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); + const scene = new Scene(); + scene.name = 'AuxScene'; - } + for ( let i = 0; i < objects.length; i ++ ) { - this.processScene( scene ); + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push( objects[ i ] ); } - /** - * @param {THREE.Object3D|Array} input - */ - processInput( input ) { + this.processScene( scene ); + + } - const options = this.options; + /** + * @param {THREE.Object3D|Array} input + */ + processInput( input ) { - input = input instanceof Array ? input : [ input ]; + const options = this.options; - this._invokeAll( function ( ext ) { + input = input instanceof Array ? input : [ input ]; - ext.beforeParse && ext.beforeParse( input ); + this._invokeAll( function ( ext ) { - } ); + ext.beforeParse && ext.beforeParse( input ); - const objectsWithoutScene = []; + } ); - for ( let i = 0; i < input.length; i ++ ) { + const objectsWithoutScene = []; - if ( input[ i ] instanceof Scene ) { + for ( let i = 0; i < input.length; i ++ ) { - this.processScene( input[ i ] ); + if ( input[ i ] instanceof Scene ) { - } else { + this.processScene( input[ i ] ); - objectsWithoutScene.push( input[ i ] ); + } else { - } + objectsWithoutScene.push( input[ i ] ); } - if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + } - for ( let i = 0; i < this.skins.length; ++ i ) { + if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); - this.processSkin( this.skins[ i ] ); + for ( let i = 0; i < this.skins.length; ++ i ) { - } + this.processSkin( this.skins[ i ] ); - for ( let i = 0; i < options.animations.length; ++ i ) { + } - this.processAnimation( options.animations[ i ], input[ 0 ] ); + for ( let i = 0; i < options.animations.length; ++ i ) { - } + this.processAnimation( options.animations[ i ], input[ 0 ] ); - this._invokeAll( function ( ext ) { + } - ext.afterParse && ext.afterParse( input ); + this._invokeAll( function ( ext ) { - } ); + ext.afterParse && ext.afterParse( input ); - } + } ); - _invokeAll( func ) { + } - for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { + _invokeAll( func ) { - func( this.plugins[ i ] ); + for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - } + func( this.plugins[ i ] ); } } - /** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ - class GLTFLightExtension { +} - constructor( writer ) { +/** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ +class GLTFLightExtension { - this.writer = writer; - this.name = 'KHR_lights_punctual'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_lights_punctual'; - writeNode( light, nodeDef ) { + } - if ( ! light.isLight ) return; + writeNode( light, nodeDef ) { - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { + if ( ! light.isLight ) return; - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; + if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - } + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); + return; - const writer = this.writer; - const json = writer.json; - const extensionsUsed = writer.extensionsUsed; + } - const lightDef = {}; + const writer = this.writer; + const json = writer.json; + const extensionsUsed = writer.extensionsUsed; - if ( light.name ) lightDef.name = light.name; + const lightDef = {}; - lightDef.color = light.color.toArray(); + if ( light.name ) lightDef.name = light.name; - lightDef.intensity = light.intensity; + lightDef.color = light.color.toArray(); - if ( light.isDirectionalLight ) { + lightDef.intensity = light.intensity; - lightDef.type = 'directional'; + if ( light.isDirectionalLight ) { - } else if ( light.isPointLight ) { + lightDef.type = 'directional'; - lightDef.type = 'point'; + } else if ( light.isPointLight ) { - if ( light.distance > 0 ) lightDef.range = light.distance; + lightDef.type = 'point'; - } else if ( light.isSpotLight ) { + if ( light.distance > 0 ) lightDef.range = light.distance; - lightDef.type = 'spot'; + } else if ( light.isSpotLight ) { - if ( light.distance > 0 ) lightDef.range = light.distance; + lightDef.type = 'spot'; - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; - lightDef.spot.outerConeAngle = light.angle; + if ( light.distance > 0 ) lightDef.range = light.distance; - } + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; + lightDef.spot.outerConeAngle = light.angle; - if ( light.decay !== undefined && light.decay !== 2 ) { + } - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); + if ( light.decay !== undefined && light.decay !== 2 ) { - } + console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + + 'and expects light.decay=2.' ); - if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { + } - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); + if ( light.target + && ( light.target.parent !== light + || light.target.position.x !== 0 + || light.target.position.y !== 0 + || light.target.position.z !== - 1 ) ) { - } + console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + + 'make light.target a child of the light with position 0,0,-1.' ); - if ( ! extensionsUsed[ this.name ] ) { + } - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { lights: [] }; - extensionsUsed[ this.name ] = true; + if ( ! extensionsUsed[ this.name ] ) { - } + json.extensions = json.extensions || {}; + json.extensions[ this.name ] = { lights: [] }; + extensionsUsed[ this.name ] = true; - const lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); + } - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + const lights = json.extensions[ this.name ].lights; + lights.push( lightDef ); - } + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; } - /** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ - class GLTFMaterialsUnlitExtension { +} - constructor( writer ) { +/** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ +class GLTFMaterialsUnlitExtension { - this.writer = writer; - this.name = 'KHR_materials_unlit'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_unlit'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshBasicMaterial ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshBasicMaterial ) return; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = {}; - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; + extensionsUsed[ this.name ] = true; - } + materialDef.pbrMetallicRoughness.metallicFactor = 0.0; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; } - /** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - */ - class GLTFMaterialsClearcoatExtension { +} - constructor( writer ) { +/** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ +class GLTFMaterialsClearcoatExtension { - this.writer = writer; - this.name = 'KHR_materials_clearcoat'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_clearcoat'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionDef.clearcoatFactor = material.clearcoat; + const extensionDef = {}; - if ( material.clearcoatMap ) { + extensionDef.clearcoatFactor = material.clearcoat; - const clearcoatMapDef = { - index: writer.processTexture( material.clearcoatMap ), - texCoord: material.clearcoatMap.channel - }; - writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); - extensionDef.clearcoatTexture = clearcoatMapDef; + if ( material.clearcoatMap ) { - } + const clearcoatMapDef = { + index: writer.processTexture( material.clearcoatMap ), + texCoord: material.clearcoatMap.channel + }; + writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); + extensionDef.clearcoatTexture = clearcoatMapDef; - extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; + } - if ( material.clearcoatRoughnessMap ) { + extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; - const clearcoatRoughnessMapDef = { - index: writer.processTexture( material.clearcoatRoughnessMap ), - texCoord: material.clearcoatRoughnessMap.channel - }; - writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); - extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; + if ( material.clearcoatRoughnessMap ) { - } + const clearcoatRoughnessMapDef = { + index: writer.processTexture( material.clearcoatRoughnessMap ), + texCoord: material.clearcoatRoughnessMap.channel + }; + writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); + extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; - if ( material.clearcoatNormalMap ) { + } - const clearcoatNormalMapDef = { - index: writer.processTexture( material.clearcoatNormalMap ), - texCoord: material.clearcoatNormalMap.channel - }; - writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); - extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; + if ( material.clearcoatNormalMap ) { - } + const clearcoatNormalMapDef = { + index: writer.processTexture( material.clearcoatNormalMap ), + texCoord: material.clearcoatNormalMap.channel + }; + writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); + extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + } - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - } } - /** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - */ - class GLTFMaterialsIridescenceExtension { +} - constructor( writer ) { +/** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ +class GLTFMaterialsIridescenceExtension { - this.writer = writer; - this.name = 'KHR_materials_iridescence'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_iridescence'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionDef.iridescenceFactor = material.iridescence; + const extensionDef = {}; - if ( material.iridescenceMap ) { + extensionDef.iridescenceFactor = material.iridescence; - const iridescenceMapDef = { - index: writer.processTexture( material.iridescenceMap ), - texCoord: material.iridescenceMap.channel - }; - writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); - extensionDef.iridescenceTexture = iridescenceMapDef; + if ( material.iridescenceMap ) { - } + const iridescenceMapDef = { + index: writer.processTexture( material.iridescenceMap ), + texCoord: material.iridescenceMap.channel + }; + writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); + extensionDef.iridescenceTexture = iridescenceMapDef; - extensionDef.iridescenceIor = material.iridescenceIOR; - extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; - extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; + } - if ( material.iridescenceThicknessMap ) { + extensionDef.iridescenceIor = material.iridescenceIOR; + extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; + extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; - const iridescenceThicknessMapDef = { - index: writer.processTexture( material.iridescenceThicknessMap ), - texCoord: material.iridescenceThicknessMap.channel - }; - writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); - extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; + if ( material.iridescenceThicknessMap ) { - } + const iridescenceThicknessMapDef = { + index: writer.processTexture( material.iridescenceThicknessMap ), + texCoord: material.iridescenceThicknessMap.channel + }; + writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); + extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + } - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - */ - class GLTFMaterialsTransmissionExtension { +} - constructor( writer ) { +/** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + */ +class GLTFMaterialsTransmissionExtension { - this.writer = writer; - this.name = 'KHR_materials_transmission'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_transmission'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionDef.transmissionFactor = material.transmission; + const extensionDef = {}; - if ( material.transmissionMap ) { + extensionDef.transmissionFactor = material.transmission; - const transmissionMapDef = { - index: writer.processTexture( material.transmissionMap ), - texCoord: material.transmissionMap.channel - }; - writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); - extensionDef.transmissionTexture = transmissionMapDef; + if ( material.transmissionMap ) { - } + const transmissionMapDef = { + index: writer.processTexture( material.transmissionMap ), + texCoord: material.transmissionMap.channel + }; + writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); + extensionDef.transmissionTexture = transmissionMapDef; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + } - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ - class GLTFMaterialsVolumeExtension { +} - constructor( writer ) { +/** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ +class GLTFMaterialsVolumeExtension { - this.writer = writer; - this.name = 'KHR_materials_volume'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_volume'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionDef.thicknessFactor = material.thickness; + const extensionDef = {}; - if ( material.thicknessMap ) { + extensionDef.thicknessFactor = material.thickness; - const thicknessMapDef = { - index: writer.processTexture( material.thicknessMap ), - texCoord: material.thicknessMap.channel - }; - writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); - extensionDef.thicknessTexture = thicknessMapDef; + if ( material.thicknessMap ) { - } + const thicknessMapDef = { + index: writer.processTexture( material.thicknessMap ), + texCoord: material.thicknessMap.channel + }; + writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); + extensionDef.thicknessTexture = thicknessMapDef; - extensionDef.attenuationDistance = material.attenuationDistance; - extensionDef.attenuationColor = material.attenuationColor.toArray(); + } - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionDef.attenuationDistance = material.attenuationDistance; + extensionDef.attenuationColor = material.attenuationColor.toArray(); - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - */ - class GLTFMaterialsIorExtension { +} - constructor( writer ) { +/** + * Materials ior Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + */ +class GLTFMaterialsIorExtension { - this.writer = writer; - this.name = 'KHR_materials_ior'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_ior'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionDef.ior = material.ior; + const extensionDef = {}; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionDef.ior = material.ior; - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - */ - class GLTFMaterialsSpecularExtension { +} - constructor( writer ) { +/** + * Materials specular Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + */ +class GLTFMaterialsSpecularExtension { - this.writer = writer; - this.name = 'KHR_materials_specular'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_specular'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && - material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && - ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && + material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && + ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - if ( material.specularIntensityMap ) { + const extensionDef = {}; - const specularIntensityMapDef = { - index: writer.processTexture( material.specularIntensityMap ), - texCoord: material.specularIntensityMap.channel - }; - writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); - extensionDef.specularTexture = specularIntensityMapDef; + if ( material.specularIntensityMap ) { - } + const specularIntensityMapDef = { + index: writer.processTexture( material.specularIntensityMap ), + texCoord: material.specularIntensityMap.channel + }; + writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); + extensionDef.specularTexture = specularIntensityMapDef; - if ( material.specularColorMap ) { + } - const specularColorMapDef = { - index: writer.processTexture( material.specularColorMap ), - texCoord: material.specularColorMap.channel - }; - writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); - extensionDef.specularColorTexture = specularColorMapDef; + if ( material.specularColorMap ) { - } + const specularColorMapDef = { + index: writer.processTexture( material.specularColorMap ), + texCoord: material.specularColorMap.channel + }; + writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); + extensionDef.specularColorTexture = specularColorMapDef; - extensionDef.specularFactor = material.specularIntensity; - extensionDef.specularColorFactor = material.specularColor.toArray(); + } - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionDef.specularFactor = material.specularIntensity; + extensionDef.specularColorFactor = material.specularColor.toArray(); - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - */ - class GLTFMaterialsSheenExtension { +} - constructor( writer ) { +/** + * Sheen Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + */ +class GLTFMaterialsSheenExtension { - this.writer = writer; - this.name = 'KHR_materials_sheen'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_sheen'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - if ( material.sheenRoughnessMap ) { + const extensionDef = {}; - const sheenRoughnessMapDef = { - index: writer.processTexture( material.sheenRoughnessMap ), - texCoord: material.sheenRoughnessMap.channel - }; - writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); - extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; + if ( material.sheenRoughnessMap ) { - } + const sheenRoughnessMapDef = { + index: writer.processTexture( material.sheenRoughnessMap ), + texCoord: material.sheenRoughnessMap.channel + }; + writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); + extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; - if ( material.sheenColorMap ) { + } - const sheenColorMapDef = { - index: writer.processTexture( material.sheenColorMap ), - texCoord: material.sheenColorMap.channel - }; - writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); - extensionDef.sheenColorTexture = sheenColorMapDef; + if ( material.sheenColorMap ) { - } + const sheenColorMapDef = { + index: writer.processTexture( material.sheenColorMap ), + texCoord: material.sheenColorMap.channel + }; + writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); + extensionDef.sheenColorTexture = sheenColorMapDef; - extensionDef.sheenRoughnessFactor = material.sheenRoughness; - extensionDef.sheenColorFactor = material.sheenColor.toArray(); + } - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionDef.sheenRoughnessFactor = material.sheenRoughness; + extensionDef.sheenColorFactor = material.sheenColor.toArray(); - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Anisotropy Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy - */ - class GLTFMaterialsAnisotropyExtension { +} - constructor( writer ) { +/** + * Anisotropy Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy + */ +class GLTFMaterialsAnisotropyExtension { - this.writer = writer; - this.name = 'KHR_materials_anisotropy'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_anisotropy'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - if ( material.anisotropyMap ) { + const extensionDef = {}; - const anisotropyMapDef = { index: writer.processTexture( material.anisotropyMap ) }; - writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); - extensionDef.anisotropyTexture = anisotropyMapDef; + if ( material.anisotropyMap ) { - } + const anisotropyMapDef = { index: writer.processTexture( material.anisotropyMap ) }; + writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); + extensionDef.anisotropyTexture = anisotropyMapDef; - extensionDef.anisotropyStrength = material.anisotropy; - extensionDef.anisotropyRotation = material.anisotropyRotation; + } - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionDef.anisotropyStrength = material.anisotropy; + extensionDef.anisotropyRotation = material.anisotropyRotation; - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - */ - class GLTFMaterialsEmissiveStrengthExtension { +} - constructor( writer ) { +/** + * Materials Emissive Strength Extension + * + * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md + */ +class GLTFMaterialsEmissiveStrengthExtension { - this.writer = writer; - this.name = 'KHR_materials_emissive_strength'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'KHR_materials_emissive_strength'; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; + writeMaterial( material, materialDef ) { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; - const extensionDef = {}; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - extensionDef.emissiveStrength = material.emissiveIntensity; + const extensionDef = {}; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + extensionDef.emissiveStrength = material.emissiveIntensity; - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - /** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - */ - class GLTFMeshGpuInstancing { +} - constructor( writer ) { +/** + * GPU Instancing Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing + */ +class GLTFMeshGpuInstancing { - this.writer = writer; - this.name = 'EXT_mesh_gpu_instancing'; + constructor( writer ) { - } + this.writer = writer; + this.name = 'EXT_mesh_gpu_instancing'; - writeNode( object, nodeDef ) { + } - if ( ! object.isInstancedMesh ) return; + writeNode( object, nodeDef ) { - const writer = this.writer; + if ( ! object.isInstancedMesh ) return; - const mesh = object; + const writer = this.writer; - const translationAttr = new Float32Array( mesh.count * 3 ); - const rotationAttr = new Float32Array( mesh.count * 4 ); - const scaleAttr = new Float32Array( mesh.count * 3 ); + const mesh = object; - const matrix = new Matrix4(); - const position = new Vector3(); - const quaternion = new Quaternion(); - const scale = new Vector3(); + const translationAttr = new Float32Array( mesh.count * 3 ); + const rotationAttr = new Float32Array( mesh.count * 4 ); + const scaleAttr = new Float32Array( mesh.count * 3 ); - for ( let i = 0; i < mesh.count; i ++ ) { + const matrix = new Matrix4(); + const position = new Vector3(); + const quaternion = new Quaternion(); + const scale = new Vector3(); - mesh.getMatrixAt( i, matrix ); - matrix.decompose( position, quaternion, scale ); + for ( let i = 0; i < mesh.count; i ++ ) { - position.toArray( translationAttr, i * 3 ); - quaternion.toArray( rotationAttr, i * 4 ); - scale.toArray( scaleAttr, i * 3 ); + mesh.getMatrixAt( i, matrix ); + matrix.decompose( position, quaternion, scale ); - } + position.toArray( translationAttr, i * 3 ); + quaternion.toArray( rotationAttr, i * 4 ); + scale.toArray( scaleAttr, i * 3 ); - const attributes = { - TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), - ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), - SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), - }; + } - if ( mesh.instanceColor ) - attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); + const attributes = { + TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), + ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), + SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), + }; - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { attributes }; + if ( mesh.instanceColor ) + attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); - writer.extensionsUsed[ this.name ] = true; - writer.extensionsRequired[ this.name ] = true; + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { attributes }; - } + writer.extensionsUsed[ this.name ] = true; + writer.extensionsRequired[ this.name ] = true; } - /** - * Static utility functions - */ - GLTFExporter.Utils = { +} - insertKeyframe: function ( track, time ) { +/** + * Static utility functions + */ +GLTFExporter.Utils = { - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); + insertKeyframe: function ( track, time ) { - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); - let index; + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - if ( track.times.length === 0 ) { + let index; - times[ 0 ] = time; + if ( track.times.length === 0 ) { - for ( let i = 0; i < valueSize; i ++ ) { + times[ 0 ] = time; - values[ i ] = 0; + for ( let i = 0; i < valueSize; i ++ ) { - } + values[ i ] = 0; - index = 0; + } - } else if ( time < track.times[ 0 ] ) { + index = 0; - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + } else if ( time < track.times[ 0 ] ) { - times[ 0 ] = time; - times.set( track.times, 1 ); + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); + times[ 0 ] = time; + times.set( track.times, 1 ); - index = 0; + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); - } else if ( time > track.times[ track.times.length - 1 ] ) { + index = 0; - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + } else if ( time > track.times[ track.times.length - 1 ] ) { - return track.times.length - 1; + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - } + return track.times.length - 1; - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); + } - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); - index = times.length - 1; + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); - } else { + index = times.length - 1; - for ( let i = 0; i < track.times.length; i ++ ) { + } else { - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + for ( let i = 0; i < track.times.length; i ++ ) { - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); - index = i + 1; + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - break; + index = i + 1; - } + break; } } - track.times = times; - track.values = values; - - return index; - - }, + } - mergeMorphTargetTracks: function ( clip, root ) { + track.times = times; + track.values = values; - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; + return index; - for ( let i = 0; i < sourceTracks.length; ++ i ) { + }, - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + mergeMorphTargetTracks: function ( clip, root ) { - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + for ( let i = 0; i < sourceTracks.length; ++ i ) { - } + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + } - } + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); } - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - - if ( targetIndex === undefined ) { + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); - } + } - let mergedTrack; + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + if ( targetIndex === undefined ) { - mergedTrack = sourceTrack.clone(); + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + } - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + let mergedTrack; - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - } + mergedTrack = sourceTrack.clone(); - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - continue; + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; } - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + continue; - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + } - } + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - } + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); } - clip.tracks = tracks; + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - return clip; + } } - }; + clip.tracks = tracks; + + return clip; + + } + +}; - return GLTFExporter; +return GLTFExporter; } )(); diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index 4869ca70fd3609..9129b35d794e67 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -28,429 +28,429 @@ if(this.p>4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p UTIF.encodeImage = function(rgba, w, h, metadata) { - var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset - "t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts - "t282":[[72,1]], "t283":[[72,1]], "t284":[1], "t286":[[0,1]], "t287":[[0,1]], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1] - }; - if (metadata) for (var i in metadata) idf[i] = metadata[i]; - - var prfx = new Uint8Array(UTIF.encode([idf])); - var img = new Uint8Array(rgba); - var data = new Uint8Array(1000+w*h*4); - for(var i=0; i probably not an image - img.isLE = id=="II"; - img.width = img["t256"][0]; //delete img["t256"]; - img.height = img["t257"][0]; //delete img["t257"]; - - var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; - var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; - if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); - if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); - - var spp = img["t277"]?img["t277"][0]:1; - var bps = img["t258"]?img["t258"][0]:1; - var bipp = bps*spp; // bits per pixel - /* - var bipp; // bits per pixel - if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; - else bipp = (img["t277"]?img["t277"][0]:1); - */ - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { - bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); - } - if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 - var bipl = Math.ceil(img.width*bipp/8)*8; - var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; - var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; - - if(img["t322"]!=null) // tiled - { - var tw = img["t322"][0], th = img["t323"][0]; - var tx = Math.floor((img.width + tw - 1) / tw); - var ty = Math.floor((img.height + th - 1) / th); - var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); - console.log("====", tx,ty); - for(var y=0; y probably not an image +img.isLE = id=="II"; +img.width = img["t256"][0]; //delete img["t256"]; +img.height = img["t257"][0]; //delete img["t257"]; + +var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; +var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; +if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); +if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); + +var spp = img["t277"]?img["t277"][0]:1; +var bps = img["t258"]?img["t258"][0]:1; +var bipp = bps*spp; // bits per pixel +/* +var bipp; // bits per pixel +if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; +else bipp = (img["t277"]?img["t277"][0]:1); +*/ +// Some .NEF files have t258==14, even though they use 16 bits per pixel +if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { + bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); +} +if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 +var bipl = Math.ceil(img.width*bipp/8)*8; +var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; +var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; +//bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" +var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; + +if(img["t322"]!=null) // tiled +{ + var tw = img["t322"][0], th = img["t323"][0]; + var tx = Math.floor((img.width + tw - 1) / tw); + var ty = Math.floor((img.height + th - 1) / th); + var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); + console.log("====", tx,ty); + for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); - - // convert to Little Endian /* - if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG - for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); + +// convert to Little Endian /* +if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG + for(var y=0; y>>8)&255; + } + else if(noc==3) for(var j= 3; j>>8)&255; - } - else if(noc==3) for(var j= 3; j> 3 ^ 0x3ff0; - return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); - } - } - // Raw Format 6 - function getBufferDataRW6(i) { - return buffer[vpos + 15 - i]; - } - function readPageRW6() { - bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit - bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; - bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); - bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); - bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); - bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; - bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); - bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); - bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; - bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); - bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; - bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; - vpos += 16; - byte = 0; - } - function readPageRw6_bps12() { - bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); - bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; - bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); - bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); - bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); - bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; - bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); - bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); - bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); - bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); - bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); - bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); - bytes[14] = getBufferDataRW6(12) & 0x3; - bytes[15] = getBufferDataRW6(13); - bytes[16] = getBufferDataRW6(14); - bytes[17] = getBufferDataRW6(15); - - vpos += 16; - byte = 0; - } - // Main loop - function resetPredNonzeros(){ - pred[0]=0; pred[1]=0; - nonz[0]=0; nonz[1]=0; - } - if (RW2_Format == 7) { - throw RW2_Format; - - // Skatch of version 7 - /* - var pixels_per_block = bitsPerSample == 14 ? 9 : 10; - rowbytes = 0|(rawWidth / pixels_per_block * 16); - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - i = 0; - for (crow = 0; crow < rowstoread; crow++) { - idx = (row + crow) * rawWidth; - for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { - for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; - if (bitsPerSample == 12) { - result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - } - */ - } - else if(RW2_Format == 6) { - var is12bit = bitsPerSample == 12, - readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, - pixelsPerBlock = is12bit ? 14 : 11, - pixelbase0 = is12bit ? 0x80 : 0x200, - pixelbase_compare = is12bit ? 0x800 : 0x2000, - spix_compare = is12bit ? 0x3fff : 0xffff, - pixel_mask = is12bit ? 0xfff : 0x3fff, - blocksperrow = rawWidth / pixelsPerBlock, - rowbytes = blocksperrow * 16, - bufferSize = is12bit ? 18 : 14; - - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { - idx = (row + crow) * rawWidth; - for (var rblock = 0; rblock < blocksperrow; rblock++) { - readPageRw6Fn(); - resetPredNonzeros(); - sh=0; pixel_base=0; - for (i = 0; i < pixelsPerBlock; i++){ - isOdd = i & 1; - if (i % 3 == 2) { - var base = byte < bufferSize ? bytes[byte++] : 0; - if (base == 3) base = 4; - pixel_base = pixelbase0 << base; - sh = 1 << base; - } - var epixel = byte < bufferSize ? bytes[byte++] : 0; - if (pred[isOdd]) { - epixel *= sh; - if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) - epixel += nonz[isOdd] - pixel_base; - nonz[isOdd] = epixel; - } else { - pred[isOdd] = epixel; - if (epixel) - nonz[isOdd] = epixel; - else - epixel = nonz[isOdd]; - } - result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; - } - } - } - } - } - else if (RW2_Format == 5) { - var blockSize = bitsPerSample == 12 ? 10 : 9; - for (row = 0; row < rawHeight; row++) { - for (col = 0; col < rawWidth; col+=blockSize) { - getDataRaw(0); - // Tuhle podminku pouziva i RW2_Format 7 - if (bitsPerSample == 12) { - result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - //console.log(result[1000000 - 1]) - } else if(RW2_Format == 4) { - for (row = 0; row < rawHeight; row++){ - for(col = 0; col < rawWidth; col++){ - i = col % 14; - isOdd = i & 1; - if (i==0) resetPredNonzeros(); - if (i%3 == 2) - sh = 4 >> (3 - getDataRaw(2)); - if (nonz[isOdd]) { - j = getDataRaw(8); - if(j != 0){ - pred[isOdd] -= 0x80 << sh; - if (pred[isOdd] < 0 || sh == 4) - pred[isOdd] &= ~((-1) << sh); - pred[isOdd] += j << sh; - } - } else { - nonz[isOdd] = getDataRaw(8); - if(nonz[isOdd] || i > 11) - pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); - } - result[idx++] = pred[col & 1]; - } - } - } - else throw RW2_Format; - } + UTIF.decode._decodePanasonic = function(img, data, off, len, tgt, toff) { + + var img_buffer = data.buffer; + + var rawWidth = img["t2"][0]; + var rawHeight = img["t3"][0]; + var bitsPerSample = img["t10"][0]; + var RW2_Format = img["t45"][0]; + + var bidx = 0; + var imageIndex = 0; + var vpos = 0; + var byte = 0; + var arr_a, arr_b; + var bytes = (RW2_Format == 6 ? new Uint32Array(18) : new Uint8Array(16)); + var i, j, sh, pred=[0,0], nonz=[0,0], isOdd, idx = 0, pixel_base; + var row, col, crow; + var buffer = new Uint8Array(0x4000); + var result = new Uint16Array(tgt.buffer); + + function getDataRaw(bits){ + if (vpos == 0) { + var arr_a = new Uint8Array(img_buffer, off+imageIndex + 0x1ff8, 0x4000-0x1ff8); + var arr_b = new Uint8Array(img_buffer, off+imageIndex, 0x1ff8); + buffer.set(arr_a); + buffer.set(arr_b, arr_a.length); + imageIndex += 0x4000; + } + if(RW2_Format == 5) { + for (i = 0; i < 16; i++){ + bytes[i] = buffer[vpos++]; + vpos &= 0x3FFF; + } + } else { + vpos = (vpos - bits) & 0x1ffff; + byte = vpos >> 3 ^ 0x3ff0; + return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); + } + } + // Raw Format 6 + function getBufferDataRW6(i) { + return buffer[vpos + 15 - i]; + } + function readPageRW6() { + bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit + bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; + bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); + bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); + bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); + bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; + bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); + bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); + bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; + bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); + bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; + bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; + vpos += 16; + byte = 0; + } + function readPageRw6_bps12() { + bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); + bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; + bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); + bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); + bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); + bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; + bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); + bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); + bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); + bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); + bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); + bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); + bytes[14] = getBufferDataRW6(12) & 0x3; + bytes[15] = getBufferDataRW6(13); + bytes[16] = getBufferDataRW6(14); + bytes[17] = getBufferDataRW6(15); + + vpos += 16; + byte = 0; + } + // Main loop + function resetPredNonzeros(){ + pred[0]=0; pred[1]=0; + nonz[0]=0; nonz[1]=0; + } + if (RW2_Format == 7) { + throw RW2_Format; + + // Skatch of version 7 + /* + var pixels_per_block = bitsPerSample == 14 ? 9 : 10; + rowbytes = 0|(rawWidth / pixels_per_block * 16); + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + i = 0; + for (crow = 0; crow < rowstoread; crow++) { + idx = (row + crow) * rawWidth; + for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { + for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; + if (bitsPerSample == 12) { + result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + } + */ + } + else if(RW2_Format == 6) { + var is12bit = bitsPerSample == 12, + readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, + pixelsPerBlock = is12bit ? 14 : 11, + pixelbase0 = is12bit ? 0x80 : 0x200, + pixelbase_compare = is12bit ? 0x800 : 0x2000, + spix_compare = is12bit ? 0x3fff : 0xffff, + pixel_mask = is12bit ? 0xfff : 0x3fff, + blocksperrow = rawWidth / pixelsPerBlock, + rowbytes = blocksperrow * 16, + bufferSize = is12bit ? 18 : 14; + + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { + idx = (row + crow) * rawWidth; + for (var rblock = 0; rblock < blocksperrow; rblock++) { + readPageRw6Fn(); + resetPredNonzeros(); + sh=0; pixel_base=0; + for (i = 0; i < pixelsPerBlock; i++){ + isOdd = i & 1; + if (i % 3 == 2) { + var base = byte < bufferSize ? bytes[byte++] : 0; + if (base == 3) base = 4; + pixel_base = pixelbase0 << base; + sh = 1 << base; + } + var epixel = byte < bufferSize ? bytes[byte++] : 0; + if (pred[isOdd]) { + epixel *= sh; + if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) + epixel += nonz[isOdd] - pixel_base; + nonz[isOdd] = epixel; + } else { + pred[isOdd] = epixel; + if (epixel) + nonz[isOdd] = epixel; + else + epixel = nonz[isOdd]; + } + result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; + } + } + } + } + } + else if (RW2_Format == 5) { + var blockSize = bitsPerSample == 12 ? 10 : 9; + for (row = 0; row < rawHeight; row++) { + for (col = 0; col < rawWidth; col+=blockSize) { + getDataRaw(0); + // Tuhle podminku pouziva i RW2_Format 7 + if (bitsPerSample == 12) { + result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + //console.log(result[1000000 - 1]) + } else if(RW2_Format == 4) { + for (row = 0; row < rawHeight; row++){ + for(col = 0; col < rawWidth; col++){ + i = col % 14; + isOdd = i & 1; + if (i==0) resetPredNonzeros(); + if (i%3 == 2) + sh = 4 >> (3 - getDataRaw(2)); + if (nonz[isOdd]) { + j = getDataRaw(8); + if(j != 0){ + pred[isOdd] -= 0x80 << sh; + if (pred[isOdd] < 0 || sh == 4) + pred[isOdd] &= ~((-1) << sh); + pred[isOdd] += j << sh; + } + } else { + nonz[isOdd] = getDataRaw(8); + if(nonz[isOdd] || i > 11) + pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); + } + result[idx++] = pred[col & 1]; + } + } + } + else throw RW2_Format; + } UTIF.decode._decodeVC5 = function(){var x=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],o,C,k,P=[3,3,3,3,2,2,2,1,1,1],V=24576,ar=16384,H=8192,az=ar|H; @@ -483,668 +483,668 @@ L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==92 UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { - var w = img.width, qw=w*4; - var io = 0, out = new Uint8Array(qw); +var w = img.width, qw=w*4; +var io = 0, out = new Uint8Array(qw); + +while(io>> (tab[i] >>> 8); - for(var c=0; c>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } - return; +var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; +var bin=(img.isLE ? UTIF._binLE : UTIF._binBE); +//console.log(raw_width, height, tiff_bps, raw_width*height, src_length); +var arw2 = (raw_width*height == src_length) || (raw_width*height*1.5 == src_length); +//arw2 = true; +//console.log("ARW2: ", arw2, raw_width*height, src_length, tgt.length); +if(!arw2) { //"sony_arw_load_raw"; // not arw2 + height+=8; + var prm = [off,0,0,0]; + var huff = new Uint16Array(32770); + var tab = [ 0xf11,0xf10,0xe0f,0xd0e,0xc0d,0xb0c,0xa0b,0x90a,0x809, + 0x708,0x607,0x506,0x405,0x304,0x303,0x300,0x202,0x201 ]; + var i, c, n, col, row, sum=0; + var ljpeg_diff = UTIF.decode._ljpeg_diff; + + huff[0] = 15; + for (n=i=0; i < 18; i++) { + var lim = 32768 >>> (tab[i] >>> 8); + for(var c=0; c>> 11); - imax = 0x0f & (val >>> 22); - imin = 0x0f & (val >>> 26); - for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); - for (bit=30, i=0; i < 16; i++) - if (i == imax) pix[i] = max; - else if (i == imin) pix[i] = min; - else { - pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; - if (pix[i] > 0x7ff) pix[i] = 0x7ff; - bit += 7; - } - for (i=0; i < 16; i++, col+=2) { - //RAW(row,col) = curve[pix[i] << 1] >> 2; - var clr = pix[i]<<1; //clr = 0xffff; + for (col = raw_width; col--; ) + for (row=0; row < height+1; row+=2) { + if (row == height) row = 1; + sum += ljpeg_diff(inp, prm, huff); + if (row < height) { + var clr = (sum)&4095; UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); } - col -= col & 1 ? 1:31; } + return; +} +if(raw_width*height*1.5==src_length) { + //console.log("weird compression"); + for(var i=0; i>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } + return; +} + +var pix = new Uint16Array(16); +var row, col, val, max, min, imax, imin, sh, bit, i, dp; + +var data = new Uint8Array(raw_width+1); +for (row=0; row < height; row++) { + //fread (data, 1, raw_width, ifp); + for(var j=0; j>> 11); + imax = 0x0f & (val >>> 22); + imin = 0x0f & (val >>> 26); + for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); + for (bit=30, i=0; i < 16; i++) + if (i == imax) pix[i] = max; + else if (i == imin) pix[i] = min; + else { + pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; + if (pix[i] > 0x7ff) pix[i] = 0x7ff; + bit += 7; + } + for (i=0; i < 16; i++, col+=2) { + //RAW(row,col) = curve[pix[i] << 1] >> 2; + var clr = pix[i]<<1; //clr = 0xffff; + UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); + } + col -= col & 1 ? 1:31; } } +} UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) { - var nikon_tree = [ - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ - 5,4,3,6,2,7,1,0,8,9,11,10,12 ], - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ - 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], - [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ - 5,4,6,3,7,2,8,1,9,0,10,11,12 ], - [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ - 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], - [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ - 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], - [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ - 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; - - var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; - - var tree = 0, split = 0; - var make_decoder = UTIF.decode._make_decoder; - var getbithuff = UTIF.decode._getbithuff; - - var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); - //console.log(md[0].toString(16), md[1].toString(16), tiff_bps); - var ver0 = md[mdo++], ver1 = md[mdo++]; - if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; - if (ver0 == 0x46) tree = 2; - if (tiff_bps == 14) tree += 3; - - var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); - for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] - //console.log(vpred); - +var nikon_tree = [ + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ + 5,4,3,6,2,7,1,0,8,9,11,10,12 ], + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ + 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], + [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ + 5,4,6,3,7,2,8,1,9,0,10,11,12 ], + [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ + 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], + [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ + 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], + [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ + 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; - var max = 1 << tiff_bps & 0x7fff, step=0; - var csize = bin.readShort(md,mdo); mdo+=2; - if (csize > 1) step = Math.floor(max / (csize-1)); - if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); - - - var i; - var row, col; - var len, shl, diff; - var min_v = 0; - var hpred = [0,0]; - var huff = make_decoder(nikon_tree[tree]); - - //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; - var prm = [off,0,0,0]; - //console.log(split); split = 170; - - for (min_v=row=0; row < height; row++) { - if (split && row == split) { - //free (huff); - huff = make_decoder (nikon_tree[tree+1]); - //max_v += (min_v = 16) << 1; - } - for (col=0; col < raw_width; col++) { - i = getbithuff(data,prm,huff[0],huff); - len = i & 15; - shl = i >>> 4; - diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; - if ((diff & (1 << (len-1))) == 0) - diff -= (1 << len) - (shl==0?1:0); - if (col < 2) hpred[col] = vpred[row & 1][col] += diff; - else hpred[col & 1] += diff; - - var clr = Math.min(Math.max(hpred[col & 1],0),(1< 1) step = Math.floor(max / (csize-1)); +if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); + + +var i; +var row, col; +var len, shl, diff; +var min_v = 0; +var hpred = [0,0]; +var huff = make_decoder(nikon_tree[tree]); + +//var g_input_offset=0, bitbuf=0, vbits=0, reset=0; +var prm = [off,0,0,0]; +//console.log(split); split = 170; + +for (min_v=row=0; row < height; row++) { + if (split && row == split) { + //free (huff); + huff = make_decoder (nikon_tree[tree+1]); + //max_v += (min_v = 16) << 1; + } + for (col=0; col < raw_width; col++) { + i = getbithuff(data,prm,huff[0],huff); + len = i & 15; + shl = i >>> 4; + diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - (shl==0?1:0); + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + + var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } UTIF.decode._getbithuff = function(data,prm,nbits, huff) { - var zero_after_ff = 0; - var get_byte = UTIF.decode._get_byte; - var c; - - var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; - - //if (nbits > 25) return 0; - //if (nbits < 0) return bitbuf = vbits = reset = 0; - if (nbits == 0 || vbits < 0) return 0; - while (!reset && vbits < nbits && (c = data[off++]) != -1 && - !(reset = zero_after_ff && c == 0xff && data[off++])) { - //console.log("byte read into c"); - bitbuf = (bitbuf << 8) + c; - vbits += 8; - } - c = (bitbuf << (32-vbits)) >>> (32-nbits); - if (huff) { - vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); - c = huff[c+1]&255; - } else - vbits -= nbits; - if (vbits < 0) throw "e"; - - prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; - - return c; +var zero_after_ff = 0; +var get_byte = UTIF.decode._get_byte; +var c; + +var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; + +//if (nbits > 25) return 0; +//if (nbits < 0) return bitbuf = vbits = reset = 0; +if (nbits == 0 || vbits < 0) return 0; +while (!reset && vbits < nbits && (c = data[off++]) != -1 && + !(reset = zero_after_ff && c == 0xff && data[off++])) { + //console.log("byte read into c"); + bitbuf = (bitbuf << 8) + c; + vbits += 8; +} +c = (bitbuf << (32-vbits)) >>> (32-nbits); +if (huff) { + vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); + c = huff[c+1]&255; +} else + vbits -= nbits; +if (vbits < 0) throw "e"; + +prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; + +return c; } UTIF.decode._make_decoder = function(source) { - var max, len, h, i, j; - var huff = []; - - for (max=16; max!=0 && !source[max]; max--); - var si=17; - - huff[0] = max; - for (h=len=1; len <= max; len++) - for (i=0; i < source[len]; i++, ++si) - for (j=0; j < 1 << (max-len); j++) - if (h <= 1 << max) - huff[h++] = (len << 8) | source[si]; - return huff; +var max, len, h, i, j; +var huff = []; + +for (max=16; max!=0 && !source[max]; max--); +var si=17; + +huff[0] = max; +for (h=len=1; len <= max; len++) + for (i=0; i < source[len]; i++, ++si) + for (j=0; j < 1 << (max-len); j++) + if (h <= 1 << max) + huff[h++] = (len << 8) | source[si]; +return huff; } UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff) { - len = Math.min(len, data.length-off); - var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); - - if (tables) { - var SOI = 216, EOI = 217, boff = 0; - for (var i=0; i<(tlen-1); i++) - { - // Skip EOI marker from JPEGTables - if (tables[i]==255 && tables[i+1]==EOI) break; - buff[boff++] = tables[i]; - } +len = Math.min(len, data.length-off); +var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); - // Skip SOI marker from data - var byte1 = data[off], byte2 = data[off + 1]; - if (byte1!=255 || byte2!=SOI) - { - buff[boff++] = byte1; - buff[boff++] = byte2; - } - for (var i=2; i>>8); } - else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } - } - else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 - var rst = 16-bps; - for(var i=0; i>>8); } + else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } + } + else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 + var rst = 16-bps; + for(var i=0; i 1); - } +var SOI = 216, EOI = 217, DQT = 219, DHT = 196, DRI = 221, SOF0 = 192, SOS = 218; +var joff = 0, soff = 0, tables, sosMarker, isTiled = false, i, j, k; +var jpgIchgFmt = img["t513"], jifoff = jpgIchgFmt ? jpgIchgFmt[0] : 0; +var jpgIchgFmtLen = img["t514"], jiflen = jpgIchgFmtLen ? jpgIchgFmtLen[0] : 0; +var soffTag = img["t324"] || img["t273"] || jpgIchgFmt; +var ycbcrss = img["t530"], ssx = 0, ssy = 0; +var spp = img["t277"]?img["t277"][0]:1; +var jpgresint = img["t515"]; + +if(soffTag) +{ + soff = soffTag[0]; + isTiled = (soffTag.length > 1); +} - if(!isTiled) +if(!isTiled) +{ + if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; + if(jpgIchgFmt!=null) { - if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; - if(jpgIchgFmt!=null) - { - if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; - else log("JPEGInterchangeFormat does not point to SOI"); + if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; + else log("JPEGInterchangeFormat does not point to SOI"); - if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); - else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); + if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); + else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); - if(joff != null) return { jpegOffset: joff }; - } + if(joff != null) return { jpegOffset: joff }; } +} - if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } - - if(jpgIchgFmt!=null) - if(jpgIchgFmtLen!=null) - if(jiflen >= 2 && (jifoff+jiflen) <= soff) - { - if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); - else tables = new Uint8Array(jiflen); - - for(i=0; i offset to first strip or tile"); - - if(tables == null) - { - var ooff = 0, out = []; - out[ooff++] = 255; out[ooff++] = SOI; +if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } - var qtables = img["t519"]; - if(qtables==null) throw new Error("JPEGQTables tag is missing"); - for(i=0; i= 2 && (jifoff+jiflen) <= soff) { - out[ooff++] = 255; out[ooff++] = DQT; out[ooff++] = 0; out[ooff++] = 67; out[ooff++] = i; - for(j=0; j<64; j++) out[ooff++] = data[off+qtables[i]+j]; - } + if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); + else tables = new Uint8Array(jiflen); - for(k=0; k<2; k++) - { - var htables = img[(k == 0) ? "t520" : "t521"]; - if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing"); - for(i=0; i>> 8); out[ooff++] = nc & 255; - out[ooff++] = (i | (k << 4)); - for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; - for(j=0; j offset to first strip or tile"); - out[ooff++] = 255; out[ooff++] = SOF0; - out[ooff++] = 0; out[ooff++] = 8 + 3*spp; out[ooff++] = 8; - out[ooff++] = (img.height >>> 8) & 255; out[ooff++] = img.height & 255; - out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; - out[ooff++] = spp; - if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } - else for(i=0; i<3; i++) - { - out[ooff++] = i + 1; - out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - out[ooff++] = i; - } +if(tables == null) +{ + var ooff = 0, out = []; + out[ooff++] = 255; out[ooff++] = SOI; - if(jpgresint!=null && jpgresint[0]!=0) + var qtables = img["t519"]; + if(qtables==null) throw new Error("JPEGQTables tag is missing"); + for(i=0; i>> 8) & 255; - out[ooff++] = jpgresint[0] & 255; + out[ooff++] = 255; out[ooff++] = DHT; + //out[ooff++] = 0; out[ooff++] = 67; out[ooff++] = i; + var nc = 19; + for(j=0; j<16; j++) nc += data[off+htables[i]+j]; + + out[ooff++] = (nc >>> 8); out[ooff++] = nc & 255; + out[ooff++] = (i | (k << 4)); + for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; + for(j=0; j>> 8) & 255; out[ooff++] = img.height & 255; + out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; + out[ooff++] = spp; + if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } + else for(i=0; i<3; i++) + { + out[ooff++] = i + 1; + out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + out[ooff++] = i; } - if(sofpos == -1) + if(jpgresint!=null && jpgresint[0]!=0) { - var tmptab = new Uint8Array(tables.length + 10 + 3*spp); - tmptab.set(tables); - var tmpoff = tables.length; - sofpos = tables.length; - tables = tmptab; - - tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; - tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; - tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; - tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; - tables[tmpoff++] = spp; - if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } - else for(i=0; i<3; i++) - { - tables[tmpoff++] = i + 1; - tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - tables[tmpoff++] = i; - } + out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; + out[ooff++] = (jpgresint[0] >>> 8) & 255; + out[ooff++] = jpgresint[0] & 255; } - if(data[soff]==255 && data[soff+1]==SOS) + tables = new Uint8Array(out); +} + +var sofpos = -1; +i = 0; +while(i < (tables.length - 1)) { + if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } + i++; +} + +if(sofpos == -1) +{ + var tmptab = new Uint8Array(tables.length + 10 + 3*spp); + tmptab.set(tables); + var tmpoff = tables.length; + sofpos = tables.length; + tables = tmptab; + + tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; + tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; + tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; + tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; + tables[tmpoff++] = spp; + if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } + else for(i=0; i<3; i++) { - var soslen = (data[soff+2]<<8) | data[soff+3]; - sosMarker = new Uint8Array(soslen+2); - sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; - for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; + tables[tmpoff++] = i + 1; + tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + tables[tmpoff++] = i; } - else +} + +if(data[soff]==255 && data[soff+1]==SOS) +{ + var soslen = (data[soff+2]<<8) | data[soff+3]; + sosMarker = new Uint8Array(soslen+2); + sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; + for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; +} +else +{ + sosMarker = new Uint8Array(2 + 6 + 2*spp); + var sosoff = 0; + sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; + if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } + else for(i=0; i<3; i++) { - sosMarker = new Uint8Array(2 + 6 + 2*spp); - var sosoff = 0; - sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; - if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } - else for(i=0; i<3; i++) - { - sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; - } - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; + sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; } + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; +} - return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; +return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; } UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) { - var i, dlen, tlen, buff, buffoff; - var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); +var i, dlen, tlen, buff, buffoff; +var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); - if(jpegData.jpegOffset!=null) - { - dlen = off+len-jpegData.jpegOffset; - buff = new Uint8Array(dlen); - for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; - buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; + buff[jpegData.sofPosition+5] = (img.height >>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; + buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; - if(data[off]!=255 || data[off+1]!=SOS) - { - buff.set(jpegData.sosMarker, buffoff); - buffoff += sosMarker.length; - } - for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } - if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } - } - return toff; +var sa = new Int8Array(data.buffer), ta = new Int8Array(tgt.buffer), lim = off+len; +while(off=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } + if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } +} +return toff; } UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) { - var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; - var lim = off+len, qoff = toff*2, px = 0; - while(off>>6), n = (b&63); off++; - if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - } +var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; +var lim = off+len, qoff = toff*2, px = 0; +while(off>>6), n = (b&63); off++; + if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } +} } UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 }; UTIF.decode._lens = ( function() { - var addKeys = function(lens, arr, i0, inc) { for(var i=0; i>>3)>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + if(mode=="H") { - b1 = U._findDiff(pline, a0+(a0==0?0:1), 1-clr), b2 = U._findDiff(pline, b1, clr); // could be precomputed - var bit =0; - if(fo==1) bit = (data[boff>>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - if(mode=="H") + if(U._lens[clr][wrd]!=null) { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } - } + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } } - else - { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } - } - if(line.length==w && mode=="") - { - U._writeBits(line, tgt, toff*8+y*bipl); - clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; - } - //if(wrd.length>150) { log(wrd); break; throw "e"; } } + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + } + if(line.length==w && mode=="") + { + U._writeBits(line, tgt, toff*8+y*bipl); + clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; + } + //if(wrd.length>150) { log(wrd); break; throw "e"; } +} } UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } UTIF.decode._makeDiff = function(line) { - var out = []; if(line[0]==1) out.push(0,1); - for(var i=1; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; - while((boff>>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - len = U._lens[clr][wrd]; - if(len!=null) { - U._addNtimes(line,len,clr); wrd=""; - if(len<64) clr = 1-clr; - if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } - } + len = U._lens[clr][wrd]; + if(len!=null) { + U._addNtimes(line,len,clr); wrd=""; + if(len<64) clr = 1-clr; + if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } } } +} UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) { - var U = UTIF.decode, boff=off<<3, len=0, wrd=""; - var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; +var U = UTIF.decode, boff=off<<3, len=0, wrd=""; +var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; - if(is1D) + if(is1D) + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } + } + } + else + { + if(mode=="H") { if(U._lens[clr][wrd]!=null) { var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } } } else { - if(mode=="H") - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } - } - } - else - { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } - } + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } } - if(wrd.endsWith("000000000001")) // needed for some files - { - if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); - if(twoDim) { - if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; - if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; - boff++; - } - //log("EOL",y, "next 1D:", is1D); - wrd=""; clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; + } + if(wrd.endsWith("000000000001")) // needed for some files + { + if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); + if(twoDim) { + if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; + if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; + boff++; } + //log("EOL",y, "next 1D:", is1D); + wrd=""; clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; } - if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); +} +if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); } UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); +for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); } UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>>----------------"); - for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } - if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); - if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); - else arr = new Uint8Array(data.buffer, o0, len); } - if(type== 3) { for(var j=0; j>>----------------"); +for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } + if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); + if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); + else arr = new Uint8Array(data.buffer, o0, len); } + if(type== 3) { for(var j=0; j4) { bin.writeUint(data, offset, eoff); toff=eoff; } - - if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } - offset += 4; +for(var ki=0; ki4) { bin.writeUint(data, offset, eoff); toff=eoff; } + + if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } + offset += 4; +} +return [offset, eoff]; } UTIF.toRGBA8 = function(out, scl) { - function gamma(x) { return x < 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; } - - - var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; - var img = new Uint8Array(area*4); - //console.log(out); - // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK - var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); - if(out["t262"]==null && bps==1) intp=0; - - var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); - var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format - var bpl = Math.ceil(smpls*bps*w/8); - - //log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); +function gamma(x) { return x < 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; } + + +var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; +var img = new Uint8Array(area*4); +//console.log(out); +// 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK +var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); +if(out["t262"]==null && bps==1) intp=0; + +var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); +var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format +var bpl = Math.ceil(smpls*bps*w/8); + +//log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); + +if(false) {} +else if(intp==0) +{ + scl = 1/256; // "Photopeatest.tif" + for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } + if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } - if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } + if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } } - else if(intp==1) +} +else if(intp==2) +{ + if(bps== 8) { - if(scl==null) scl=1/256; - var f32 = ((data.length&3)==0) ? new Float32Array(data.buffer) : null; + if(smpls==1) for(var i=0; i=4) for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } - if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } - } + if(smpls==3) for(var i=0; i=4) for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; + + for(var y=0; y>>3)]>>>(7- (x&7)))& 1; + else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; + else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; + else if(bps==8) mi= data[dof+x*smpls]; + else throw bps; + img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; } - else if(bps==16){ // 3x 16-bit channel - if(smpls==4) for(var i=0; i4 ? 1 : 0; + for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; - - for(var y=0; y>>3)]>>>(7- (x&7)))& 1; - else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; - else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; - else if(bps==8) mi= data[dof+x*smpls]; - else throw bps; - img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; - } + img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha; } - else if(intp==5) - { - var gotAlpha = smpls>4 ? 1 : 0; - for(var i=0; i>>1); + var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; - if(window.UDOC) { - var C=data[si], M=data[si+1], Y=data[si+2], K=data[si+3]; - var c = UDOC.C.cmykToRgb([C*(1/255), M*(1/255), Y*(1/255), K*(1/255)]); - img[qi] = ~~(0.5+255*c[0]); img[qi+1] = ~~(0.5+255*c[1]); img[qi+2] = ~~(0.5+255*c[2]); - } - else { - var C=255-data[si], M=255-data[si+1], Y=255-data[si+2], K=(255-data[si+3])*(1/255); - img[qi]=~~(C*K+0.5); img[qi+1]=~~(M*K+0.5); img[qi+2]=~~(Y*K+0.5); - } + var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; + var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; + var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; - img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha; + img[qi ]=Math.max(0,Math.min(255,r)); + img[qi+1]=Math.max(0,Math.min(255,g)); + img[qi+2]=Math.max(0,Math.min(255,b)); + img[qi+3]=255; } } - else if(intp==6 && out["t278"]) { // only for DSC_1538.TIF - var rps = out["t278"][0]; - for(var y=0; y>>1); - var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; - - var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; - var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; - var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; - - img[qi ]=Math.max(0,Math.min(255,r)); - img[qi+1]=Math.max(0,Math.min(255,g)); - img[qi+2]=Math.max(0,Math.min(255,b)); - img[qi+3]=255; - } + var L = Math.pow(2, (L + 0.5) / 256 - 64); + var u = (data[si+3] + 0.5) / 410; + var v = (data[si+5] + 0.5) / 410; + + // Luv to xyY + var sX = (9 * u) / (6 * u - 16 * v + 12); + var sY = (4 * v) / (6 * u - 16 * v + 12); + var bY = L; + + // xyY to XYZ + var X = (sX*bY)/sY, Y = bY, Z = (1-sX-sY)*bY/sY; + + + var r = 2.690*X -1.276*Y -0.414*Z + var g = -1.022*X +1.978*Y +0.044*Z + var b = 0.061*X -0.224*Y +1.163*Z + + img[qi ] = gamma(Math.min(r,1))*255; + img[qi+1] = gamma(Math.min(g,1))*255; + img[qi+2] = gamma(Math.min(b,1))*255; + img[qi+3] = 255; } - } - else if(intp==32845) { - - for(var y=0; yma) { ma=ar; page=img; } - } - UTIF.decodeImage(buff, page, ifds); - var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; - - var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; - var ctx = cnv.getContext("2d"); - var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); - ctx.putImageData(imgd,0,0); - return cnv.toDataURL(); +var ifds = UTIF.decode(buff); //console.log(ifds); +var vsns = ifds, ma=0, page=vsns[0]; if(ifds[0].subIFD) vsns = vsns.concat(ifds[0].subIFD); +for(var i=0; ima) { ma=ar; page=img; } +} +UTIF.decodeImage(buff, page, ifds); +var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; + +var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; +var ctx = cnv.getContext("2d"); +var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); +ctx.putImageData(imgd,0,0); +return cnv.toDataURL(); } UTIF._binBE = { - nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, - readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, - readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, - writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, - writeDouble: function(buff, p, n) - { - UTIF._binBE.fl64[0] = n; - for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; - } +nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, +readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, +readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, +readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, +readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, +readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, +writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, +writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, +writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, +writeDouble: function(buff, p, n) +{ + UTIF._binBE.fl64[0] = n; + for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; +} } UTIF._binBE.ui8 = new Uint8Array (8); UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); @@ -1539,66 +1539,66 @@ UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); UTIF._binLE = { - nextZero : UTIF._binBE.nextZero, - readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, - readASCII : UTIF._binBE.readASCII, - readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, - readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, - - writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, - writeASCII : UTIF._binBE.writeASCII +nextZero : UTIF._binBE.nextZero, +readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, +readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, +readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, +readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, +readASCII : UTIF._binBE.readASCII, +readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, +readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, + +writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, +writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, +writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, +writeASCII : UTIF._binBE.writeASCII } UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) { - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min(tw, w-xoff); - var ylim = Math.min(th, h-yoff); - for(var y=0; y>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); - var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; - w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; - h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; - cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; - d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; - if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); - d+=Y&15;while(w>>4; - if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); - n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; - while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; - H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; - H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; - return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); - (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; - V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; - N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; - N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); - H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); - n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); - - +if(N[0]==3&&N[1]==0)return W?W:new R(0);var V=H.H,n=V.b,A=V.e,l=V.R,M=V.n,I=V.A,e=V.Z,b=V.m,Z=W==null; +if(Z)W=new R(N.length>>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); +var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; +w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; +h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; +cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; +d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; +if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); +d+=Y&15;while(w>>4; +if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); +n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; +while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; +H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; +H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; +return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); +(function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; +V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; +N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; +N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); +H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); +n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); + + UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x { - UniformsLib.line = { - - worldUnits: { value: 1 }, - linewidth: { value: 1 }, - resolution: { value: new Vector2( 1, 1 ) }, - dashOffset: { value: 0 }, - dashScale: { value: 1 }, - dashSize: { value: 1 }, - gapSize: { value: 1 } // todo FIX - maybe change to totalSize - - }; - - ShaderLib[ 'line' ] = { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.fog, - UniformsLib.line - ] ), - - vertexShader: - /* glsl */` - #include - #include - #include - #include - #include - - uniform float linewidth; - uniform vec2 resolution; - - attribute vec3 instanceStart; - attribute vec3 instanceEnd; - - attribute vec3 instanceColorStart; - attribute vec3 instanceColorEnd; - - #ifdef WORLD_UNITS - - varying vec4 worldPos; - varying vec3 worldStart; - varying vec3 worldEnd; - - #ifdef USE_DASH - - varying vec2 vUv; - - #endif - - #else - +UniformsLib.line = { + + worldUnits: { value: 1 }, + linewidth: { value: 1 }, + resolution: { value: new Vector2( 1, 1 ) }, + dashOffset: { value: 0 }, + dashScale: { value: 1 }, + dashSize: { value: 1 }, + gapSize: { value: 1 } // todo FIX - maybe change to totalSize + +}; + +ShaderLib[ 'line' ] = { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.fog, + UniformsLib.line + ] ), + + vertexShader: + /* glsl */` + #include + #include + #include + #include + #include + + uniform float linewidth; + uniform vec2 resolution; + + attribute vec3 instanceStart; + attribute vec3 instanceEnd; + + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + varying vec2 vUv; - + #endif - + + #else + + varying vec2 vUv; + + #endif + + #ifdef USE_DASH + + uniform float dashScale; + attribute float instanceDistanceStart; + attribute float instanceDistanceEnd; + varying float vLineDistance; + + #endif + + void trimSegment( const in vec4 start, inout vec4 end ) { + + // trim end segment so it terminates between the camera plane and the near plane + + // conservative estimate of the near plane + float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column + float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column + float nearEstimate = - 0.5 * b / a; + + float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); + + end.xyz = mix( start.xyz, end.xyz, alpha ); + + } + + void main() { + + #ifdef USE_COLOR + + vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; + + #endif + #ifdef USE_DASH - - uniform float dashScale; - attribute float instanceDistanceStart; - attribute float instanceDistanceEnd; - varying float vLineDistance; - + + vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; + vUv = uv; + #endif - - void trimSegment( const in vec4 start, inout vec4 end ) { - - // trim end segment so it terminates between the camera plane and the near plane - - // conservative estimate of the near plane - float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column - float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column - float nearEstimate = - 0.5 * b / a; - - float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); - - end.xyz = mix( start.xyz, end.xyz, alpha ); - - } - - void main() { - - #ifdef USE_COLOR - - vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; - - #endif - - #ifdef USE_DASH - - vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; - vUv = uv; - - #endif - - float aspect = resolution.x / resolution.y; - - // camera space - vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); - vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); - - #ifdef WORLD_UNITS - - worldStart = start.xyz; - worldEnd = end.xyz; - - #else - - vUv = uv; - - #endif - - // special case for perspective projection, and segments that terminate either in, or behind, the camera plane - // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space - // but we need to perform ndc-space calculations in the shader, so we must address this issue directly - // perhaps there is a more elegant solution -- WestLangley - - bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column - - if ( perspective ) { - - if ( start.z < 0.0 && end.z >= 0.0 ) { - - trimSegment( start, end ); - - } else if ( end.z < 0.0 && start.z >= 0.0 ) { - - trimSegment( end, start ); - - } - + + float aspect = resolution.x / resolution.y; + + // camera space + vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + + #ifdef WORLD_UNITS + + worldStart = start.xyz; + worldEnd = end.xyz; + + #else + + vUv = uv; + + #endif + + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley + + bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column + + if ( perspective ) { + + if ( start.z < 0.0 && end.z >= 0.0 ) { + + trimSegment( start, end ); + + } else if ( end.z < 0.0 && start.z >= 0.0 ) { + + trimSegment( end, start ); + } - - // clip space - vec4 clipStart = projectionMatrix * start; - vec4 clipEnd = projectionMatrix * end; - - // ndc space - vec3 ndcStart = clipStart.xyz / clipStart.w; - vec3 ndcEnd = clipEnd.xyz / clipEnd.w; - - // direction - vec2 dir = ndcEnd.xy - ndcStart.xy; - - // account for clip-space aspect ratio - dir.x *= aspect; - dir = normalize( dir ); - - #ifdef WORLD_UNITS - - // get the offset direction as perpendicular to the view vector - vec3 worldDir = normalize( end.xyz - start.xyz ); - vec3 offset; - if ( position.y < 0.5 ) { - - offset = normalize( cross( start.xyz, worldDir ) ); - - } else { - - offset = normalize( cross( end.xyz, worldDir ) ); - - } - - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; - - float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); - - // don't extend the line if we're rendering dashes because we - // won't be rendering the endcaps - #ifndef USE_DASH - - // extend the line bounds to encompass endcaps - start.xyz += - worldDir * linewidth * 0.5; - end.xyz += worldDir * linewidth * 0.5; - - // shift the position of the quad so it hugs the forward edge of the line - offset.xy -= dir * forwardOffset; - offset.z += 0.5; - - #endif - - // endcaps - if ( position.y > 1.0 || position.y < 0.0 ) { - - offset.xy += dir * 2.0 * forwardOffset; - - } - - // adjust for linewidth - offset *= linewidth * 0.5; - - // set the world position - worldPos = ( position.y < 0.5 ) ? start : end; - worldPos.xyz += offset; - - // project the worldpos - vec4 clip = projectionMatrix * worldPos; - - // shift the depth of the projected points so the line - // segments overlap neatly - vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; - clip.z = clipPose.z * clip.w; - - #else - - vec2 offset = vec2( dir.y, - dir.x ); - // undo aspect ratio adjustment - dir.x /= aspect; - offset.x /= aspect; - - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; - - // endcaps - if ( position.y < 0.0 ) { - - offset += - dir; - - } else if ( position.y > 1.0 ) { - - offset += dir; - - } - - // adjust for linewidth - offset *= linewidth; - - // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... - offset /= resolution.y; - - // select end - vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; - - // back to clip space - offset *= clip.w; - - clip.xy += offset; - - #endif - - gl_Position = clip; - - vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation - - #include - #include - #include - + } - `, - - fragmentShader: - /* glsl */` - uniform vec3 diffuse; - uniform float opacity; - uniform float linewidth; - - #ifdef USE_DASH - - uniform float dashOffset; - uniform float dashSize; - uniform float gapSize; - - #endif - - varying float vLineDistance; - + + // clip space + vec4 clipStart = projectionMatrix * start; + vec4 clipEnd = projectionMatrix * end; + + // ndc space + vec3 ndcStart = clipStart.xyz / clipStart.w; + vec3 ndcEnd = clipEnd.xyz / clipEnd.w; + + // direction + vec2 dir = ndcEnd.xy - ndcStart.xy; + + // account for clip-space aspect ratio + dir.x *= aspect; + dir = normalize( dir ); + #ifdef WORLD_UNITS - - varying vec4 worldPos; - varying vec3 worldStart; - varying vec3 worldEnd; - - #ifdef USE_DASH - - varying vec2 vUv; - + + // get the offset direction as perpendicular to the view vector + vec3 worldDir = normalize( end.xyz - start.xyz ); + vec3 offset; + if ( position.y < 0.5 ) { + + offset = normalize( cross( start.xyz, worldDir ) ); + + } else { + + offset = normalize( cross( end.xyz, worldDir ) ); + + } + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); + + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + #ifndef USE_DASH + + // extend the line bounds to encompass endcaps + start.xyz += - worldDir * linewidth * 0.5; + end.xyz += worldDir * linewidth * 0.5; + + // shift the position of the quad so it hugs the forward edge of the line + offset.xy -= dir * forwardOffset; + offset.z += 0.5; + #endif - + + // endcaps + if ( position.y > 1.0 || position.y < 0.0 ) { + + offset.xy += dir * 2.0 * forwardOffset; + + } + + // adjust for linewidth + offset *= linewidth * 0.5; + + // set the world position + worldPos = ( position.y < 0.5 ) ? start : end; + worldPos.xyz += offset; + + // project the worldpos + vec4 clip = projectionMatrix * worldPos; + + // shift the depth of the projected points so the line + // segments overlap neatly + vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; + clip.z = clipPose.z * clip.w; + #else - + + vec2 offset = vec2( dir.y, - dir.x ); + // undo aspect ratio adjustment + dir.x /= aspect; + offset.x /= aspect; + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + // endcaps + if ( position.y < 0.0 ) { + + offset += - dir; + + } else if ( position.y > 1.0 ) { + + offset += dir; + + } + + // adjust for linewidth + offset *= linewidth; + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset /= resolution.y; + + // select end + vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; + + // back to clip space + offset *= clip.w; + + clip.xy += offset; + + #endif + + gl_Position = clip; + + vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation + + #include + #include + #include + + } + `, + + fragmentShader: + /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + uniform float linewidth; + + #ifdef USE_DASH + + uniform float dashOffset; + uniform float dashSize; + uniform float gapSize; + + #endif + + varying float vLineDistance; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + varying vec2 vUv; - + #endif - - #include - #include - #include - #include - #include - - vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { - - float mua; - float mub; - - vec3 p13 = p1 - p3; - vec3 p43 = p4 - p3; - - vec3 p21 = p2 - p1; - - float d1343 = dot( p13, p43 ); - float d4321 = dot( p43, p21 ); - float d1321 = dot( p13, p21 ); - float d4343 = dot( p43, p43 ); - float d2121 = dot( p21, p21 ); - - float denom = d2121 * d4343 - d4321 * d4321; - - float numer = d1343 * d4321 - d1321 * d4343; - - mua = numer / denom; - mua = clamp( mua, 0.0, 1.0 ); - mub = ( d1343 + d4321 * ( mua ) ) / d4343; - mub = clamp( mub, 0.0, 1.0 ); - - return vec2( mua, mub ); - - } - - void main() { - - #include - - #ifdef USE_DASH - - if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps - - if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX - - #endif - - float alpha = opacity; - - #ifdef WORLD_UNITS - - // Find the closest points on the view ray and the line segment - vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; - vec3 lineDir = worldEnd - worldStart; - vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); - - vec3 p1 = worldStart + lineDir * params.x; - vec3 p2 = rayEnd * params.y; - vec3 delta = p1 - p2; - float len = length( delta ); - float norm = len / linewidth; - - #ifndef USE_DASH - - #ifdef USE_ALPHA_TO_COVERAGE - - float dnorm = fwidth( norm ); - alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); - - #else - - if ( norm > 0.5 ) { - - discard; - - } - - #endif - - #endif - - #else - + + #else + + varying vec2 vUv; + + #endif + + #include + #include + #include + #include + #include + + vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { + + float mua; + float mub; + + vec3 p13 = p1 - p3; + vec3 p43 = p4 - p3; + + vec3 p21 = p2 - p1; + + float d1343 = dot( p13, p43 ); + float d4321 = dot( p43, p21 ); + float d1321 = dot( p13, p21 ); + float d4343 = dot( p43, p43 ); + float d2121 = dot( p21, p21 ); + + float denom = d2121 * d4343 - d4321 * d4321; + + float numer = d1343 * d4321 - d1321 * d4343; + + mua = numer / denom; + mua = clamp( mua, 0.0, 1.0 ); + mub = ( d1343 + d4321 * ( mua ) ) / d4343; + mub = clamp( mub, 0.0, 1.0 ); + + return vec2( mua, mub ); + + } + + void main() { + + #include + + #ifdef USE_DASH + + if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps + + if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX + + #endif + + float alpha = opacity; + + #ifdef WORLD_UNITS + + // Find the closest points on the view ray and the line segment + vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; + vec3 lineDir = worldEnd - worldStart; + vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); + + vec3 p1 = worldStart + lineDir * params.x; + vec3 p2 = rayEnd * params.y; + vec3 delta = p1 - p2; + float len = length( delta ); + float norm = len / linewidth; + + #ifndef USE_DASH + #ifdef USE_ALPHA_TO_COVERAGE - - // artifacts appear on some hardware if a derivative is taken within a conditional - float a = vUv.x; - float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; - float len2 = a * a + b * b; - float dlen = fwidth( len2 ); - - if ( abs( vUv.y ) > 1.0 ) { - - alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); - - } - + + float dnorm = fwidth( norm ); + alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); + #else - - if ( abs( vUv.y ) > 1.0 ) { - - float a = vUv.x; - float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; - float len2 = a * a + b * b; - - if ( len2 > 1.0 ) discard; - + + if ( norm > 0.5 ) { + + discard; + } - + #endif - + #endif - - vec4 diffuseColor = vec4( diffuse, alpha ); - - #include - #include - - gl_FragColor = vec4( diffuseColor.rgb, alpha ); - - #include - #include - #include - #include - - } - ` - }; - class LineMaterial extends ShaderMaterial { + #else - constructor( parameters ) { + #ifdef USE_ALPHA_TO_COVERAGE - super( { + // artifacts appear on some hardware if a derivative is taken within a conditional + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; + float dlen = fwidth( len2 ); - type: 'LineMaterial', + if ( abs( vUv.y ) > 1.0 ) { - uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), + alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); - vertexShader: ShaderLib[ 'line' ].vertexShader, - fragmentShader: ShaderLib[ 'line' ].fragmentShader, + } - clipping: true // required for clipping support + #else - } ); + if ( abs( vUv.y ) > 1.0 ) { - this.isLineMaterial = true; + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; - this.setValues( parameters ); + if ( len2 > 1.0 ) discard; - } + } - get color() { + #endif - return this.uniforms.diffuse.value; + #endif - } + vec4 diffuseColor = vec4( diffuse, alpha ); - set color( value ) { + #include + #include - this.uniforms.diffuse.value = value; + gl_FragColor = vec4( diffuseColor.rgb, alpha ); + + #include + #include + #include + #include } + ` +}; - get worldUnits() { +class LineMaterial extends ShaderMaterial { - return 'WORLD_UNITS' in this.defines; + constructor( parameters ) { - } + super( { - set worldUnits( value ) { + type: 'LineMaterial', - if ( value === true ) { + uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), - this.defines.WORLD_UNITS = ''; + vertexShader: ShaderLib[ 'line' ].vertexShader, + fragmentShader: ShaderLib[ 'line' ].fragmentShader, - } else { + clipping: true // required for clipping support - delete this.defines.WORLD_UNITS; + } ); - } + this.isLineMaterial = true; - } + this.setValues( parameters ); - get linewidth() { + } - return this.uniforms.linewidth.value; + get color() { - } + return this.uniforms.diffuse.value; - set linewidth( value ) { + } - if ( ! this.uniforms.linewidth ) return; - this.uniforms.linewidth.value = value; + set color( value ) { - } + this.uniforms.diffuse.value = value; + + } + + get worldUnits() { + + return 'WORLD_UNITS' in this.defines; + + } + + set worldUnits( value ) { + + if ( value === true ) { - get dashed() { + this.defines.WORLD_UNITS = ''; - return 'USE_DASH' in this.defines; + } else { + + delete this.defines.WORLD_UNITS; } - set dashed( value ) { + } - if ( ( value === true ) !== this.dashed ) { + get linewidth() { - this.needsUpdate = true; + return this.uniforms.linewidth.value; - } + } - if ( value === true ) { + set linewidth( value ) { - this.defines.USE_DASH = ''; + if ( ! this.uniforms.linewidth ) return; + this.uniforms.linewidth.value = value; - } else { + } - delete this.defines.USE_DASH; + get dashed() { - } + return 'USE_DASH' in this.defines; - } + } - get dashScale() { + set dashed( value ) { - return this.uniforms.dashScale.value; + if ( ( value === true ) !== this.dashed ) { + + this.needsUpdate = true; } - set dashScale( value ) { + if ( value === true ) { + + this.defines.USE_DASH = ''; - this.uniforms.dashScale.value = value; + } else { + + delete this.defines.USE_DASH; } - get dashSize() { + } - return this.uniforms.dashSize.value; + get dashScale() { - } + return this.uniforms.dashScale.value; - set dashSize( value ) { + } - this.uniforms.dashSize.value = value; + set dashScale( value ) { - } + this.uniforms.dashScale.value = value; - get dashOffset() { + } - return this.uniforms.dashOffset.value; + get dashSize() { - } + return this.uniforms.dashSize.value; - set dashOffset( value ) { + } - this.uniforms.dashOffset.value = value; + set dashSize( value ) { - } + this.uniforms.dashSize.value = value; - get gapSize() { + } - return this.uniforms.gapSize.value; + get dashOffset() { - } + return this.uniforms.dashOffset.value; - set gapSize( value ) { + } - this.uniforms.gapSize.value = value; + set dashOffset( value ) { - } + this.uniforms.dashOffset.value = value; - get opacity() { + } - return this.uniforms.opacity.value; + get gapSize() { - } + return this.uniforms.gapSize.value; - set opacity( value ) { + } - if ( ! this.uniforms ) return; - this.uniforms.opacity.value = value; + set gapSize( value ) { - } + this.uniforms.gapSize.value = value; - get resolution() { + } - return this.uniforms.resolution.value; + get opacity() { - } + return this.uniforms.opacity.value; - set resolution( value ) { + } - this.uniforms.resolution.value.copy( value ); + set opacity( value ) { - } + if ( ! this.uniforms ) return; + this.uniforms.opacity.value = value; - get alphaToCoverage() { + } - return 'USE_ALPHA_TO_COVERAGE' in this.defines; + get resolution() { - } + return this.uniforms.resolution.value; + + } - set alphaToCoverage( value ) { + set resolution( value ) { - if ( ! this.defines ) return; + this.uniforms.resolution.value.copy( value ); - if ( ( value === true ) !== this.alphaToCoverage ) { + } - this.needsUpdate = true; + get alphaToCoverage() { - } + return 'USE_ALPHA_TO_COVERAGE' in this.defines; - if ( value === true ) { + } - this.defines.USE_ALPHA_TO_COVERAGE = ''; - this.extensions.derivatives = true; + set alphaToCoverage( value ) { - } else { + if ( ! this.defines ) return; - delete this.defines.USE_ALPHA_TO_COVERAGE; - this.extensions.derivatives = false; + if ( ( value === true ) !== this.alphaToCoverage ) { - } + this.needsUpdate = true; + + } + + if ( value === true ) { + + this.defines.USE_ALPHA_TO_COVERAGE = ''; + this.extensions.derivatives = true; + + } else { + + delete this.defines.USE_ALPHA_TO_COVERAGE; + this.extensions.derivatives = false; } } - return LineMaterial; +} + +return LineMaterial; } )(); diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index aed2600ae5fef4..07449cddb8a524 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -73,850 +73,850 @@ import { ZSTDDecoder } from '../libs/zstddec.module.js'; const KTX2Loader = /* @__PURE__ */ ( () => { - const _taskCache = new WeakMap(); +const _taskCache = new WeakMap(); - let _activeLoaders = 0; +let _activeLoaders = 0; - let _zstd; +let _zstd; - class KTX2Loader extends Loader { +class KTX2Loader extends Loader { - constructor( manager ) { + constructor( manager ) { - super( manager ); + super( manager ); - this.transcoderPath = ''; - this.transcoderBinary = null; - this.transcoderPending = null; + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; - this.workerPool = new WorkerPool(); - this.workerSourceURL = ''; - this.workerConfig = null; + this.workerPool = new WorkerPool(); + this.workerSourceURL = ''; + this.workerConfig = null; - if ( typeof MSC_TRANSCODER !== 'undefined' ) { + if ( typeof MSC_TRANSCODER !== 'undefined' ) { - console.warn( + console.warn( - 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' - + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' + 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' - ); - - } + ); } - setTranscoderPath( path ) { + } - this.transcoderPath = path; + setTranscoderPath( path ) { - return this; + this.transcoderPath = path; - } + return this; - setWorkerLimit( num ) { + } - this.workerPool.setWorkerLimit( num ); + setWorkerLimit( num ) { - return this; + this.workerPool.setWorkerLimit( num ); - } + return this; - detectSupport( renderer ) { + } - if ( renderer.isWebGPURenderer === true ) { + detectSupport( renderer ) { - this.workerConfig = { - astcSupported: renderer.hasFeature( 'texture-compression-astc' ), - etc1Supported: false, - etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), - dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), - bptcSupported: false, - pvrtcSupported: false - }; + if ( renderer.isWebGPURenderer === true ) { - } else { + this.workerConfig = { + astcSupported: renderer.hasFeature( 'texture-compression-astc' ), + etc1Supported: false, + etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), + dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), + bptcSupported: false, + pvrtcSupported: false + }; - this.workerConfig = { - astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), - etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), - etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), - dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), - bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), - pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) - || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) - }; + } else { - if ( renderer.capabilities.isWebGL2 ) { + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + }; - // https://github.com/mrdoob/three.js/pull/22928 - this.workerConfig.etc1Supported = false; + if ( renderer.capabilities.isWebGL2 ) { - } + // https://github.com/mrdoob/three.js/pull/22928 + this.workerConfig.etc1Supported = false; } - return this; - } - init() { + return this; + + } - if ( ! this.transcoderPending ) { + init() { - // Load transcoder wrapper. - const jsLoader = new FileLoader( this.manager ); - jsLoader.setPath( this.transcoderPath ); - jsLoader.setWithCredentials( this.withCredentials ); - const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); + if ( ! this.transcoderPending ) { - // Load transcoder WASM binary. - const binaryLoader = new FileLoader( this.manager ); - binaryLoader.setPath( this.transcoderPath ); - binaryLoader.setResponseType( 'arraybuffer' ); - binaryLoader.setWithCredentials( this.withCredentials ); - const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); - this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) - .then( ( [ jsContent, binaryContent ] ) => { + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); - const fn = KTX2Loader.BasisWorker.toString(); + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { - const body = [ - '/* constants */', - 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), - 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), - 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), - '/* basis_transcoder.js */', - jsContent, - '/* worker */', - fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) - ].join( '\n' ); + const fn = KTX2Loader.BasisWorker.toString(); - this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); - this.transcoderBinary = binaryContent; + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); - this.workerPool.setWorkerCreator( () => { + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; - const worker = new Worker( this.workerSourceURL ); - const transcoderBinary = this.transcoderBinary.slice( 0 ); + this.workerPool.setWorkerCreator( () => { - worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); + const worker = new Worker( this.workerSourceURL ); + const transcoderBinary = this.transcoderBinary.slice( 0 ); - return worker; + worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); - } ); + return worker; } ); - if ( _activeLoaders > 0 ) { + } ); - // Each instance loads a transcoder and allocates workers, increasing network and memory cost. + if ( _activeLoaders > 0 ) { - console.warn( + // Each instance loads a transcoder and allocates workers, increasing network and memory cost. - 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' - + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' + console.warn( - ); + 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' + + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' - } - - _activeLoaders ++; + ); } - return this.transcoderPending; + _activeLoaders ++; } - load( url, onLoad, onProgress, onError ) { - - if ( this.workerConfig === null ) { - - throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); + return this.transcoderPending; - } + } - const loader = new FileLoader( this.manager ); + load( url, onLoad, onProgress, onError ) { - loader.setResponseType( 'arraybuffer' ); - loader.setWithCredentials( this.withCredentials ); + if ( this.workerConfig === null ) { - loader.load( url, ( buffer ) => { + throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache.has( buffer ) ) { + } - const cachedTask = _taskCache.get( buffer ); + const loader = new FileLoader( this.manager ); - return cachedTask.promise.then( onLoad ).catch( onError ); + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); - } + loader.load( url, ( buffer ) => { - this._createTexture( buffer ) - .then( ( texture ) => onLoad ? onLoad( texture ) : null ) - .catch( onError ); + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { - }, onProgress, onError ); + const cachedTask = _taskCache.get( buffer ); - } + return cachedTask.promise.then( onLoad ).catch( onError ); - _createTextureFrom( transcodeResult, container ) { + } - const { faces, width, height, format, type, error, dfdFlags } = transcodeResult; + this._createTexture( buffer ) + .then( ( texture ) => onLoad ? onLoad( texture ) : null ) + .catch( onError ); - if ( type === 'error' ) return Promise.reject( error ); + }, onProgress, onError ); - let texture; + } - if ( container.faceCount === 6 ) { + _createTextureFrom( transcodeResult, container ) { - texture = new CompressedCubeTexture( faces, format, UnsignedByteType ); + const { faces, width, height, format, type, error, dfdFlags } = transcodeResult; - } else { + if ( type === 'error' ) return Promise.reject( error ); - const mipmaps = faces[ 0 ].mipmaps; + let texture; - texture = container.layerCount > 1 - ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) - : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + if ( container.faceCount === 6 ) { - } + texture = new CompressedCubeTexture( faces, format, UnsignedByteType ); - texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; - texture.magFilter = LinearFilter; - texture.generateMipmaps = false; + } else { - texture.needsUpdate = true; - texture.colorSpace = parseColorSpace( container ); - texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); + const mipmaps = faces[ 0 ].mipmaps; - return texture; + texture = container.layerCount > 1 + ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) + : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); } - /** - * @param {ArrayBuffer} buffer - * @param {object?} config - * @return {Promise} - */ - async _createTexture( buffer, config = {} ) { - - const container = read( new Uint8Array( buffer ) ); - - if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { + texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; - return createRawTexture( container ); + texture.needsUpdate = true; + texture.colorSpace = parseColorSpace( container ); + texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); - } + return texture; - // - const taskConfig = config; - const texturePending = this.init().then( () => { + } - return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] ); + /** + * @param {ArrayBuffer} buffer + * @param {object?} config + * @return {Promise} + */ + async _createTexture( buffer, config = {} ) { - } ).then( ( e ) => this._createTextureFrom( e.data, container ) ); + const container = read( new Uint8Array( buffer ) ); - // Cache the task result. - _taskCache.set( buffer, { promise: texturePending } ); + if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { - return texturePending; + return createRawTexture( container ); } - dispose() { + // + const taskConfig = config; + const texturePending = this.init().then( () => { - this.workerPool.dispose(); - if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); + return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] ); - _activeLoaders --; + } ).then( ( e ) => this._createTextureFrom( e.data, container ) ); - return this; + // Cache the task result. + _taskCache.set( buffer, { promise: texturePending } ); - } + return texturePending; } + dispose() { - /* CONSTANTS */ - - KTX2Loader.BasisFormat = { - ETC1S: 0, - UASTC_4x4: 1, - }; - - KTX2Loader.TranscoderFormat = { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, - }; + this.workerPool.dispose(); + if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); - KTX2Loader.EngineFormat = { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, - }; + _activeLoaders --; + return this; - /* WEB WORKER */ - - KTX2Loader.BasisWorker = function () { - - let config; - let transcoderPending; - let BasisModule; - - const EngineFormat = _EngineFormat; // eslint-disable-line no-undef - const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef - const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + } - self.addEventListener( 'message', function ( e ) { +} - const message = e.data; - switch ( message.type ) { +/* CONSTANTS */ - case 'init': - config = message.config; - init( message.transcoderBinary ); - break; +KTX2Loader.BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, +}; - case 'transcode': - transcoderPending.then( () => { +KTX2Loader.TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, +}; - try { +KTX2Loader.EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, +}; - const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); - self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); +/* WEB WORKER */ - } catch ( error ) { +KTX2Loader.BasisWorker = function () { - console.error( error ); + let config; + let transcoderPending; + let BasisModule; - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef - } + self.addEventListener( 'message', function ( e ) { - } ); - break; + const message = e.data; - } + switch ( message.type ) { - } ); + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; - function init( wasmBinary ) { + case 'transcode': + transcoderPending.then( () => { - transcoderPending = new Promise( ( resolve ) => { + try { - BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; - BASIS( BasisModule ); // eslint-disable-line no-undef + const { faces, buffers, width, height, hasAlpha, format, dfdFlags } = transcode( message.buffer ); - } ).then( () => { + self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdFlags }, buffers ); - BasisModule.initializeBasis(); + } catch ( error ) { - if ( BasisModule.KTX2File === undefined ) { + console.error( error ); - console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); + self.postMessage( { type: 'error', id: message.id, error: error.message } ); - } + } - } ); + } ); + break; } - function transcode( buffer ) { + } ); - const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + function init( wasmBinary ) { - function cleanup() { + transcoderPending = new Promise( ( resolve ) => { - ktx2File.close(); - ktx2File.delete(); + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef - } + } ).then( () => { - if ( ! ktx2File.isValid() ) { + BasisModule.initializeBasis(); - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); + if ( BasisModule.KTX2File === undefined ) { - } + console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; - const width = ktx2File.getWidth(); - const height = ktx2File.getHeight(); - const layerCount = ktx2File.getLayers() || 1; - const levelCount = ktx2File.getLevels(); - const faceCount = ktx2File.getFaces(); - const hasAlpha = ktx2File.getHasAlpha(); - const dfdFlags = ktx2File.getDFDFlags(); + } - const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + } ); - if ( ! width || ! height || ! levelCount ) { + } - cleanup(); - throw new Error( 'THREE.KTX2Loader: Invalid texture' ); + function transcode( buffer ) { - } + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); - if ( ! ktx2File.startTranscoding() ) { + function cleanup() { - cleanup(); - throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); + ktx2File.close(); + ktx2File.delete(); - } + } - const faces = []; - const buffers = []; + if ( ! ktx2File.isValid() ) { - for ( let face = 0; face < faceCount; face ++ ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); - const mipmaps = []; + } - for ( let mip = 0; mip < levelCount; mip ++ ) { + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const layerCount = ktx2File.getLayers() || 1; + const levelCount = ktx2File.getLevels(); + const faceCount = ktx2File.getFaces(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdFlags = ktx2File.getDFDFlags(); - const layerMips = []; + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); - let mipWidth, mipHeight; + if ( ! width || ! height || ! levelCount ) { - for ( let layer = 0; layer < layerCount; layer ++ ) { + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid texture' ); - const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); + } - if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + if ( ! ktx2File.startTranscoding() ) { - console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + cleanup(); + throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); - } + } - if ( levelCount > 1 ) { + const faces = []; + const buffers = []; - mipWidth = levelInfo.origWidth; - mipHeight = levelInfo.origHeight; + for ( let face = 0; face < faceCount; face ++ ) { - } else { + const mipmaps = []; - // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with - // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. - // See mrdoob/three.js#25908. - mipWidth = levelInfo.width; - mipHeight = levelInfo.height; + for ( let mip = 0; mip < levelCount; mip ++ ) { - } + const layerMips = []; - const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); - const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + let mipWidth, mipHeight; - if ( ! status ) { + for ( let layer = 0; layer < layerCount; layer ++ ) { - cleanup(); - throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); - } + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { - layerMips.push( dst ); + console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); } - const mipData = concat( layerMips ); + if ( levelCount > 1 ) { - mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); - buffers.push( mipData.buffer ); + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; - } + } else { - faces.push( { mipmaps, width, height, format: engineFormat } ); + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; - } - - cleanup(); + } - return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); - } + if ( ! status ) { - // + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [ BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], - engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], - engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], - engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], - engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.ETC1 ], - engineFormat: [ EngineFormat.RGB_ETC1_Format ], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], - transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], - engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ]; - - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityETC1S - b.priorityETC1S; - - } ); - const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { - - return a.priorityUASTC - b.priorityUASTC; + } - } ); + layerMips.push( dst ); - function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + } - let transcoderFormat; - let engineFormat; + const mipData = concat( layerMips ); - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); + buffers.push( mipData.buffer ); - for ( let i = 0; i < options.length; i ++ ) { + } - const opt = options[ i ]; + faces.push( { mipmaps, width, height, format: engineFormat } ); - if ( ! config[ opt.if ] ) continue; - if ( ! opt.basisFormat.includes( basisFormat ) ) continue; - if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; - if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + } - transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; - engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + cleanup(); - return { transcoderFormat, engineFormat }; + return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdFlags }; - } + } - console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); + // - transcoderFormat = TranscoderFormat.RGBA32; - engineFormat = EngineFormat.RGBAFormat; + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityETC1S - b.priorityETC1S; + + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityUASTC - b.priorityUASTC; + + } ); + + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + + let transcoderFormat; + let engineFormat; + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + + for ( let i = 0; i < options.length; i ++ ) { + + const opt = options[ i ]; + + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; return { transcoderFormat, engineFormat }; } - function isPowerOfTwo( value ) { + console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); - if ( value <= 2 ) return true; + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; - return ( value & ( value - 1 ) ) === 0 && value !== 0; + return { transcoderFormat, engineFormat }; - } + } - /** Concatenates N byte arrays. */ - function concat( arrays ) { + function isPowerOfTwo( value ) { - if ( arrays.length === 1 ) return arrays[ 0 ]; + if ( value <= 2 ) return true; - let totalByteLength = 0; + return ( value & ( value - 1 ) ) === 0 && value !== 0; - for ( let i = 0; i < arrays.length; i ++ ) { + } - const array = arrays[ i ]; - totalByteLength += array.byteLength; + /** Concatenates N byte arrays. */ + function concat( arrays ) { - } + if ( arrays.length === 1 ) return arrays[ 0 ]; - const result = new Uint8Array( totalByteLength ); + let totalByteLength = 0; - let byteOffset = 0; + for ( let i = 0; i < arrays.length; i ++ ) { - for ( let i = 0; i < arrays.length; i ++ ) { + const array = arrays[ i ]; + totalByteLength += array.byteLength; - const array = arrays[ i ]; - result.set( array, byteOffset ); + } - byteOffset += array.byteLength; + const result = new Uint8Array( totalByteLength ); - } + let byteOffset = 0; + + for ( let i = 0; i < arrays.length; i ++ ) { + + const array = arrays[ i ]; + result.set( array, byteOffset ); - return result; + byteOffset += array.byteLength; } - }; + return result; - // - // Parsing for non-Basis textures. These textures are may have supercompression - // like Zstd, but they do not require transcoding. + } - const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] ); +}; - const FORMAT_MAP = { +// +// Parsing for non-Basis textures. These textures are may have supercompression +// like Zstd, but they do not require transcoding. - [ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat, - [ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat, - [ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat, - [ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat, +const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] ); - [ VK_FORMAT_R32G32_SFLOAT ]: RGFormat, - [ VK_FORMAT_R16G16_SFLOAT ]: RGFormat, - [ VK_FORMAT_R8G8_UNORM ]: RGFormat, - [ VK_FORMAT_R8G8_SRGB ]: RGFormat, +const FORMAT_MAP = { - [ VK_FORMAT_R32_SFLOAT ]: RedFormat, - [ VK_FORMAT_R16_SFLOAT ]: RedFormat, - [ VK_FORMAT_R8_SRGB ]: RedFormat, - [ VK_FORMAT_R8_UNORM ]: RedFormat, + [ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat, + [ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat, + [ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat, + [ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat, - [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format, - [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format, + [ VK_FORMAT_R32G32_SFLOAT ]: RGFormat, + [ VK_FORMAT_R16G16_SFLOAT ]: RGFormat, + [ VK_FORMAT_R8G8_UNORM ]: RGFormat, + [ VK_FORMAT_R8G8_SRGB ]: RGFormat, - }; + [ VK_FORMAT_R32_SFLOAT ]: RedFormat, + [ VK_FORMAT_R16_SFLOAT ]: RedFormat, + [ VK_FORMAT_R8_SRGB ]: RedFormat, + [ VK_FORMAT_R8_UNORM ]: RedFormat, - const TYPE_MAP = { + [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format, + [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format, - [ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType, - [ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType, - [ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType, - [ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType, +}; - [ VK_FORMAT_R32G32_SFLOAT ]: FloatType, - [ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType, - [ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType, - [ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType, +const TYPE_MAP = { - [ VK_FORMAT_R32_SFLOAT ]: FloatType, - [ VK_FORMAT_R16_SFLOAT ]: HalfFloatType, - [ VK_FORMAT_R8_SRGB ]: UnsignedByteType, - [ VK_FORMAT_R8_UNORM ]: UnsignedByteType, + [ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType, + [ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType, + [ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType, - [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType, - [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType, + [ VK_FORMAT_R32G32_SFLOAT ]: FloatType, + [ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType, + [ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType, - }; + [ VK_FORMAT_R32_SFLOAT ]: FloatType, + [ VK_FORMAT_R16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R8_SRGB ]: UnsignedByteType, + [ VK_FORMAT_R8_UNORM ]: UnsignedByteType, - async function createRawTexture( container ) { + [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType, + [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType, - const { vkFormat } = container; +}; - if ( FORMAT_MAP[ vkFormat ] === undefined ) { +async function createRawTexture( container ) { - throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' ); + const { vkFormat } = container; - } + if ( FORMAT_MAP[ vkFormat ] === undefined ) { - // + throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' ); - let zstd; + } - if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { + // - if ( ! _zstd ) { + let zstd; - _zstd = new Promise( async ( resolve ) => { + if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { - const zstd = new ZSTDDecoder(); - await zstd.init(); - resolve( zstd ); + if ( ! _zstd ) { - } ); + _zstd = new Promise( async ( resolve ) => { - } + const zstd = new ZSTDDecoder(); + await zstd.init(); + resolve( zstd ); - zstd = await _zstd; + } ); } - // + zstd = await _zstd; - const mipmaps = []; + } + // - for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) { + const mipmaps = []; - const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex ); - const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex ); - const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0; - const level = container.levels[ levelIndex ]; + for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) { - let levelData; + const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex ); + const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex ); + const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0; - if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) { + const level = container.levels[ levelIndex ]; - levelData = level.levelData; + let levelData; - } else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { + if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) { - levelData = zstd.decode( level.levelData, level.uncompressedByteLength ); + levelData = level.levelData; - } else { + } else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { - throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' ); + levelData = zstd.decode( level.levelData, level.uncompressedByteLength ); - } + } else { - let data; + throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' ); - if ( TYPE_MAP[ vkFormat ] === FloatType ) { + } - data = new Float32Array( + let data; - levelData.buffer, - levelData.byteOffset, - levelData.byteLength / Float32Array.BYTES_PER_ELEMENT + if ( TYPE_MAP[ vkFormat ] === FloatType ) { - ); + data = new Float32Array( - } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { + levelData.buffer, + levelData.byteOffset, + levelData.byteLength / Float32Array.BYTES_PER_ELEMENT - data = new Uint16Array( + ); - levelData.buffer, - levelData.byteOffset, - levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT + } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { - ); + data = new Uint16Array( - } else { + levelData.buffer, + levelData.byteOffset, + levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT - data = levelData; + ); - } + } else { - mipmaps.push( { + data = levelData; - data: data, - width: levelWidth, - height: levelHeight, - depth: levelDepth, + } - } ); + mipmaps.push( { - } + data: data, + width: levelWidth, + height: levelHeight, + depth: levelDepth, - let texture; + } ); - if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) { + } - texture = container.pixelDepth === 0 - ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) - : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); + let texture; - } else { + if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) { - if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' ); + texture = container.pixelDepth === 0 + ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) + : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); - texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight ); + } else { - } + if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' ); - texture.mipmaps = mipmaps; + texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight ); - texture.type = TYPE_MAP[ vkFormat ]; - texture.format = FORMAT_MAP[ vkFormat ]; - texture.colorSpace = parseColorSpace( container ); - texture.needsUpdate = true; + } - // + texture.mipmaps = mipmaps; - return Promise.resolve( texture ); + texture.type = TYPE_MAP[ vkFormat ]; + texture.format = FORMAT_MAP[ vkFormat ]; + texture.colorSpace = parseColorSpace( container ); + texture.needsUpdate = true; - } + // - function parseColorSpace( container ) { + return Promise.resolve( texture ); - const dfd = container.dataFormatDescriptor[ 0 ]; +} - if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_BT709 ) { +function parseColorSpace( container ) { - return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : LinearSRGBColorSpace; + const dfd = container.dataFormatDescriptor[ 0 ]; - } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_DISPLAYP3 ) { + if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_BT709 ) { - return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? DisplayP3ColorSpace : LinearDisplayP3ColorSpace; + return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : LinearSRGBColorSpace; - } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_UNSPECIFIED ) { + } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_DISPLAYP3 ) { - return NoColorSpace; + return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? DisplayP3ColorSpace : LinearDisplayP3ColorSpace; - } else { + } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_UNSPECIFIED ) { - console.warn( `THREE.KTX2Loader: Unsupported color primaries, "${ dfd.colorPrimaries }"` ); - return NoColorSpace; + return NoColorSpace; - } + } else { + + console.warn( `THREE.KTX2Loader: Unsupported color primaries, "${ dfd.colorPrimaries }"` ); + return NoColorSpace; } - return KTX2Loader; +} + +return KTX2Loader; } )(); diff --git a/examples/jsm/loaders/LogLuvLoader.js b/examples/jsm/loaders/LogLuvLoader.js index ddf6d3d737669f..089cd3668fcf3c 100644 --- a/examples/jsm/loaders/LogLuvLoader.js +++ b/examples/jsm/loaders/LogLuvLoader.js @@ -46,566 +46,566 @@ class LogLuvLoader extends DataTextureLoader { const UTIF = /* @__PURE__ */ ( () => { - const UTIF = {}; +const UTIF = {}; - UTIF.decode = function ( buff, prm ) { +UTIF.decode = function ( buff, prm ) { - if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug - var data = new Uint8Array( buff ), offset = 0; + if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug + var data = new Uint8Array( buff ), offset = 0; - var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; - var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; - bin.readUshort( data, offset ); offset += 2; + var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; + var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; + bin.readUshort( data, offset ); offset += 2; - var ifdo = bin.readUint( data, offset ); - var ifds = []; - while ( true ) { + var ifdo = bin.readUint( data, offset ); + var ifds = []; + while ( true ) { - var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { + var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { - console.log( 'error in TIFF' ); break; + console.log( 'error in TIFF' ); break; - } - - - UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); + } - ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); - if ( ifdo == 0 ) break; - } + UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); - return ifds; + ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); + if ( ifdo == 0 ) break; - }; + } - UTIF.decodeImage = function ( buff, img, ifds ) { + return ifds; - if ( img.data ) return; - var data = new Uint8Array( buff ); - var id = UTIF._binBE.readASCII( data, 0, 2 ); +}; - if ( img[ 't256' ] == null ) return; // No width => probably not an image - img.isLE = id == 'II'; - img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; - img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; +UTIF.decodeImage = function ( buff, img, ifds ) { - var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; - var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; - if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); - if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); + if ( img.data ) return; + var data = new Uint8Array( buff ); + var id = UTIF._binBE.readASCII( data, 0, 2 ); - var bipp; // bits per pixel - if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; - else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { + if ( img[ 't256' ] == null ) return; // No width => probably not an image + img.isLE = id == 'II'; + img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; + img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; - bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); + var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; + var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; + if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); + if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); - } + var bipp; // bits per pixel + if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; + else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { - var bipl = Math.ceil( img.width * bipp / 8 ) * 8; - var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; - var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; + bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); - if ( img[ 't322' ] != null ) { + } - var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; - var tx = Math.floor( ( img.width + tw - 1 ) / tw ); - var ty = Math.floor( ( img.height + th - 1 ) / th ); - var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); - for ( var y = 0; y < ty; y ++ ) - for ( var x = 0; x < tx; x ++ ) { + var bipl = Math.ceil( img.width * bipp / 8 ) * 8; + var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; + var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; - var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); - // Might be required for 7 too. Need to check - if ( cmpr == 6 ) bytes = tbuff; - else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); + if ( img[ 't322' ] != null ) { - } + var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; + var tx = Math.floor( ( img.width + tw - 1 ) / tw ); + var ty = Math.floor( ( img.height + th - 1 ) / th ); + var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); + for ( var y = 0; y < ty; y ++ ) + for ( var x = 0; x < tx; x ++ ) { - bilen = bytes.length * 8; + var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); + // Might be required for 7 too. Need to check + if ( cmpr == 6 ) bytes = tbuff; + else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); - } else { + } - var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); - for ( var i = 0; i < soff.length; i ++ ) { + bilen = bytes.length * 8; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); - bilen += bipl * rps; + } else { - } + var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); + for ( var i = 0; i < soff.length; i ++ ) { - bilen = Math.min( bilen, bytes.length * 8 ); + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); + bilen += bipl * rps; } - img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); + bilen = Math.min( bilen, bytes.length * 8 ); - }; + } - UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { + img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); - //console.log("compression", cmpr); - //var time = Date.now(); - if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); - else console.log( 'Unsupported compression', cmpr ); +}; - //console.log(Date.now()-time); +UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { - var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); - var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); + //console.log("compression", cmpr); + //var time = Date.now(); + if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); + else console.log( 'Unsupported compression', cmpr ); - // convert to Little Endian /* - if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG - for ( var y = 0; y < h; y ++ ) { + //console.log(Date.now()-time); - //console.log("fixing endianity"); - var roff = toff + y * bpl; - for ( var x = 1; x < bpl; x += 2 ) { + var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); + var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); - var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; + // convert to Little Endian /* + if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG + for ( var y = 0; y < h; y ++ ) { - } + //console.log("fixing endianity"); + var roff = toff + y * bpl; + for ( var x = 1; x < bpl; x += 2 ) { - } //*/ + var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; - if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { + } - for ( var y = 0; y < h; y ++ ) { + } //*/ - var ntoff = toff + y * bpl; - if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { + if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { - var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); - tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; + for ( var y = 0; y < h; y ++ ) { - } - else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + var ntoff = toff + y * bpl; + if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { - tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; - tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; - tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); + tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; - } - else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; + } + else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + + tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; + tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; + tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; } + else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; } - }; + } - UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { +}; - var w = img.width, qw = w * 4; - var io = 0, out = new Uint8Array( qw ); +UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { - while ( io < len ) { + var w = img.width, qw = w * 4; + var io = 0, out = new Uint8Array( qw ); - var oo = 0; - while ( oo < qw ) { + while ( io < len ) { - var c = data[ off + io ]; io ++; - if ( c < 128 ) { + var oo = 0; + while ( oo < qw ) { - for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; + var c = data[ off + io ]; io ++; + if ( c < 128 ) { - } else { + for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; - c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; + } else { - } + c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; } - for ( var x = 0; x < w; x ++ ) { + } - tgt[ toff + 0 ] = out[ x ]; - tgt[ toff + 1 ] = out[ x + w ]; - tgt[ toff + 2 ] = out[ x + w * 2 ]; - tgt[ toff + 4 ] = out[ x + w * 3 ]; - toff += 6; + for ( var x = 0; x < w; x ++ ) { - } + tgt[ toff + 0 ] = out[ x ]; + tgt[ toff + 1 ] = out[ x + w ]; + tgt[ toff + 2 ] = out[ x + w * 2 ]; + tgt[ toff + 4 ] = out[ x + w * 3 ]; + toff += 6; } - }; + } - UTIF.tags = {}; - //UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; - // start at tag 250 - UTIF._types = function () { +}; - var main = new Array( 250 ); main.fill( 0 ); - main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); - var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; - return { - basic: { - main: main, - rest: rest - }, - gps: { - main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], - rest: { 18: 2, 29: 2 } - } - }; +UTIF.tags = {}; +//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; +// start at tag 250 +UTIF._types = function () { - }(); + var main = new Array( 250 ); main.fill( 0 ); + main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); + var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; + return { + basic: { + main: main, + rest: rest + }, + gps: { + main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], + rest: { 18: 2, 29: 2 } + } + }; - UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { +}(); - var cnt = bin.readUshort( data, offset ); offset += 2; - var ifd = {}; +UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { - if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); - for ( var i = 0; i < cnt; i ++ ) { + var cnt = bin.readUshort( data, offset ); offset += 2; + var ifd = {}; - var tag = bin.readUshort( data, offset ); offset += 2; - var type = bin.readUshort( data, offset ); offset += 2; - var num = bin.readUint( data, offset ); offset += 4; - var voff = bin.readUint( data, offset ); offset += 4; + if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); + for ( var i = 0; i < cnt; i ++ ) { - var arr = []; - //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; - if ( type == 1 || type == 7 ) { + var tag = bin.readUshort( data, offset ); offset += 2; + var type = bin.readUshort( data, offset ); offset += 2; + var num = bin.readUint( data, offset ); offset += 4; + var voff = bin.readUint( data, offset ); offset += 4; - arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if ( type == 1 || type == 7 ) { - } + arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); - if ( type == 2 ) { + } - var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); - if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); - else arr = new Uint8Array( data.buffer, o0, len ); + if ( type == 2 ) { - } + var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); + if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); + else arr = new Uint8Array( data.buffer, o0, len ); - if ( type == 3 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + if ( type == 3 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - if ( type == 4 - || type == 13 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + if ( type == 4 + || type == 13 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - if ( type == 5 || type == 10 ) { + } - var ri = type == 5 ? bin.readUint : bin.readInt; - for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); + if ( type == 5 || type == 10 ) { - } + var ri = type == 5 ? bin.readUint : bin.readInt; + for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); - if ( type == 8 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + if ( type == 8 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - if ( type == 9 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + if ( type == 9 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - if ( type == 11 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); + if ( type == 11 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); - if ( type == 12 ) { + } - for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); + if ( type == 12 ) { - } + for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); - if ( num != 0 && arr.length == 0 ) { + } - console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; + if ( num != 0 && arr.length == 0 ) { - } + console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; - if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); + } - ifd[ 't' + tag ] = arr; + if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); - if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { + ifd[ 't' + tag ] = arr; - var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; - var subfd = []; - for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); - if ( tag == 330 ) ifd.subIFD = subfd; - if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; - if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } - if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; - if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; + if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { - } + var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; + var subfd = []; + for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); + if ( tag == 330 ) ifd.subIFD = subfd; + if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; + if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } + if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; + if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; - if ( tag == 37500 && prm.parseMN ) { + } - var mn = arr; - //console.log(bin.readASCII(mn,0,mn.length), mn); - if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; - else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { + if ( tag == 37500 && prm.parseMN ) { - var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); - ifd.makerNote = subsub[ 0 ]; + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; + else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { - } + var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); + ifd.makerNote = subsub[ 0 ]; } } - ifds.push( ifd ); - if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); - return offset; + } - }; + ifds.push( ifd ); + if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); + return offset; - UTIF.toRGBA = function ( out, type ) { +}; - const w = out.width, h = out.height, area = w * h, data = out.data; +UTIF.toRGBA = function ( out, type ) { - let img; + const w = out.width, h = out.height, area = w * h, data = out.data; - switch ( type ) { + let img; - case HalfFloatType: + switch ( type ) { - img = new Uint16Array( area * 4 ); - break; + case HalfFloatType: - case FloatType: + img = new Uint16Array( area * 4 ); + break; - img = new Float32Array( area * 4 ); - break; + case FloatType: - default: - throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); + img = new Float32Array( area * 4 ); + break; - } + default: + throw new Error( 'THREE.LogLuvLoader: Unsupported texture data type: ' + type ); - let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; - const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; + } - if ( out[ 't262' ] == null && bps == 1 ) intp = 0; + let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; + const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; - if ( intp == 32845 ) { + if ( out[ 't262' ] == null && bps == 1 ) intp = 0; - for ( let y = 0; y < h; y ++ ) { + if ( intp == 32845 ) { - for ( let x = 0; x < w; x ++ ) { + for ( let y = 0; y < h; y ++ ) { - const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; - let L = ( data[ si + 1 ] << 8 ) | data[ si ]; + for ( let x = 0; x < w; x ++ ) { - L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); - const u = ( data[ si + 3 ] + 0.5 ) / 410; - const v = ( data[ si + 5 ] + 0.5 ) / 410; + const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; + let L = ( data[ si + 1 ] << 8 ) | data[ si ]; - // Luv to xyY - const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); - const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); - const bY = L; + L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); + const u = ( data[ si + 3 ] + 0.5 ) / 410; + const v = ( data[ si + 5 ] + 0.5 ) / 410; - // xyY to XYZ - const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; + // Luv to xyY + const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); + const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); + const bY = L; - // XYZ to linear RGB - const r = 2.690 * X - 1.276 * Y - 0.414 * Z; - const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; - const b = 0.061 * X - 0.224 * Y + 1.163 * Z; + // xyY to XYZ + const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; - if ( type === HalfFloatType ) { + // XYZ to linear RGB + const r = 2.690 * X - 1.276 * Y - 0.414 * Z; + const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; + const b = 0.061 * X - 0.224 * Y + 1.163 * Z; - img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); - img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); - img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); - img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); + if ( type === HalfFloatType ) { + img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); + img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); + img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); + img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); - } else { - img[ qi ] = r; - img[ qi + 1 ] = g; - img[ qi + 2 ] = b; - img[ qi + 3 ] = 1; + } else { - } + img[ qi ] = r; + img[ qi + 1 ] = g; + img[ qi + 2 ] = b; + img[ qi + 3 ] = 1; } } - } else { + } - throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); + } else { - } + throw new Error( 'THREE.LogLuvLoader: Unsupported Photometric interpretation: ' + intp ); - return img; + } - }; + return img; - UTIF._binBE = - { - nextZero: function ( data, o ) { +}; - while ( data[ o ] != 0 ) o ++; return o; +UTIF._binBE = +{ + nextZero: function ( data, o ) { - }, - readUshort: function ( buff, p ) { + while ( data[ o ] != 0 ) o ++; return o; - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + }, + readUshort: function ( buff, p ) { - }, - readShort: function ( buff, p ) { + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; + }, + readShort: function ( buff, p ) { - }, - readInt: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; + }, + readInt: function ( buff, p ) { - }, - readUint: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; + }, + readUint: function ( buff, p ) { - }, - readASCII: function ( buff, p, l ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + }, + readASCII: function ( buff, p, l ) { - }, - readFloat: function ( buff, p ) { + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; + }, + readFloat: function ( buff, p ) { - }, - readDouble: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; + }, + readDouble: function ( buff, p ) { - }, + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; - writeUshort: function ( buff, p, n ) { + }, - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + writeUshort: function ( buff, p, n ) { - }, - writeInt: function ( buff, p, n ) { + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; + }, + writeInt: function ( buff, p, n ) { - }, - writeUint: function ( buff, p, n ) { + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; + }, + writeUint: function ( buff, p, n ) { - }, - writeASCII: function ( buff, p, s ) { + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; - for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); + }, + writeASCII: function ( buff, p, s ) { - }, - writeDouble: function ( buff, p, n ) { + for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); - UTIF._binBE.fl64[ 0 ] = n; - for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; + }, + writeDouble: function ( buff, p, n ) { - } - }; - UTIF._binBE.ui8 = new Uint8Array( 8 ); - UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); - UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); + UTIF._binBE.fl64[ 0 ] = n; + for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; - UTIF._binLE = - { - nextZero: UTIF._binBE.nextZero, - readUshort: function ( buff, p ) { + } +}; +UTIF._binBE.ui8 = new Uint8Array( 8 ); +UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); - return ( buff[ p + 1 ] << 8 ) | buff[ p ]; +UTIF._binLE = +{ + nextZero: UTIF._binBE.nextZero, + readUshort: function ( buff, p ) { - }, - readShort: function ( buff, p ) { + return ( buff[ p + 1 ] << 8 ) | buff[ p ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; + }, + readShort: function ( buff, p ) { - }, - readInt: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; + }, + readInt: function ( buff, p ) { - }, - readUint: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; + }, + readUint: function ( buff, p ) { - }, - readASCII: UTIF._binBE.readASCII, - readFloat: function ( buff, p ) { + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; + }, + readASCII: UTIF._binBE.readASCII, + readFloat: function ( buff, p ) { - }, - readDouble: function ( buff, p ) { + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; + }, + readDouble: function ( buff, p ) { - }, + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; - writeUshort: function ( buff, p, n ) { + }, - buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; + writeUshort: function ( buff, p, n ) { - }, - writeInt: function ( buff, p, n ) { + buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; + }, + writeInt: function ( buff, p, n ) { - }, - writeUint: function ( buff, p, n ) { + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; - buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; + }, + writeUint: function ( buff, p, n ) { - }, - writeASCII: UTIF._binBE.writeASCII - }; - UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { + buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min( tw, w - xoff ); - var ylim = Math.min( th, h - yoff ); - for ( var y = 0; y < ylim; y ++ ) { + }, + writeASCII: UTIF._binBE.writeASCII +}; +UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { - var tof = ( yoff + y ) * w + xoff; - var sof = y * tw; - for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min( tw, w - xoff ); + var ylim = Math.min( th, h - yoff ); + for ( var y = 0; y < ylim; y ++ ) { - } + var tof = ( yoff + y ) * w + xoff; + var sof = y * tw; + for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; - }; + } + +}; - return UTIF; +return UTIF; } )(); diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index c4928ef9289d7c..6704a26d306c55 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -129,942 +129,942 @@ class RGBMLoader extends DataTextureLoader { const UPNG = /* @__PURE__ */ ( () => { - var UPNG = {}; +var UPNG = {}; - UPNG.toRGBA8 = function ( out ) { +UPNG.toRGBA8 = function ( out ) { - var w = out.width, h = out.height; - if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; + var w = out.width, h = out.height; + if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; - var frms = []; - if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; + var frms = []; + if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; - var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); - for ( var i = 0; i < out.frames.length; i ++ ) { + var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); + for ( var i = 0; i < out.frames.length; i ++ ) { - var frm = out.frames[ i ]; - var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; - var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); + var frm = out.frames[ i ]; + var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; + var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); - if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; + if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; - if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); + if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); - frms.push( img.buffer.slice( 0 ) ); + frms.push( img.buffer.slice( 0 ) ); - if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); - else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; + if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; - } - - return frms; + } - }; + return frms; - UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { +}; - var area = w * h, bpp = UPNG.decode._getBPP( out ); - var bpl = Math.ceil( w * bpp / 8 ); // bytes per line +UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { - var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); - var ctype = out.ctype, depth = out.depth; - var rs = UPNG._bin.readUshort; + var area = w * h, bpp = UPNG.decode._getBPP( out ); + var bpl = Math.ceil( w * bpp / 8 ); // bytes per line - if ( ctype == 6 ) { // RGB + alpha + var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); + var ctype = out.ctype, depth = out.depth; + var rs = UPNG._bin.readUshort; - var qarea = area << 2; - if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { + if ( ctype == 6 ) { // RGB + alpha - bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; + var qarea = area << 2; + if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { - } + bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; - if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { + } - bf[ i ] = data[ i << 1 ]; + if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { - } + bf[ i ] = data[ i << 1 ]; - } else if ( ctype == 2 ) { // RGB + } - var ts = out.tabs[ 'tRNS' ]; - if ( ts == null ) { + } else if ( ctype == 2 ) { // RGB - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + var ts = out.tabs[ 'tRNS' ]; + if ( ts == null ) { - var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + } - var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - } + var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - } else { + } - var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + } else { - var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; - if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; + var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + } - var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; - if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; } - } else if ( ctype == 3 ) { // palette + } - var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; - //console.log(p, ap); - if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { + } else if ( ctype == 3 ) { // palette - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; + //console.log(p, ap); + if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - } + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; } - if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { + } - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - } + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; } - if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { + } - var s0 = y * bpl, t0 = y * w; - for ( var i = 0; i < w; i ++ ) { + if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { - var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { - } + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; } - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + } - var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; - } else if ( ctype == 4 ) { // gray + alpha + } - if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + } else if ( ctype == 4 ) { // gray + alpha - var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; - if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + } - var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { - } + var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; - } else if ( ctype == 0 ) { // gray + } - var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; - for ( var y = 0; y < h; y ++ ) { + } else if ( ctype == 0 ) { // gray - var off = y * bpl, to = y * w; - if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { + var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; + for ( var y = 0; y < h; y ++ ) { - var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + var off = y * bpl, to = y * w; + if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { + var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { + var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { + var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { - } - else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { + var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; - var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + } + else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { - } + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; } } - //console.log(Date.now()-time); - return bf; - - }; + } + //console.log(Date.now()-time); + return bf; +}; - UPNG.decode = function ( buff ) { - var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; - var out = { tabs: {}, frames: [] }; - var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it - var fd, foff = 0; // frames - var text, keyw, bfr; - var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; - for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); +UPNG.decode = function ( buff ) { - while ( offset < data.length ) { + var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; + var out = { tabs: {}, frames: [] }; + var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it + var fd, foff = 0; // frames + var text, keyw, bfr; - var len = bin.readUint( data, offset ); offset += 4; - var type = bin.readASCII( data, offset, 4 ); offset += 4; - //console.log(type,len); + var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; + for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); - if ( type == 'IHDR' ) { + while ( offset < data.length ) { - UPNG.decode._IHDR( data, offset, out ); + var len = bin.readUint( data, offset ); offset += 4; + var type = bin.readASCII( data, offset, 4 ); offset += 4; + //console.log(type,len); - } else if ( type == 'CgBI' ) { + if ( type == 'IHDR' ) { - out.tabs[ type ] = data.slice( offset, offset + 4 ); + UPNG.decode._IHDR( data, offset, out ); - } else if ( type == 'IDAT' ) { + } else if ( type == 'CgBI' ) { - for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; - doff += len; + out.tabs[ type ] = data.slice( offset, offset + 4 ); - } else if ( type == 'acTL' ) { + } else if ( type == 'IDAT' ) { - out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; - fd = new Uint8Array( data.length ); + for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; + doff += len; - } else if ( type == 'fcTL' ) { + } else if ( type == 'acTL' ) { - if ( foff != 0 ) { + out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; + fd = new Uint8Array( data.length ); - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + } else if ( type == 'fcTL' ) { - } + if ( foff != 0 ) { - var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; - var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); - var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; - //console.log(frm); - out.frames.push( frm ); + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; - } else if ( type == 'fdAT' ) { + } - for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; - foff += len - 4; + var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; + var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); + var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; + //console.log(frm); + out.frames.push( frm ); - } else if ( type == 'pHYs' ) { + } else if ( type == 'fdAT' ) { - out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; + for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; + foff += len - 4; - } else if ( type == 'cHRM' ) { + } else if ( type == 'pHYs' ) { - out.tabs[ type ] = []; - for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); + out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; - } else if ( type == 'tEXt' || type == 'zTXt' ) { + } else if ( type == 'cHRM' ) { - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = bin.nextZero( data, offset ); - keyw = bin.readASCII( data, offset, nz - offset ); - var tl = offset + len - nz - 1; - if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); - else { + out.tabs[ type ] = []; + for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); - bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); + } else if ( type == 'tEXt' || type == 'zTXt' ) { - } + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = bin.nextZero( data, offset ); + keyw = bin.readASCII( data, offset, nz - offset ); + var tl = offset + len - nz - 1; + if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); + else { - out.tabs[ type ][ keyw ] = text; + bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); - } else if ( type == 'iTXt' ) { + } - if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; - var nz = 0, off = offset; - nz = bin.nextZero( data, off ); - keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; - var cflag = data[ off ]; off += 2; - nz = bin.nextZero( data, off ); - bin.readASCII( data, off, nz - off ); off = nz + 1; - nz = bin.nextZero( data, off ); - bin.readUTF8( data, off, nz - off ); off = nz + 1; - var tl = len - ( off - offset ); - if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); - else { + out.tabs[ type ][ keyw ] = text; - bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); - text = bin.readUTF8( bfr, 0, bfr.length ); + } else if ( type == 'iTXt' ) { - } + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = 0, off = offset; + nz = bin.nextZero( data, off ); + keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; + var cflag = data[ off ]; off += 2; + nz = bin.nextZero( data, off ); + bin.readASCII( data, off, nz - off ); off = nz + 1; + nz = bin.nextZero( data, off ); + bin.readUTF8( data, off, nz - off ); off = nz + 1; + var tl = len - ( off - offset ); + if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); + else { - out.tabs[ type ][ keyw ] = text; + bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); - } else if ( type == 'PLTE' ) { + } - out.tabs[ type ] = bin.readBytes( data, offset, len ); + out.tabs[ type ][ keyw ] = text; - } else if ( type == 'hIST' ) { + } else if ( type == 'PLTE' ) { - var pl = out.tabs[ 'PLTE' ].length / 3; - out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); + out.tabs[ type ] = bin.readBytes( data, offset, len ); - } else if ( type == 'tRNS' ) { + } else if ( type == 'hIST' ) { - if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); - else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); - else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - //else console.log("tRNS for unsupported color type",out.ctype, len); + var pl = out.tabs[ 'PLTE' ].length / 3; + out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); - } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; - else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; - else if ( type == 'bKGD' ) { + } else if ( type == 'tRNS' ) { - if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; - else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; - else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; + if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); + else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); + else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + //else console.log("tRNS for unsupported color type",out.ctype, len); - } else if ( type == 'IEND' ) { + } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; + else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; + else if ( type == 'bKGD' ) { - break; + if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; + else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; - } + } else if ( type == 'IEND' ) { - //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } - offset += len; - bin.readUint( data, offset ); offset += 4; + break; } - if ( foff != 0 ) { + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len; + bin.readUint( data, offset ); offset += 4; - var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); + } - } + if ( foff != 0 ) { - out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); - delete out.compress; delete out.interlace; delete out.filter; - return out; + } - }; + out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); - UPNG.decode._decompress = function ( out, dd, w, h ) { + delete out.compress; delete out.interlace; delete out.filter; + return out; - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); - if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); - else dd = UPNG.decode._inflate( dd, buff ); +}; - if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); - else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); +UPNG.decode._decompress = function ( out, dd, w, h ) { - return dd; + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); + if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); + else dd = UPNG.decode._inflate( dd, buff ); - }; + if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); + else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); - UPNG.decode._inflate = function ( data, buff ) { + return dd; - var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; +}; - }; +UPNG.decode._inflate = function ( data, buff ) { - UPNG.inflateRaw = function () { + var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; - var H = {}; H.H = {}; H.H.N = function ( N, W ) { +}; - var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; - if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; - if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { +UPNG.inflateRaw = function () { - i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { + var H = {}; H.H = {}; H.H.N = function ( N, W ) { - if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); - var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; - w += q; continue - ; + var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; + if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; + if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { - } + i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { - if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { + if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); + var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; + w += q; continue + ; - v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; + } - } + if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { - if ( m == 2 ) { + v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; - J = A( N, d, 5 ) + 257; - h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { + } - b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; + if ( m == 2 ) { - } + J = A( N, d, 5 ) + 257; + h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { - for ( var c = 0; - c < Q; c ++ ) { + b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; - var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K - ; + } - } + for ( var c = 0; + c < Q; c ++ ) { - d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; - d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); - I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K ; } - while ( ! 0 ) { + d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; + d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); + I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + ; - var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { + } - W[ w ++ ] = p; + while ( ! 0 ) { - } else if ( p == 256 ) { + var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { - break; + W[ w ++ ] = p; - } else { + } else if ( p == 256 ) { - var z = w + p - 254; - if ( p > 264 ) { + break; - var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; + } else { - } + var z = w + p - 254; + if ( p > 264 ) { - var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); - d += Y & 15; while ( w < z ) { + var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; - W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; + } - } + var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); + d += Y & 15; while ( w < z ) { - w = z - ; + W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; } + w = z + ; + } } - return W.length == w ? W : W.slice( 0, w ) - ; + } - }; + return W.length == w ? W : W.slice( 0, w ) + ; - H.H.W = function ( N, W ) { + }; - var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; + H.H.W = function ( N, W ) { - }; + var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; - H.H.R = function ( N, W, R, V, n, A ) { + }; - var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { + H.H.R = function ( N, W, R, V, n, A ) { - var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; - if ( b <= 15 ) { + var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { - A[ I ] = b; I ++; + var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; + if ( b <= 15 ) { - } else { + A[ I ] = b; I ++; - var Z = 0, m = 0; if ( b == 16 ) { + } else { - m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; + var Z = 0, m = 0; if ( b == 16 ) { - } else if ( b == 17 ) { + m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; - m = 3 + l( V, n, 3 ); - n += 3 - ; + } else if ( b == 17 ) { - } else if ( b == 18 ) { + m = 3 + l( V, n, 3 ); + n += 3 + ; - m = 11 + l( V, n, 7 ); n += 7; + } else if ( b == 18 ) { - } + m = 11 + l( V, n, 7 ); n += 7; - var J = I + m; while ( I < J ) { + } - A[ I ] = Z; I ++; + var J = I + m; while ( I < J ) { - } + A[ I ] = Z; I ++; } } - return n - ; + } - }; + return n + ; - H.H.V = function ( N, W, R, V ) { + }; - var n = 0, A = 0, l = V.length >>> 1; - while ( A < R ) { + H.H.V = function ( N, W, R, V ) { - var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; + var n = 0, A = 0, l = V.length >>> 1; + while ( A < R ) { - } + var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; - while ( A < l ) { + } - V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + while ( A < l ) { - } + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; - return n - ; + } - }; + return n + ; - H.H.n = function ( N, W ) { + }; - var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; - var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { + H.H.n = function ( N, W ) { - n = n + e[ A - 1 ] << 1; b[ A ] = n; + var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; + var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { - } + n = n + e[ A - 1 ] << 1; b[ A ] = n; - for ( l = 0; l < V; l += 2 ) { + } - I = N[ l + 1 ]; if ( I != 0 ) { + for ( l = 0; l < V; l += 2 ) { - N[ l ] = b[ I ]; - b[ I ] ++ - ; + I = N[ l + 1 ]; if ( I != 0 ) { - } + N[ l ] = b[ I ]; + b[ I ] ++ + ; } - }; + } - H.H.A = function ( N, W, R ) { + }; - var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { + H.H.A = function ( N, W, R ) { - var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); - while ( Z != m ) { + var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { - var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; + var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); + while ( Z != m ) { - } + var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; } - }; - - H.H.l = function ( N, W ) { + } - var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; - n += 2 ) { + }; - var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; + H.H.l = function ( N, W ) { - } + var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; + n += 2 ) { - }; + var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; - H.H.M = function ( N, W, R ) { + } - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + }; - }; + H.H.M = function ( N, W, R ) { - H.H.I = function ( N, W, R ) { + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; - R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; + }; - }; + H.H.I = function ( N, W, R ) { - H.H.e = function ( N, W, R ) { + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + }; - }; + H.H.e = function ( N, W, R ) { - H.H.b = function ( N, W, R ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + }; - }; + H.H.b = function ( N, W, R ) { - H.H.Z = function ( N, W ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + }; - }; + H.H.Z = function ( N, W ) { - H.H.i = function ( N, W ) { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); - return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); + }; - }; + H.H.i = function ( N, W ) { - H.H.m = function () { + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); - var N = Uint16Array, W = Uint32Array; - return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } - ; + }; - }(); - ( function () { + H.H.m = function () { - var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { + var N = Uint16Array, W = Uint32Array; + return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } + ; - var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; - V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; - N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 - ; + }(); + ( function () { - } + var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { - function n( A, l, M ) { + var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; + V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; + N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 + ; - while ( l -- != 0 )A.push( 0, M ) - ; + } - } + function n( A, l, M ) { - for ( var R = 0; R < 32; R ++ ) { + while ( l -- != 0 )A.push( 0, M ) + ; - N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; - N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] - ; + } - } + for ( var R = 0; R < 32; R ++ ) { - n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); - H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); - n( N.D, 30, 0 ); n( N.v, 320, 0 ) + N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; + N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] ; - }() ); + } - return H.H.N + n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); + H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); + n( N.D, 30, 0 ); n( N.v, 320, 0 ) ; - }(); + }() ); + return H.H.N + ; - UPNG.decode._readInterlace = function ( data, out ) { +}(); - var w = out.width, h = out.height; - var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); - var img = new Uint8Array( h * bpl ); - var di = 0; - var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; - var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; - var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; - var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; +UPNG.decode._readInterlace = function ( data, out ) { - var pass = 0; - while ( pass < 7 ) { + var w = out.width, h = out.height; + var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); + var img = new Uint8Array( h * bpl ); + var di = 0; - var ri = row_increment[ pass ], ci = col_increment[ pass ]; - var sw = 0, sh = 0; - var cr = starting_row[ pass ]; while ( cr < h ) { + var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; + var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; + var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; + var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; - cr += ri; sh ++; + var pass = 0; + while ( pass < 7 ) { - } + var ri = row_increment[ pass ], ci = col_increment[ pass ]; + var sw = 0, sh = 0; + var cr = starting_row[ pass ]; while ( cr < h ) { - var cc = starting_col[ pass ]; while ( cc < w ) { + cr += ri; sh ++; - cc += ci; sw ++; + } - } + var cc = starting_col[ pass ]; while ( cc < w ) { - var bpll = Math.ceil( sw * bpp / 8 ); - UPNG.decode._filterZero( data, out, di, sw, sh ); + cc += ci; sw ++; - var y = 0, row = starting_row[ pass ]; - var val; + } - while ( row < h ) { + var bpll = Math.ceil( sw * bpp / 8 ); + UPNG.decode._filterZero( data, out, di, sw, sh ); - var col = starting_col[ pass ]; - var cdi = ( di + y * bpll ) << 3; + var y = 0, row = starting_row[ pass ]; + var val; - while ( col < w ) { + while ( row < h ) { - if ( bpp == 1 ) { + var col = starting_col[ pass ]; + var cdi = ( di + y * bpll ) << 3; - val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; - img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); + while ( col < w ) { - } + if ( bpp == 1 ) { - if ( bpp == 2 ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; + img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); - val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; - img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); + } - } + if ( bpp == 2 ) { - if ( bpp == 4 ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; + img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); - val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; - img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); + } - } + if ( bpp == 4 ) { - if ( bpp >= 8 ) { + val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; + img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); - var ii = row * bpl + col * cbpp; - for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; + } - } + if ( bpp >= 8 ) { - cdi += bpp; col += ci; + var ii = row * bpl + col * cbpp; + for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; } - y ++; row += ri; + cdi += bpp; col += ci; } - if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); - pass = pass + 1; + y ++; row += ri; } - return img; + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; - }; + } - UPNG.decode._getBPP = function ( out ) { + return img; - var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; - return noc * out.depth; +}; - }; +UPNG.decode._getBPP = function ( out ) { - UPNG.decode._filterZero = function ( data, out, off, w, h ) { + var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; + return noc * out.depth; - var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; - bpp = Math.ceil( bpp / 8 ); +}; - var i, di, type = data[ off ], x = 0; +UPNG.decode._filterZero = function ( data, out, off, w, h ) { - if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; - if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; + bpp = Math.ceil( bpp / 8 ); - for ( var y = 0; y < h; y ++ ) { + var i, di, type = data[ off ], x = 0; - i = off + y * bpl; di = i + y + 1; - type = data[ di - 1 ]; x = 0; + if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; + if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; - if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; - else if ( type == 1 ) { + for ( var y = 0; y < h; y ++ ) { - for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); + i = off + y * bpl; di = i + y + 1; + type = data[ di - 1 ]; x = 0; - } else if ( type == 2 ) { + if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; + else if ( type == 1 ) { - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); + for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); - } else if ( type == 3 ) { + } else if ( type == 2 ) { - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); - } else { + } else if ( type == 3 ) { - for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); - for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); - } + } else { - } + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); - return data; + } - }; + } - UPNG.decode._paeth = function ( a, b, c ) { + return data; - var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); - if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; - else if ( pb * pb <= pc * pc ) return b; - return c; +}; - }; +UPNG.decode._paeth = function ( a, b, c ) { - UPNG.decode._IHDR = function ( data, offset, out ) { + var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); + if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; + else if ( pb * pb <= pc * pc ) return b; + return c; - var bin = UPNG._bin; - out.width = bin.readUint( data, offset ); offset += 4; - out.height = bin.readUint( data, offset ); offset += 4; - out.depth = data[ offset ]; offset ++; - out.ctype = data[ offset ]; offset ++; - out.compress = data[ offset ]; offset ++; - out.filter = data[ offset ]; offset ++; - out.interlace = data[ offset ]; offset ++; +}; - }; +UPNG.decode._IHDR = function ( data, offset, out ) { - UPNG._bin = { - nextZero: function ( data, p ) { + var bin = UPNG._bin; + out.width = bin.readUint( data, offset ); offset += 4; + out.height = bin.readUint( data, offset ); offset += 4; + out.depth = data[ offset ]; offset ++; + out.ctype = data[ offset ]; offset ++; + out.compress = data[ offset ]; offset ++; + out.filter = data[ offset ]; offset ++; + out.interlace = data[ offset ]; offset ++; - while ( data[ p ] != 0 ) p ++; return p; +}; - }, - readUshort: function ( buff, p ) { +UPNG._bin = { + nextZero: function ( data, p ) { - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + while ( data[ p ] != 0 ) p ++; return p; - }, - writeUshort: function ( buff, p, n ) { + }, + readUshort: function ( buff, p ) { - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - }, - readUint: function ( buff, p ) { + }, + writeUshort: function ( buff, p, n ) { - return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - }, - writeUint: function ( buff, p, n ) { + }, + readUint: function ( buff, p ) { - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; + return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); - }, - readASCII: function ( buff, p, l ) { + }, + writeUint: function ( buff, p, n ) { - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; - }, - writeASCII: function ( data, p, s ) { + }, + readASCII: function ( buff, p, l ) { - for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - }, - readBytes: function ( buff, p, l ) { + }, + writeASCII: function ( data, p, s ) { - var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; + for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); - }, - pad: function ( n ) { + }, + readBytes: function ( buff, p, l ) { - return n.length < 2 ? '0' + n : n; + var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; - }, - readUTF8: function ( buff, p, l ) { + }, + pad: function ( n ) { - var s = '', ns; - for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); - try { + return n.length < 2 ? '0' + n : n; - ns = decodeURIComponent( s ); + }, + readUTF8: function ( buff, p, l ) { - } catch ( e ) { + var s = '', ns; + for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); + try { - return UPNG._bin.readASCII( buff, p, l ); + ns = decodeURIComponent( s ); - } + } catch ( e ) { - return ns; + return UPNG._bin.readASCII( buff, p, l ); } - }; - UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - var w = Math.min( sw, tw ), h = Math.min( sh, th ); - var si = 0, ti = 0; - for ( var y = 0; y < h; y ++ ) - for ( var x = 0; x < w; x ++ ) { + return ns; - if ( xoff >= 0 && yoff >= 0 ) { + } +}; +UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { - si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; + var w = Math.min( sw, tw ), h = Math.min( sh, th ); + var si = 0, ti = 0; + for ( var y = 0; y < h; y ++ ) + for ( var x = 0; x < w; x ++ ) { - } else { + if ( xoff >= 0 && yoff >= 0 ) { - si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; + si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; - } - - if ( mode == 0 ) { + } else { - tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; + si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; - } else if ( mode == 1 ) { + } - var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; - var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; + if ( mode == 0 ) { - var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); - tb[ ti + 3 ] = 255 * oa; - tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; - tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; - tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; + tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; - } else if ( mode == 2 ) { // copy only differences, otherwise zero + } else if ( mode == 1 ) { - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) { + var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; + var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; - tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; + var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); + tb[ ti + 3 ] = 255 * oa; + tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; + tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; + tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; - } else { + } else if ( mode == 2 ) { // copy only differences, otherwise zero - tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) { - } + tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; - } else if ( mode == 3 ) { // check if can be blended + } else { - var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; - var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; - if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; - //if(fa!=255 && ba!=0) return false; - if ( fa < 220 && ba > 20 ) return false; + tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; } + } else if ( mode == 3 ) { // check if can be blended + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; + //if(fa!=255 && ba!=0) return false; + if ( fa < 220 && ba > 20 ) return false; + } - return true; + } - }; + return true; + +}; - return UPNG; +return UPNG; } )(); diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index 6c7d193ee61131..2df3bbacc62a65 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -17,296 +17,296 @@ import { const Lensflare = /* @__PURE__ */ ( () => { - class Lensflare extends Mesh { +class Lensflare extends Mesh { - constructor() { + constructor() { - super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); + super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); - this.isLensflare = true; + this.isLensflare = true; - this.type = 'Lensflare'; - this.frustumCulled = false; - this.renderOrder = Infinity; + this.type = 'Lensflare'; + this.frustumCulled = false; + this.renderOrder = Infinity; - // + // - const positionScreen = new Vector3(); - const positionView = new Vector3(); + const positionScreen = new Vector3(); + const positionView = new Vector3(); - // textures + // textures - const tempMap = new FramebufferTexture( 16, 16 ); - const occlusionMap = new FramebufferTexture( 16, 16 ); + const tempMap = new FramebufferTexture( 16, 16 ); + const occlusionMap = new FramebufferTexture( 16, 16 ); - let currentType = UnsignedByteType; + let currentType = UnsignedByteType; - // material + // material - const geometry = Lensflare.Geometry; + const geometry = Lensflare.Geometry; - const material1a = new RawShaderMaterial( { - uniforms: { - 'scale': { value: null }, - 'screenPosition': { value: null } - }, - vertexShader: /* glsl */` + const material1a = new RawShaderMaterial( { + uniforms: { + 'scale': { value: null }, + 'screenPosition': { value: null } + }, + vertexShader: /* glsl */` - precision highp float; + precision highp float; - uniform vec3 screenPosition; - uniform vec2 scale; + uniform vec3 screenPosition; + uniform vec2 scale; - attribute vec3 position; + attribute vec3 position; - void main() { + void main() { - gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); + gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); - }`, + }`, - fragmentShader: /* glsl */` + fragmentShader: /* glsl */` - precision highp float; + precision highp float; - void main() { + void main() { - gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 ); + gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 ); - }`, - depthTest: true, - depthWrite: false, - transparent: false - } ); + }`, + depthTest: true, + depthWrite: false, + transparent: false + } ); - const material1b = new RawShaderMaterial( { - uniforms: { - 'map': { value: tempMap }, - 'scale': { value: null }, - 'screenPosition': { value: null } - }, - vertexShader: /* glsl */` + const material1b = new RawShaderMaterial( { + uniforms: { + 'map': { value: tempMap }, + 'scale': { value: null }, + 'screenPosition': { value: null } + }, + vertexShader: /* glsl */` - precision highp float; + precision highp float; - uniform vec3 screenPosition; - uniform vec2 scale; + uniform vec3 screenPosition; + uniform vec2 scale; - attribute vec3 position; - attribute vec2 uv; + attribute vec3 position; + attribute vec2 uv; - varying vec2 vUV; + varying vec2 vUV; - void main() { + void main() { - vUV = uv; + vUV = uv; - gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); + gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 ); - }`, + }`, - fragmentShader: /* glsl */` + fragmentShader: /* glsl */` - precision highp float; + precision highp float; - uniform sampler2D map; + uniform sampler2D map; - varying vec2 vUV; + varying vec2 vUV; - void main() { + void main() { - gl_FragColor = texture2D( map, vUV ); + gl_FragColor = texture2D( map, vUV ); - }`, - depthTest: false, - depthWrite: false, - transparent: false - } ); + }`, + depthTest: false, + depthWrite: false, + transparent: false + } ); - // the following object is used for occlusionMap generation + // the following object is used for occlusionMap generation - const mesh1 = new Mesh( geometry, material1a ); + const mesh1 = new Mesh( geometry, material1a ); - // + // - const elements = []; + const elements = []; - const shader = LensflareElement.Shader; + const shader = LensflareElement.Shader; - const material2 = new RawShaderMaterial( { - uniforms: { - 'map': { value: null }, - 'occlusionMap': { value: occlusionMap }, - 'color': { value: new Color( 0xffffff ) }, - 'scale': { value: new Vector2() }, - 'screenPosition': { value: new Vector3() } - }, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - blending: AdditiveBlending, - transparent: true, - depthWrite: false - } ); + const material2 = new RawShaderMaterial( { + uniforms: { + 'map': { value: null }, + 'occlusionMap': { value: occlusionMap }, + 'color': { value: new Color( 0xffffff ) }, + 'scale': { value: new Vector2() }, + 'screenPosition': { value: new Vector3() } + }, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + blending: AdditiveBlending, + transparent: true, + depthWrite: false + } ); - const mesh2 = new Mesh( geometry, material2 ); + const mesh2 = new Mesh( geometry, material2 ); - this.addElement = function ( element ) { + this.addElement = function ( element ) { - elements.push( element ); + elements.push( element ); - }; + }; - // + // - const scale = new Vector2(); - const screenPositionPixels = new Vector2(); - const validArea = new Box2(); - const viewport = new Vector4(); + const scale = new Vector2(); + const screenPositionPixels = new Vector2(); + const validArea = new Box2(); + const viewport = new Vector4(); - this.onBeforeRender = function ( renderer, scene, camera ) { + this.onBeforeRender = function ( renderer, scene, camera ) { - renderer.getCurrentViewport( viewport ); + renderer.getCurrentViewport( viewport ); - const renderTarget = renderer.getRenderTarget(); - const type = ( renderTarget !== null ) ? renderTarget.texture.type : UnsignedByteType; + const renderTarget = renderer.getRenderTarget(); + const type = ( renderTarget !== null ) ? renderTarget.texture.type : UnsignedByteType; - if ( currentType !== type ) { + if ( currentType !== type ) { - tempMap.dispose(); - occlusionMap.dispose(); - - tempMap.type = occlusionMap.type = type; + tempMap.dispose(); + occlusionMap.dispose(); - currentType = type; + tempMap.type = occlusionMap.type = type; - } + currentType = type; - const invAspect = viewport.w / viewport.z; - const halfViewportWidth = viewport.z / 2.0; - const halfViewportHeight = viewport.w / 2.0; + } - let size = 16 / viewport.w; - scale.set( size * invAspect, size ); + const invAspect = viewport.w / viewport.z; + const halfViewportWidth = viewport.z / 2.0; + const halfViewportHeight = viewport.w / 2.0; - validArea.min.set( viewport.x, viewport.y ); - validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); + let size = 16 / viewport.w; + scale.set( size * invAspect, size ); - // calculate position in screen space + validArea.min.set( viewport.x, viewport.y ); + validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); - positionView.setFromMatrixPosition( this.matrixWorld ); - positionView.applyMatrix4( camera.matrixWorldInverse ); + // calculate position in screen space - if ( positionView.z > 0 ) return; // lensflare is behind the camera + positionView.setFromMatrixPosition( this.matrixWorld ); + positionView.applyMatrix4( camera.matrixWorldInverse ); - positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix ); + if ( positionView.z > 0 ) return; // lensflare is behind the camera - // horizontal and vertical coordinate of the lower left corner of the pixels to copy + positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix ); - screenPositionPixels.x = viewport.x + ( positionScreen.x * halfViewportWidth ) + halfViewportWidth - 8; - screenPositionPixels.y = viewport.y + ( positionScreen.y * halfViewportHeight ) + halfViewportHeight - 8; + // horizontal and vertical coordinate of the lower left corner of the pixels to copy - // screen cull + screenPositionPixels.x = viewport.x + ( positionScreen.x * halfViewportWidth ) + halfViewportWidth - 8; + screenPositionPixels.y = viewport.y + ( positionScreen.y * halfViewportHeight ) + halfViewportHeight - 8; - if ( validArea.containsPoint( screenPositionPixels ) ) { + // screen cull - // save current RGB to temp texture + if ( validArea.containsPoint( screenPositionPixels ) ) { - renderer.copyFramebufferToTexture( screenPositionPixels, tempMap ); + // save current RGB to temp texture - // render pink quad + renderer.copyFramebufferToTexture( screenPositionPixels, tempMap ); - let uniforms = material1a.uniforms; - uniforms[ 'scale' ].value = scale; - uniforms[ 'screenPosition' ].value = positionScreen; + // render pink quad - renderer.renderBufferDirect( camera, null, geometry, material1a, mesh1, null ); + let uniforms = material1a.uniforms; + uniforms[ 'scale' ].value = scale; + uniforms[ 'screenPosition' ].value = positionScreen; - // copy result to occlusionMap + renderer.renderBufferDirect( camera, null, geometry, material1a, mesh1, null ); - renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap ); + // copy result to occlusionMap - // restore graphics + renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap ); - uniforms = material1b.uniforms; - uniforms[ 'scale' ].value = scale; - uniforms[ 'screenPosition' ].value = positionScreen; + // restore graphics - renderer.renderBufferDirect( camera, null, geometry, material1b, mesh1, null ); + uniforms = material1b.uniforms; + uniforms[ 'scale' ].value = scale; + uniforms[ 'screenPosition' ].value = positionScreen; - // render elements + renderer.renderBufferDirect( camera, null, geometry, material1b, mesh1, null ); - const vecX = - positionScreen.x * 2; - const vecY = - positionScreen.y * 2; + // render elements - for ( let i = 0, l = elements.length; i < l; i ++ ) { + const vecX = - positionScreen.x * 2; + const vecY = - positionScreen.y * 2; - const element = elements[ i ]; + for ( let i = 0, l = elements.length; i < l; i ++ ) { - const uniforms = material2.uniforms; + const element = elements[ i ]; - uniforms[ 'color' ].value.copy( element.color ); - uniforms[ 'map' ].value = element.texture; - uniforms[ 'screenPosition' ].value.x = positionScreen.x + vecX * element.distance; - uniforms[ 'screenPosition' ].value.y = positionScreen.y + vecY * element.distance; + const uniforms = material2.uniforms; - size = element.size / viewport.w; - const invAspect = viewport.w / viewport.z; + uniforms[ 'color' ].value.copy( element.color ); + uniforms[ 'map' ].value = element.texture; + uniforms[ 'screenPosition' ].value.x = positionScreen.x + vecX * element.distance; + uniforms[ 'screenPosition' ].value.y = positionScreen.y + vecY * element.distance; - uniforms[ 'scale' ].value.set( size * invAspect, size ); + size = element.size / viewport.w; + const invAspect = viewport.w / viewport.z; - material2.uniformsNeedUpdate = true; + uniforms[ 'scale' ].value.set( size * invAspect, size ); - renderer.renderBufferDirect( camera, null, geometry, material2, mesh2, null ); + material2.uniformsNeedUpdate = true; - } + renderer.renderBufferDirect( camera, null, geometry, material2, mesh2, null ); } - }; + } - this.dispose = function () { + }; - material1a.dispose(); - material1b.dispose(); - material2.dispose(); + this.dispose = function () { - tempMap.dispose(); - occlusionMap.dispose(); + material1a.dispose(); + material1b.dispose(); + material2.dispose(); - for ( let i = 0, l = elements.length; i < l; i ++ ) { + tempMap.dispose(); + occlusionMap.dispose(); - elements[ i ].texture.dispose(); + for ( let i = 0, l = elements.length; i < l; i ++ ) { - } + elements[ i ].texture.dispose(); - }; + } - } + }; } - Lensflare.Geometry = /* @__PURE__ */ ( function () { +} + +Lensflare.Geometry = /* @__PURE__ */ ( function () { - const geometry = new BufferGeometry(); + const geometry = new BufferGeometry(); - const float32Array = new Float32Array( [ - - 1, - 1, 0, 0, 0, - 1, - 1, 0, 1, 0, - 1, 1, 0, 1, 1, - - 1, 1, 0, 0, 1 - ] ); + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - return geometry; + return geometry; - } )(); +} )(); - return Lensflare; +return Lensflare; } )(); @@ -314,92 +314,92 @@ const Lensflare = /* @__PURE__ */ ( () => { const LensflareElement = /* @__PURE__ */ ( () => { - class LensflareElement { - - constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { +class LensflareElement { - this.texture = texture; - this.size = size; - this.distance = distance; - this.color = color; + constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { - } + this.texture = texture; + this.size = size; + this.distance = distance; + this.color = color; } - LensflareElement.Shader = { +} + +LensflareElement.Shader = { - uniforms: { + uniforms: { - 'map': { value: null }, - 'occlusionMap': { value: null }, - 'color': { value: null }, - 'scale': { value: null }, - 'screenPosition': { value: null } + 'map': { value: null }, + 'occlusionMap': { value: null }, + 'color': { value: null }, + 'scale': { value: null }, + 'screenPosition': { value: null } - }, + }, - vertexShader: /* glsl */` + vertexShader: /* glsl */` - precision highp float; + precision highp float; - uniform vec3 screenPosition; - uniform vec2 scale; + uniform vec3 screenPosition; + uniform vec2 scale; - uniform sampler2D occlusionMap; + uniform sampler2D occlusionMap; - attribute vec3 position; - attribute vec2 uv; + attribute vec3 position; + attribute vec2 uv; - varying vec2 vUV; - varying float vVisibility; + varying vec2 vUV; + varying float vVisibility; - void main() { + void main() { - vUV = uv; + vUV = uv; - vec2 pos = position.xy; + vec2 pos = position.xy; - vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); - visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); - visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); + vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) ); + visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ); + visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) ); - vVisibility = visibility.r / 9.0; - vVisibility *= 1.0 - visibility.g / 9.0; - vVisibility *= visibility.b / 9.0; + vVisibility = visibility.r / 9.0; + vVisibility *= 1.0 - visibility.g / 9.0; + vVisibility *= visibility.b / 9.0; - gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); + gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 ); - }`, + }`, - fragmentShader: /* glsl */` + fragmentShader: /* glsl */` - precision highp float; + precision highp float; - uniform sampler2D map; - uniform vec3 color; + uniform sampler2D map; + uniform vec3 color; - varying vec2 vUV; - varying float vVisibility; + varying vec2 vUV; + varying float vVisibility; - void main() { + void main() { - vec4 texture = texture2D( map, vUV ); - texture.a *= vVisibility; - gl_FragColor = texture; - gl_FragColor.rgb *= color; + vec4 texture = texture2D( map, vUV ); + texture.a *= vVisibility; + gl_FragColor = texture; + gl_FragColor.rgb *= color; - }` + }` - }; +}; - return LensflareElement; +return LensflareElement; } )(); diff --git a/examples/jsm/objects/Reflector.js b/examples/jsm/objects/Reflector.js index 83f49a606a5cb8..40ca3542685ee7 100644 --- a/examples/jsm/objects/Reflector.js +++ b/examples/jsm/objects/Reflector.js @@ -14,256 +14,256 @@ import { const Reflector = /* @__PURE__ */ ( () => { - class Reflector extends Mesh { +class Reflector extends Mesh { - constructor( geometry, options = {} ) { + constructor( geometry, options = {} ) { - super( geometry ); + super( geometry ); - this.isReflector = true; + this.isReflector = true; - this.type = 'Reflector'; - this.camera = new PerspectiveCamera(); + this.type = 'Reflector'; + this.camera = new PerspectiveCamera(); - const scope = this; + const scope = this; - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const shader = options.shader || Reflector.ReflectorShader; - const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const shader = options.shader || Reflector.ReflectorShader; + const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; - // + // - const reflectorPlane = new Plane(); - const normal = new Vector3(); - const reflectorWorldPosition = new Vector3(); - const cameraWorldPosition = new Vector3(); - const rotationMatrix = new Matrix4(); - const lookAtPosition = new Vector3( 0, 0, - 1 ); - const clipPlane = new Vector4(); + const reflectorPlane = new Plane(); + const normal = new Vector3(); + const reflectorWorldPosition = new Vector3(); + const cameraWorldPosition = new Vector3(); + const rotationMatrix = new Matrix4(); + const lookAtPosition = new Vector3( 0, 0, - 1 ); + const clipPlane = new Vector4(); - const view = new Vector3(); - const target = new Vector3(); - const q = new Vector4(); + const view = new Vector3(); + const target = new Vector3(); + const q = new Vector4(); - const textureMatrix = new Matrix4(); - const virtualCamera = this.camera; + const textureMatrix = new Matrix4(); + const virtualCamera = this.camera; - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); - const material = new ShaderMaterial( { - name: ( shader.name !== undefined ) ? shader.name : 'unspecified', - uniforms: UniformsUtils.clone( shader.uniforms ), - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader - } ); + const material = new ShaderMaterial( { + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', + uniforms: UniformsUtils.clone( shader.uniforms ), + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader + } ); - material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; - material.uniforms[ 'color' ].value = color; - material.uniforms[ 'textureMatrix' ].value = textureMatrix; + material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; + material.uniforms[ 'color' ].value = color; + material.uniforms[ 'textureMatrix' ].value = textureMatrix; - this.material = material; + this.material = material; - this.onBeforeRender = function ( renderer, scene, camera ) { + this.onBeforeRender = function ( renderer, scene, camera ) { - reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); - cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); - rotationMatrix.extractRotation( scope.matrixWorld ); + rotationMatrix.extractRotation( scope.matrixWorld ); - normal.set( 0, 0, 1 ); - normal.applyMatrix4( rotationMatrix ); + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); - view.subVectors( reflectorWorldPosition, cameraWorldPosition ); + view.subVectors( reflectorWorldPosition, cameraWorldPosition ); - // Avoid rendering when reflector is facing away + // Avoid rendering when reflector is facing away - if ( view.dot( normal ) > 0 ) return; + if ( view.dot( normal ) > 0 ) return; - view.reflect( normal ).negate(); - view.add( reflectorWorldPosition ); + view.reflect( normal ).negate(); + view.add( reflectorWorldPosition ); - rotationMatrix.extractRotation( camera.matrixWorld ); + rotationMatrix.extractRotation( camera.matrixWorld ); - lookAtPosition.set( 0, 0, - 1 ); - lookAtPosition.applyMatrix4( rotationMatrix ); - lookAtPosition.add( cameraWorldPosition ); + lookAtPosition.set( 0, 0, - 1 ); + lookAtPosition.applyMatrix4( rotationMatrix ); + lookAtPosition.add( cameraWorldPosition ); - target.subVectors( reflectorWorldPosition, lookAtPosition ); - target.reflect( normal ).negate(); - target.add( reflectorWorldPosition ); + target.subVectors( reflectorWorldPosition, lookAtPosition ); + target.reflect( normal ).negate(); + target.add( reflectorWorldPosition ); - virtualCamera.position.copy( view ); - virtualCamera.up.set( 0, 1, 0 ); - virtualCamera.up.applyMatrix4( rotationMatrix ); - virtualCamera.up.reflect( normal ); - virtualCamera.lookAt( target ); + virtualCamera.position.copy( view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( rotationMatrix ); + virtualCamera.up.reflect( normal ); + virtualCamera.lookAt( target ); - virtualCamera.far = camera.far; // Used in WebGLBackground + virtualCamera.far = camera.far; // Used in WebGLBackground - virtualCamera.updateMatrixWorld(); - virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); - // Update the texture matrix - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - textureMatrix.multiply( virtualCamera.projectionMatrix ); - textureMatrix.multiply( virtualCamera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + // Update the texture matrix + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + textureMatrix.multiply( virtualCamera.projectionMatrix ); + textureMatrix.multiply( virtualCamera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html - // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf - reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition ); - reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html + // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf + reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition ); + reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); - clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant ); + clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant ); - const projectionMatrix = virtualCamera.projectionMatrix; + const projectionMatrix = virtualCamera.projectionMatrix; - q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; - q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; - q.z = - 1.0; - q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + q.z = - 1.0; + q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; - // Calculate the scaled plane vector - clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) ); + // Calculate the scaled plane vector + clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) ); - // Replacing the third row of the projection matrix - projectionMatrix.elements[ 2 ] = clipPlane.x; - projectionMatrix.elements[ 6 ] = clipPlane.y; - projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; - projectionMatrix.elements[ 14 ] = clipPlane.w; + // Replacing the third row of the projection matrix + projectionMatrix.elements[ 2 ] = clipPlane.x; + projectionMatrix.elements[ 6 ] = clipPlane.y; + projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; + projectionMatrix.elements[ 14 ] = clipPlane.w; - // Render - scope.visible = false; + // Render + scope.visible = false; - const currentRenderTarget = renderer.getRenderTarget(); + const currentRenderTarget = renderer.getRenderTarget(); - const currentXrEnabled = renderer.xr.enabled; - const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + const currentXrEnabled = renderer.xr.enabled; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - renderer.xr.enabled = false; // Avoid camera modification - renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows + renderer.xr.enabled = false; // Avoid camera modification + renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows - renderer.setRenderTarget( renderTarget ); + renderer.setRenderTarget( renderTarget ); - renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 + renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 - if ( renderer.autoClear === false ) renderer.clear(); - renderer.render( scene, virtualCamera ); + if ( renderer.autoClear === false ) renderer.clear(); + renderer.render( scene, virtualCamera ); - renderer.xr.enabled = currentXrEnabled; - renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + renderer.xr.enabled = currentXrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.setRenderTarget( currentRenderTarget ); + renderer.setRenderTarget( currentRenderTarget ); - // Restore viewport + // Restore viewport - const viewport = camera.viewport; + const viewport = camera.viewport; - if ( viewport !== undefined ) { + if ( viewport !== undefined ) { - renderer.state.viewport( viewport ); + renderer.state.viewport( viewport ); - } - - scope.visible = true; + } - }; + scope.visible = true; - this.getRenderTarget = function () { + }; - return renderTarget; + this.getRenderTarget = function () { - }; + return renderTarget; - this.dispose = function () { + }; - renderTarget.dispose(); - scope.material.dispose(); + this.dispose = function () { - }; + renderTarget.dispose(); + scope.material.dispose(); - } + }; } - Reflector.ReflectorShader = { - - name: 'ReflectorShader', +} - uniforms: { +Reflector.ReflectorShader = { - 'color': { - value: null - }, + name: 'ReflectorShader', - 'tDiffuse': { - value: null - }, + uniforms: { - 'textureMatrix': { - value: null - } + 'color': { + value: null + }, + 'tDiffuse': { + value: null }, - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; + 'textureMatrix': { + value: null + } - #include - #include + }, - void main() { + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; - vUv = textureMatrix * vec4( position, 1.0 ); + #include + #include - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + void main() { - #include + vUv = textureMatrix * vec4( position, 1.0 ); - }`, + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - varying vec4 vUv; + #include - #include + }`, - float blendOverlay( float base, float blend ) { + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + varying vec4 vUv; - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + #include - } + float blendOverlay( float base, float blend ) { - vec3 blendOverlay( vec3 base, vec3 blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } - } + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } - void main() { + void main() { - #include + #include - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #include - #include + #include + #include - }` - }; + }` +}; - return Reflector; +return Reflector; } )(); diff --git a/examples/jsm/objects/ReflectorForSSRPass.js b/examples/jsm/objects/ReflectorForSSRPass.js index 7ed7a294f99759..d1d8550c812317 100644 --- a/examples/jsm/objects/ReflectorForSSRPass.js +++ b/examples/jsm/objects/ReflectorForSSRPass.js @@ -17,338 +17,338 @@ import { const ReflectorForSSRPass = /* @__PURE__ */ ( () => { - class ReflectorForSSRPass extends Mesh { +class ReflectorForSSRPass extends Mesh { - constructor( geometry, options = {} ) { + constructor( geometry, options = {} ) { - super( geometry ); + super( geometry ); - this.isReflectorForSSRPass = true; + this.isReflectorForSSRPass = true; - this.type = 'ReflectorForSSRPass'; + this.type = 'ReflectorForSSRPass'; - const scope = this; + const scope = this; - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const shader = options.shader || ReflectorForSSRPass.ReflectorShader; - const useDepthTexture = options.useDepthTexture === true; - const yAxis = new Vector3( 0, 1, 0 ); - const vecTemp0 = new Vector3(); - const vecTemp1 = new Vector3(); + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const shader = options.shader || ReflectorForSSRPass.ReflectorShader; + const useDepthTexture = options.useDepthTexture === true; + const yAxis = new Vector3( 0, 1, 0 ); + const vecTemp0 = new Vector3(); + const vecTemp1 = new Vector3(); - // + // - scope.needsUpdate = false; - scope.maxDistance = ReflectorForSSRPass.ReflectorShader.uniforms.maxDistance.value; - scope.opacity = ReflectorForSSRPass.ReflectorShader.uniforms.opacity.value; - scope.color = color; - scope.resolution = options.resolution || new Vector2( window.innerWidth, window.innerHeight ); + scope.needsUpdate = false; + scope.maxDistance = ReflectorForSSRPass.ReflectorShader.uniforms.maxDistance.value; + scope.opacity = ReflectorForSSRPass.ReflectorShader.uniforms.opacity.value; + scope.color = color; + scope.resolution = options.resolution || new Vector2( window.innerWidth, window.innerHeight ); - scope._distanceAttenuation = ReflectorForSSRPass.ReflectorShader.defines.DISTANCE_ATTENUATION; - Object.defineProperty( scope, 'distanceAttenuation', { - get() { + scope._distanceAttenuation = ReflectorForSSRPass.ReflectorShader.defines.DISTANCE_ATTENUATION; + Object.defineProperty( scope, 'distanceAttenuation', { + get() { - return scope._distanceAttenuation; + return scope._distanceAttenuation; - }, - set( val ) { + }, + set( val ) { - if ( scope._distanceAttenuation === val ) return; - scope._distanceAttenuation = val; - scope.material.defines.DISTANCE_ATTENUATION = val; - scope.material.needsUpdate = true; + if ( scope._distanceAttenuation === val ) return; + scope._distanceAttenuation = val; + scope.material.defines.DISTANCE_ATTENUATION = val; + scope.material.needsUpdate = true; - } - } ); - - scope._fresnel = ReflectorForSSRPass.ReflectorShader.defines.FRESNEL; - Object.defineProperty( scope, 'fresnel', { - get() { + } + } ); - return scope._fresnel; + scope._fresnel = ReflectorForSSRPass.ReflectorShader.defines.FRESNEL; + Object.defineProperty( scope, 'fresnel', { + get() { - }, - set( val ) { + return scope._fresnel; - if ( scope._fresnel === val ) return; - scope._fresnel = val; - scope.material.defines.FRESNEL = val; - scope.material.needsUpdate = true; + }, + set( val ) { - } - } ); + if ( scope._fresnel === val ) return; + scope._fresnel = val; + scope.material.defines.FRESNEL = val; + scope.material.needsUpdate = true; - const normal = new Vector3(); - const reflectorWorldPosition = new Vector3(); - const cameraWorldPosition = new Vector3(); - const rotationMatrix = new Matrix4(); - const lookAtPosition = new Vector3( 0, 0, - 1 ); + } + } ); - const view = new Vector3(); - const target = new Vector3(); + const normal = new Vector3(); + const reflectorWorldPosition = new Vector3(); + const cameraWorldPosition = new Vector3(); + const rotationMatrix = new Matrix4(); + const lookAtPosition = new Vector3( 0, 0, - 1 ); - const textureMatrix = new Matrix4(); - const virtualCamera = new PerspectiveCamera(); + const view = new Vector3(); + const target = new Vector3(); - let depthTexture; + const textureMatrix = new Matrix4(); + const virtualCamera = new PerspectiveCamera(); - if ( useDepthTexture ) { + let depthTexture; - depthTexture = new DepthTexture(); - depthTexture.type = UnsignedShortType; - depthTexture.minFilter = NearestFilter; - depthTexture.magFilter = NearestFilter; + if ( useDepthTexture ) { - } + depthTexture = new DepthTexture(); + depthTexture.type = UnsignedShortType; + depthTexture.minFilter = NearestFilter; + depthTexture.magFilter = NearestFilter; - const parameters = { - depthTexture: useDepthTexture ? depthTexture : null, - type: HalfFloatType - }; + } - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters ); + const parameters = { + depthTexture: useDepthTexture ? depthTexture : null, + type: HalfFloatType + }; - const material = new ShaderMaterial( { - transparent: useDepthTexture, - defines: Object.assign( {}, ReflectorForSSRPass.ReflectorShader.defines, { - useDepthTexture - } ), - uniforms: UniformsUtils.clone( shader.uniforms ), - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader - } ); + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters ); - material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; - material.uniforms[ 'color' ].value = scope.color; - material.uniforms[ 'textureMatrix' ].value = textureMatrix; - if ( useDepthTexture ) { + const material = new ShaderMaterial( { + transparent: useDepthTexture, + defines: Object.assign( {}, ReflectorForSSRPass.ReflectorShader.defines, { + useDepthTexture + } ), + uniforms: UniformsUtils.clone( shader.uniforms ), + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader + } ); - material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture; + material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; + material.uniforms[ 'color' ].value = scope.color; + material.uniforms[ 'textureMatrix' ].value = textureMatrix; + if ( useDepthTexture ) { - } + material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture; - this.material = material; + } - const globalPlane = new Plane( new Vector3( 0, 1, 0 ), clipBias ); - const globalPlanes = [ globalPlane ]; + this.material = material; - this.doRender = function ( renderer, scene, camera ) { + const globalPlane = new Plane( new Vector3( 0, 1, 0 ), clipBias ); + const globalPlanes = [ globalPlane ]; - material.uniforms[ 'maxDistance' ].value = scope.maxDistance; - material.uniforms[ 'color' ].value = scope.color; - material.uniforms[ 'opacity' ].value = scope.opacity; + this.doRender = function ( renderer, scene, camera ) { - vecTemp0.copy( camera.position ).normalize(); - vecTemp1.copy( vecTemp0 ).reflect( yAxis ); - material.uniforms[ 'fresnelCoe' ].value = ( vecTemp0.dot( vecTemp1 ) + 1. ) / 2.; // TODO: Also need to use glsl viewPosition and viewNormal per pixel. + material.uniforms[ 'maxDistance' ].value = scope.maxDistance; + material.uniforms[ 'color' ].value = scope.color; + material.uniforms[ 'opacity' ].value = scope.opacity; - reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); - cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + vecTemp0.copy( camera.position ).normalize(); + vecTemp1.copy( vecTemp0 ).reflect( yAxis ); + material.uniforms[ 'fresnelCoe' ].value = ( vecTemp0.dot( vecTemp1 ) + 1. ) / 2.; // TODO: Also need to use glsl viewPosition and viewNormal per pixel. - rotationMatrix.extractRotation( scope.matrixWorld ); + reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); - normal.set( 0, 0, 1 ); - normal.applyMatrix4( rotationMatrix ); + rotationMatrix.extractRotation( scope.matrixWorld ); - view.subVectors( reflectorWorldPosition, cameraWorldPosition ); + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); - // Avoid rendering when reflector is facing away + view.subVectors( reflectorWorldPosition, cameraWorldPosition ); - if ( view.dot( normal ) > 0 ) return; + // Avoid rendering when reflector is facing away - view.reflect( normal ).negate(); - view.add( reflectorWorldPosition ); + if ( view.dot( normal ) > 0 ) return; - rotationMatrix.extractRotation( camera.matrixWorld ); + view.reflect( normal ).negate(); + view.add( reflectorWorldPosition ); - lookAtPosition.set( 0, 0, - 1 ); - lookAtPosition.applyMatrix4( rotationMatrix ); - lookAtPosition.add( cameraWorldPosition ); + rotationMatrix.extractRotation( camera.matrixWorld ); - target.subVectors( reflectorWorldPosition, lookAtPosition ); - target.reflect( normal ).negate(); - target.add( reflectorWorldPosition ); + lookAtPosition.set( 0, 0, - 1 ); + lookAtPosition.applyMatrix4( rotationMatrix ); + lookAtPosition.add( cameraWorldPosition ); - virtualCamera.position.copy( view ); - virtualCamera.up.set( 0, 1, 0 ); - virtualCamera.up.applyMatrix4( rotationMatrix ); - virtualCamera.up.reflect( normal ); - virtualCamera.lookAt( target ); + target.subVectors( reflectorWorldPosition, lookAtPosition ); + target.reflect( normal ).negate(); + target.add( reflectorWorldPosition ); - virtualCamera.far = camera.far; // Used in WebGLBackground + virtualCamera.position.copy( view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( rotationMatrix ); + virtualCamera.up.reflect( normal ); + virtualCamera.lookAt( target ); - virtualCamera.updateMatrixWorld(); - virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.far = camera.far; // Used in WebGLBackground - material.uniforms[ 'virtualCameraNear' ].value = camera.near; - material.uniforms[ 'virtualCameraFar' ].value = camera.far; - material.uniforms[ 'virtualCameraMatrixWorld' ].value = virtualCamera.matrixWorld; - material.uniforms[ 'virtualCameraProjectionMatrix' ].value = camera.projectionMatrix; - material.uniforms[ 'virtualCameraProjectionMatrixInverse' ].value = camera.projectionMatrixInverse; - material.uniforms[ 'resolution' ].value = scope.resolution; + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); - // Update the texture matrix - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - textureMatrix.multiply( virtualCamera.projectionMatrix ); - textureMatrix.multiply( virtualCamera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + material.uniforms[ 'virtualCameraNear' ].value = camera.near; + material.uniforms[ 'virtualCameraFar' ].value = camera.far; + material.uniforms[ 'virtualCameraMatrixWorld' ].value = virtualCamera.matrixWorld; + material.uniforms[ 'virtualCameraProjectionMatrix' ].value = camera.projectionMatrix; + material.uniforms[ 'virtualCameraProjectionMatrixInverse' ].value = camera.projectionMatrixInverse; + material.uniforms[ 'resolution' ].value = scope.resolution; - // scope.visible = false; + // Update the texture matrix + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + textureMatrix.multiply( virtualCamera.projectionMatrix ); + textureMatrix.multiply( virtualCamera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - const currentRenderTarget = renderer.getRenderTarget(); + // scope.visible = false; - const currentXrEnabled = renderer.xr.enabled; - const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - const currentClippingPlanes = renderer.clippingPlanes; + const currentRenderTarget = renderer.getRenderTarget(); - renderer.xr.enabled = false; // Avoid camera modification - renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows - renderer.clippingPlanes = globalPlanes; + const currentXrEnabled = renderer.xr.enabled; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + const currentClippingPlanes = renderer.clippingPlanes; - renderer.setRenderTarget( renderTarget ); + renderer.xr.enabled = false; // Avoid camera modification + renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows + renderer.clippingPlanes = globalPlanes; - renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 + renderer.setRenderTarget( renderTarget ); - if ( renderer.autoClear === false ) renderer.clear(); - renderer.render( scene, virtualCamera ); + renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 - renderer.xr.enabled = currentXrEnabled; - renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.clippingPlanes = currentClippingPlanes; + if ( renderer.autoClear === false ) renderer.clear(); + renderer.render( scene, virtualCamera ); - renderer.setRenderTarget( currentRenderTarget ); + renderer.xr.enabled = currentXrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + renderer.clippingPlanes = currentClippingPlanes; - // Restore viewport + renderer.setRenderTarget( currentRenderTarget ); - const viewport = camera.viewport; + // Restore viewport - if ( viewport !== undefined ) { + const viewport = camera.viewport; - renderer.state.viewport( viewport ); + if ( viewport !== undefined ) { - } + renderer.state.viewport( viewport ); - // scope.visible = true; + } - }; + // scope.visible = true; - this.getRenderTarget = function () { + }; - return renderTarget; + this.getRenderTarget = function () { - }; + return renderTarget; - } + }; } - ReflectorForSSRPass.ReflectorShader = { - - defines: { - DISTANCE_ATTENUATION: true, - FRESNEL: true, - }, - - uniforms: { - - color: { value: null }, - tDiffuse: { value: null }, - tDepth: { value: null }, - textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, - maxDistance: { value: 180 }, - opacity: { value: 0.5 }, - fresnelCoe: { value: null }, - virtualCameraNear: { value: null }, - virtualCameraFar: { value: null }, - virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, - virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, - resolution: { value: /* @__PURE__ */ new Vector2() }, - - }, - - vertexShader: /* glsl */` - uniform mat4 textureMatrix; - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - uniform vec3 color; - uniform sampler2D tDiffuse; - uniform sampler2D tDepth; - uniform float maxDistance; - uniform float opacity; - uniform float fresnelCoe; - uniform float virtualCameraNear; - uniform float virtualCameraFar; - uniform mat4 virtualCameraProjectionMatrix; - uniform mat4 virtualCameraProjectionMatrixInverse; - uniform mat4 virtualCameraMatrixWorld; - uniform vec2 resolution; - varying vec4 vUv; - #include - float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } - vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } - float getDepth( const in vec2 uv ) { - return texture2D( tDepth, uv ).x; - } - float getViewZ( const in float depth ) { - return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); - } - vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { - vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc - clipPosition *= clipW; //clip - return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view - } - void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - #ifdef useDepthTexture - vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; - uv.x=1.-uv.x; - float depth = texture2DProj( tDepth, vUv ).r; - float viewZ = getViewZ( depth ); - float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; - vec3 viewPosition=getViewPosition( uv, depth, clipW ); - vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; - if(worldPosition.y>maxDistance) discard; - float op=opacity; - #ifdef DISTANCE_ATTENUATION - float ratio=1.-(worldPosition.y/maxDistance); - float attenuation=ratio*ratio; - op=opacity*attenuation; - #endif - #ifdef FRESNEL - op*=fresnelCoe; - #endif - gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); - #else - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); +} + +ReflectorForSSRPass.ReflectorShader = { + + defines: { + DISTANCE_ATTENUATION: true, + FRESNEL: true, + }, + + uniforms: { + + color: { value: null }, + tDiffuse: { value: null }, + tDepth: { value: null }, + textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, + maxDistance: { value: 180 }, + opacity: { value: 0.5 }, + fresnelCoe: { value: null }, + virtualCameraNear: { value: null }, + virtualCameraFar: { value: null }, + virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + + }, + + vertexShader: /* glsl */` + uniform mat4 textureMatrix; + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + uniform vec3 color; + uniform sampler2D tDiffuse; + uniform sampler2D tDepth; + uniform float maxDistance; + uniform float opacity; + uniform float fresnelCoe; + uniform float virtualCameraNear; + uniform float virtualCameraFar; + uniform mat4 virtualCameraProjectionMatrix; + uniform mat4 virtualCameraProjectionMatrixInverse; + uniform mat4 virtualCameraMatrixWorld; + uniform vec2 resolution; + varying vec4 vUv; + #include + float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } + vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } + float getDepth( const in vec2 uv ) { + return texture2D( tDepth, uv ).x; + } + float getViewZ( const in float depth ) { + return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); + } + vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { + vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc + clipPosition *= clipW; //clip + return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view + } + void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + #ifdef useDepthTexture + vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; + uv.x=1.-uv.x; + float depth = texture2DProj( tDepth, vUv ).r; + float viewZ = getViewZ( depth ); + float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; + vec3 viewPosition=getViewPosition( uv, depth, clipW ); + vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; + if(worldPosition.y>maxDistance) discard; + float op=opacity; + #ifdef DISTANCE_ATTENUATION + float ratio=1.-(worldPosition.y/maxDistance); + float attenuation=ratio*ratio; + op=opacity*attenuation; #endif - } - `, - }; + #ifdef FRESNEL + op*=fresnelCoe; + #endif + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); + #else + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #endif + } + `, +}; - return ReflectorForSSRPass; +return ReflectorForSSRPass; } )(); diff --git a/examples/jsm/objects/Refractor.js b/examples/jsm/objects/Refractor.js index 2985702ba4a48d..e49130d223c818 100644 --- a/examples/jsm/objects/Refractor.js +++ b/examples/jsm/objects/Refractor.js @@ -15,315 +15,315 @@ import { const Refractor = /* @__PURE__ */ ( () => { - class Refractor extends Mesh { +class Refractor extends Mesh { - constructor( geometry, options = {} ) { + constructor( geometry, options = {} ) { - super( geometry ); + super( geometry ); - this.isRefractor = true; + this.isRefractor = true; - this.type = 'Refractor'; - this.camera = new PerspectiveCamera(); + this.type = 'Refractor'; + this.camera = new PerspectiveCamera(); - const scope = this; + const scope = this; - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const shader = options.shader || Refractor.RefractorShader; - const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const shader = options.shader || Refractor.RefractorShader; + const multisample = ( options.multisample !== undefined ) ? options.multisample : 4; - // + // - const virtualCamera = this.camera; - virtualCamera.matrixAutoUpdate = false; - virtualCamera.userData.refractor = true; + const virtualCamera = this.camera; + virtualCamera.matrixAutoUpdate = false; + virtualCamera.userData.refractor = true; - // + // - const refractorPlane = new Plane(); - const textureMatrix = new Matrix4(); + const refractorPlane = new Plane(); + const textureMatrix = new Matrix4(); - // render target + // render target - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); - // material + // material - this.material = new ShaderMaterial( { - uniforms: UniformsUtils.clone( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - transparent: true // ensures, refractors are drawn from farthest to closest - } ); + this.material = new ShaderMaterial( { + uniforms: UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + transparent: true // ensures, refractors are drawn from farthest to closest + } ); - this.material.uniforms[ 'color' ].value = color; - this.material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; - this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; + this.material.uniforms[ 'color' ].value = color; + this.material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; + this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; - // functions + // functions - const visible = ( function () { + const visible = ( function () { - const refractorWorldPosition = new Vector3(); - const cameraWorldPosition = new Vector3(); - const rotationMatrix = new Matrix4(); + const refractorWorldPosition = new Vector3(); + const cameraWorldPosition = new Vector3(); + const rotationMatrix = new Matrix4(); - const view = new Vector3(); - const normal = new Vector3(); + const view = new Vector3(); + const normal = new Vector3(); - return function visible( camera ) { + return function visible( camera ) { - refractorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); - cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + refractorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); - view.subVectors( refractorWorldPosition, cameraWorldPosition ); + view.subVectors( refractorWorldPosition, cameraWorldPosition ); - rotationMatrix.extractRotation( scope.matrixWorld ); + rotationMatrix.extractRotation( scope.matrixWorld ); - normal.set( 0, 0, 1 ); - normal.applyMatrix4( rotationMatrix ); + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); - return view.dot( normal ) < 0; + return view.dot( normal ) < 0; - }; - - } )(); - - const updateRefractorPlane = ( function () { + }; - const normal = new Vector3(); - const position = new Vector3(); - const quaternion = new Quaternion(); - const scale = new Vector3(); + } )(); - return function updateRefractorPlane() { + const updateRefractorPlane = ( function () { - scope.matrixWorld.decompose( position, quaternion, scale ); - normal.set( 0, 0, 1 ).applyQuaternion( quaternion ).normalize(); + const normal = new Vector3(); + const position = new Vector3(); + const quaternion = new Quaternion(); + const scale = new Vector3(); - // flip the normal because we want to cull everything above the plane + return function updateRefractorPlane() { - normal.negate(); + scope.matrixWorld.decompose( position, quaternion, scale ); + normal.set( 0, 0, 1 ).applyQuaternion( quaternion ).normalize(); - refractorPlane.setFromNormalAndCoplanarPoint( normal, position ); + // flip the normal because we want to cull everything above the plane - }; + normal.negate(); - } )(); + refractorPlane.setFromNormalAndCoplanarPoint( normal, position ); - const updateVirtualCamera = ( function () { + }; - const clipPlane = new Plane(); - const clipVector = new Vector4(); - const q = new Vector4(); + } )(); - return function updateVirtualCamera( camera ) { + const updateVirtualCamera = ( function () { - virtualCamera.matrixWorld.copy( camera.matrixWorld ); - virtualCamera.matrixWorldInverse.copy( virtualCamera.matrixWorld ).invert(); - virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); - virtualCamera.far = camera.far; // used in WebGLBackground + const clipPlane = new Plane(); + const clipVector = new Vector4(); + const q = new Vector4(); - // The following code creates an oblique view frustum for clipping. - // see: Lengyel, Eric. “Oblique View Frustum Depth Projection and Clipping”. - // Journal of Game Development, Vol. 1, No. 2 (2005), Charles River Media, pp. 5–16 + return function updateVirtualCamera( camera ) { - clipPlane.copy( refractorPlane ); - clipPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + virtualCamera.matrixWorld.copy( camera.matrixWorld ); + virtualCamera.matrixWorldInverse.copy( virtualCamera.matrixWorld ).invert(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.far = camera.far; // used in WebGLBackground - clipVector.set( clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant ); + // The following code creates an oblique view frustum for clipping. + // see: Lengyel, Eric. “Oblique View Frustum Depth Projection and Clipping”. + // Journal of Game Development, Vol. 1, No. 2 (2005), Charles River Media, pp. 5–16 - // calculate the clip-space corner point opposite the clipping plane and - // transform it into camera space by multiplying it by the inverse of the projection matrix + clipPlane.copy( refractorPlane ); + clipPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); - const projectionMatrix = virtualCamera.projectionMatrix; + clipVector.set( clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant ); - q.x = ( Math.sign( clipVector.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; - q.y = ( Math.sign( clipVector.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; - q.z = - 1.0; - q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + // calculate the clip-space corner point opposite the clipping plane and + // transform it into camera space by multiplying it by the inverse of the projection matrix - // calculate the scaled plane vector + const projectionMatrix = virtualCamera.projectionMatrix; - clipVector.multiplyScalar( 2.0 / clipVector.dot( q ) ); + q.x = ( Math.sign( clipVector.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + q.y = ( Math.sign( clipVector.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + q.z = - 1.0; + q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; - // replacing the third row of the projection matrix + // calculate the scaled plane vector - projectionMatrix.elements[ 2 ] = clipVector.x; - projectionMatrix.elements[ 6 ] = clipVector.y; - projectionMatrix.elements[ 10 ] = clipVector.z + 1.0 - clipBias; - projectionMatrix.elements[ 14 ] = clipVector.w; + clipVector.multiplyScalar( 2.0 / clipVector.dot( q ) ); - }; + // replacing the third row of the projection matrix - } )(); + projectionMatrix.elements[ 2 ] = clipVector.x; + projectionMatrix.elements[ 6 ] = clipVector.y; + projectionMatrix.elements[ 10 ] = clipVector.z + 1.0 - clipBias; + projectionMatrix.elements[ 14 ] = clipVector.w; - // This will update the texture matrix that is used for projective texture mapping in the shader. - // see: http://developer.download.nvidia.com/assets/gamedev/docs/projective_texture_mapping.pdf + }; - function updateTextureMatrix( camera ) { + } )(); - // this matrix does range mapping to [ 0, 1 ] + // This will update the texture matrix that is used for projective texture mapping in the shader. + // see: http://developer.download.nvidia.com/assets/gamedev/docs/projective_texture_mapping.pdf - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + function updateTextureMatrix( camera ) { - // we use "Object Linear Texgen", so we need to multiply the texture matrix T - // (matrix above) with the projection and view matrix of the virtual camera - // and the model matrix of the refractor + // this matrix does range mapping to [ 0, 1 ] - textureMatrix.multiply( camera.projectionMatrix ); - textureMatrix.multiply( camera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); - } + // we use "Object Linear Texgen", so we need to multiply the texture matrix T + // (matrix above) with the projection and view matrix of the virtual camera + // and the model matrix of the refractor - // + textureMatrix.multiply( camera.projectionMatrix ); + textureMatrix.multiply( camera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - function render( renderer, scene, camera ) { + } - scope.visible = false; + // - const currentRenderTarget = renderer.getRenderTarget(); - const currentXrEnabled = renderer.xr.enabled; - const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + function render( renderer, scene, camera ) { - renderer.xr.enabled = false; // avoid camera modification - renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows + scope.visible = false; - renderer.setRenderTarget( renderTarget ); - if ( renderer.autoClear === false ) renderer.clear(); - renderer.render( scene, virtualCamera ); + const currentRenderTarget = renderer.getRenderTarget(); + const currentXrEnabled = renderer.xr.enabled; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - renderer.xr.enabled = currentXrEnabled; - renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.setRenderTarget( currentRenderTarget ); + renderer.xr.enabled = false; // avoid camera modification + renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows - // restore viewport + renderer.setRenderTarget( renderTarget ); + if ( renderer.autoClear === false ) renderer.clear(); + renderer.render( scene, virtualCamera ); - const viewport = camera.viewport; + renderer.xr.enabled = currentXrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + renderer.setRenderTarget( currentRenderTarget ); - if ( viewport !== undefined ) { + // restore viewport - renderer.state.viewport( viewport ); + const viewport = camera.viewport; - } + if ( viewport !== undefined ) { - scope.visible = true; + renderer.state.viewport( viewport ); } - // + scope.visible = true; - this.onBeforeRender = function ( renderer, scene, camera ) { + } - // ensure refractors are rendered only once per frame + // - if ( camera.userData.refractor === true ) return; + this.onBeforeRender = function ( renderer, scene, camera ) { - // avoid rendering when the refractor is viewed from behind + // ensure refractors are rendered only once per frame - if ( ! visible( camera ) === true ) return; + if ( camera.userData.refractor === true ) return; - // update + // avoid rendering when the refractor is viewed from behind - updateRefractorPlane(); + if ( ! visible( camera ) === true ) return; - updateTextureMatrix( camera ); + // update - updateVirtualCamera( camera ); + updateRefractorPlane(); - render( renderer, scene, camera ); + updateTextureMatrix( camera ); - }; + updateVirtualCamera( camera ); - this.getRenderTarget = function () { + render( renderer, scene, camera ); - return renderTarget; + }; - }; + this.getRenderTarget = function () { - this.dispose = function () { + return renderTarget; - renderTarget.dispose(); - scope.material.dispose(); + }; - }; + this.dispose = function () { - } + renderTarget.dispose(); + scope.material.dispose(); - } + }; - Refractor.RefractorShader = { + } - uniforms: { +} - 'color': { - value: null - }, +Refractor.RefractorShader = { - 'tDiffuse': { - value: null - }, + uniforms: { - 'textureMatrix': { - value: null - } + 'color': { + value: null + }, + 'tDiffuse': { + value: null }, - vertexShader: /* glsl */` + 'textureMatrix': { + value: null + } - uniform mat4 textureMatrix; + }, - varying vec4 vUv; + vertexShader: /* glsl */` - void main() { + uniform mat4 textureMatrix; - vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + varying vec4 vUv; - }`, + void main() { - fragmentShader: /* glsl */` + vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - uniform vec3 color; - uniform sampler2D tDiffuse; + }`, - varying vec4 vUv; + fragmentShader: /* glsl */` - float blendOverlay( float base, float blend ) { + uniform vec3 color; + uniform sampler2D tDiffuse; - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + varying vec4 vUv; - } + float blendOverlay( float base, float blend ) { - vec3 blendOverlay( vec3 base, vec3 blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } - } + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } - void main() { + void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #include - #include + #include + #include - }` + }` - }; +}; - return Refractor; +return Refractor; } )(); diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index c41cc4bc7b8bab..d673402e892501 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -23,200 +23,200 @@ import { const Sky = /* @__PURE__ */ ( () => { - class Sky extends Mesh { +class Sky extends Mesh { - constructor() { + constructor() { - const shader = Sky.SkyShader; + const shader = Sky.SkyShader; - const material = new ShaderMaterial( { - name: 'SkyShader', - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, - uniforms: UniformsUtils.clone( shader.uniforms ), - side: BackSide, - depthWrite: false - } ); + const material = new ShaderMaterial( { + name: 'SkyShader', + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: UniformsUtils.clone( shader.uniforms ), + side: BackSide, + depthWrite: false + } ); - super( new BoxGeometry( 1, 1, 1 ), material ); + super( new BoxGeometry( 1, 1, 1 ), material ); - this.isSky = true; - - } + this.isSky = true; } - Sky.SkyShader = { - - uniforms: { - 'turbidity': { value: 2 }, - 'rayleigh': { value: 1 }, - 'mieCoefficient': { value: 0.005 }, - 'mieDirectionalG': { value: 0.8 }, - 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, - 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } - }, - - vertexShader: /* glsl */` - uniform vec3 sunPosition; - uniform float rayleigh; - uniform float turbidity; - uniform float mieCoefficient; - uniform vec3 up; - - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; - - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); - } +} + +Sky.SkyShader = { + + uniforms: { + 'turbidity': { value: 2 }, + 'rayleigh': { value: 1 }, + 'mieCoefficient': { value: 0.005 }, + 'mieDirectionalG': { value: 0.8 }, + 'sunPosition': { value: /* @__PURE__ */ new Vector3() }, + 'up': { value: /* @__PURE__ */ new Vector3( 0, 1, 0 ) } + }, + + vertexShader: /* glsl */` + uniform vec3 sunPosition; + uniform float rayleigh; + uniform float turbidity; + uniform float mieCoefficient; + uniform vec3 up; + + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; + + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; + + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); + + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); + + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; + + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } - void main() { + void main() { - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - vSunDirection = normalize( sunPosition ); + vSunDirection = normalize( sunPosition ); - vSunE = sunIntensity( dot( vSunDirection, up ) ); + vSunE = sunIntensity( dot( vSunDirection, up ) ); - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - }`, + }`, - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - uniform float mieDirectionalG; - uniform vec3 up; + uniform float mieDirectionalG; + uniform vec3 up; - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); + } - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - void main() { + void main() { - vec3 direction = normalize( vWorldPosition - cameraPosition ); + vec3 direction = normalize( vWorldPosition - cameraPosition ); - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - // in scattering - float cosTheta = dot( direction, vSunDirection ); + // in scattering + float cosTheta = dot( direction, vSunDirection ); - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - gl_FragColor = vec4( retColor, 1.0 ); + gl_FragColor = vec4( retColor, 1.0 ); - #include - #include + #include + #include - }` + }` - }; +}; - return Sky; +return Sky; } )(); diff --git a/examples/jsm/objects/Water2.js b/examples/jsm/objects/Water2.js index 6928c4c289bbcc..4f54c4e323e9a1 100644 --- a/examples/jsm/objects/Water2.js +++ b/examples/jsm/objects/Water2.js @@ -23,341 +23,341 @@ import { Refractor } from '../objects/Refractor.js'; const Water = /* @__PURE__ */ ( () => { - class Water extends Mesh { +class Water extends Mesh { - constructor( geometry, options = {} ) { + constructor( geometry, options = {} ) { - super( geometry ); + super( geometry ); - this.isWater = true; + this.isWater = true; - this.type = 'Water'; + this.type = 'Water'; - const scope = this; + const scope = this; - const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const flowDirection = options.flowDirection || new Vector2( 1, 0 ); - const flowSpeed = options.flowSpeed || 0.03; - const reflectivity = options.reflectivity || 0.02; - const scale = options.scale || 1; - const shader = options.shader || Water.WaterShader; + const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF ); + const textureWidth = options.textureWidth || 512; + const textureHeight = options.textureHeight || 512; + const clipBias = options.clipBias || 0; + const flowDirection = options.flowDirection || new Vector2( 1, 0 ); + const flowSpeed = options.flowSpeed || 0.03; + const reflectivity = options.reflectivity || 0.02; + const scale = options.scale || 1; + const shader = options.shader || Water.WaterShader; - const textureLoader = new TextureLoader(); + const textureLoader = new TextureLoader(); - const flowMap = options.flowMap || undefined; - const normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' ); - const normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' ); + const flowMap = options.flowMap || undefined; + const normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' ); + const normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' ); - const cycle = 0.15; // a cycle of a flow map phase - const halfCycle = cycle * 0.5; - const textureMatrix = new Matrix4(); - const clock = new Clock(); + const cycle = 0.15; // a cycle of a flow map phase + const halfCycle = cycle * 0.5; + const textureMatrix = new Matrix4(); + const clock = new Clock(); - // internal components + // internal components - if ( Reflector === undefined ) { + if ( Reflector === undefined ) { - console.error( 'THREE.Water: Required component Reflector not found.' ); - return; + console.error( 'THREE.Water: Required component Reflector not found.' ); + return; - } + } - if ( Refractor === undefined ) { + if ( Refractor === undefined ) { - console.error( 'THREE.Water: Required component Refractor not found.' ); - return; + console.error( 'THREE.Water: Required component Refractor not found.' ); + return; - } + } - const reflector = new Reflector( geometry, { - textureWidth: textureWidth, - textureHeight: textureHeight, - clipBias: clipBias - } ); - - const refractor = new Refractor( geometry, { - textureWidth: textureWidth, - textureHeight: textureHeight, - clipBias: clipBias - } ); - - reflector.matrixAutoUpdate = false; - refractor.matrixAutoUpdate = false; - - // material - - this.material = new ShaderMaterial( { - uniforms: UniformsUtils.merge( [ - UniformsLib[ 'fog' ], - shader.uniforms - ] ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - transparent: true, - fog: true - } ); - - if ( flowMap !== undefined ) { - - this.material.defines.USE_FLOWMAP = ''; - this.material.uniforms[ 'tFlowMap' ] = { - type: 't', - value: flowMap - }; - - } else { - - this.material.uniforms[ 'flowDirection' ] = { - type: 'v2', - value: flowDirection - }; + const reflector = new Reflector( geometry, { + textureWidth: textureWidth, + textureHeight: textureHeight, + clipBias: clipBias + } ); + + const refractor = new Refractor( geometry, { + textureWidth: textureWidth, + textureHeight: textureHeight, + clipBias: clipBias + } ); + + reflector.matrixAutoUpdate = false; + refractor.matrixAutoUpdate = false; + + // material + + this.material = new ShaderMaterial( { + uniforms: UniformsUtils.merge( [ + UniformsLib[ 'fog' ], + shader.uniforms + ] ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + transparent: true, + fog: true + } ); + + if ( flowMap !== undefined ) { + + this.material.defines.USE_FLOWMAP = ''; + this.material.uniforms[ 'tFlowMap' ] = { + type: 't', + value: flowMap + }; - } + } else { + + this.material.uniforms[ 'flowDirection' ] = { + type: 'v2', + value: flowDirection + }; - // maps + } - normalMap0.wrapS = normalMap0.wrapT = RepeatWrapping; - normalMap1.wrapS = normalMap1.wrapT = RepeatWrapping; + // maps - this.material.uniforms[ 'tReflectionMap' ].value = reflector.getRenderTarget().texture; - this.material.uniforms[ 'tRefractionMap' ].value = refractor.getRenderTarget().texture; - this.material.uniforms[ 'tNormalMap0' ].value = normalMap0; - this.material.uniforms[ 'tNormalMap1' ].value = normalMap1; + normalMap0.wrapS = normalMap0.wrapT = RepeatWrapping; + normalMap1.wrapS = normalMap1.wrapT = RepeatWrapping; - // water + this.material.uniforms[ 'tReflectionMap' ].value = reflector.getRenderTarget().texture; + this.material.uniforms[ 'tRefractionMap' ].value = refractor.getRenderTarget().texture; + this.material.uniforms[ 'tNormalMap0' ].value = normalMap0; + this.material.uniforms[ 'tNormalMap1' ].value = normalMap1; - this.material.uniforms[ 'color' ].value = color; - this.material.uniforms[ 'reflectivity' ].value = reflectivity; - this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; + // water - // inital values + this.material.uniforms[ 'color' ].value = color; + this.material.uniforms[ 'reflectivity' ].value = reflectivity; + this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; - this.material.uniforms[ 'config' ].value.x = 0; // flowMapOffset0 - this.material.uniforms[ 'config' ].value.y = halfCycle; // flowMapOffset1 - this.material.uniforms[ 'config' ].value.z = halfCycle; // halfCycle - this.material.uniforms[ 'config' ].value.w = scale; // scale + // inital values - // functions + this.material.uniforms[ 'config' ].value.x = 0; // flowMapOffset0 + this.material.uniforms[ 'config' ].value.y = halfCycle; // flowMapOffset1 + this.material.uniforms[ 'config' ].value.z = halfCycle; // halfCycle + this.material.uniforms[ 'config' ].value.w = scale; // scale - function updateTextureMatrix( camera ) { + // functions - textureMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + function updateTextureMatrix( camera ) { - textureMatrix.multiply( camera.projectionMatrix ); - textureMatrix.multiply( camera.matrixWorldInverse ); - textureMatrix.multiply( scope.matrixWorld ); + textureMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); - } + textureMatrix.multiply( camera.projectionMatrix ); + textureMatrix.multiply( camera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); - function updateFlow() { + } - const delta = clock.getDelta(); - const config = scope.material.uniforms[ 'config' ]; + function updateFlow() { - config.value.x += flowSpeed * delta; // flowMapOffset0 - config.value.y = config.value.x + halfCycle; // flowMapOffset1 + const delta = clock.getDelta(); + const config = scope.material.uniforms[ 'config' ]; - // Important: The distance between offsets should be always the value of "halfCycle". - // Moreover, both offsets should be in the range of [ 0, cycle ]. - // This approach ensures a smooth water flow and avoids "reset" effects. + config.value.x += flowSpeed * delta; // flowMapOffset0 + config.value.y = config.value.x + halfCycle; // flowMapOffset1 - if ( config.value.x >= cycle ) { + // Important: The distance between offsets should be always the value of "halfCycle". + // Moreover, both offsets should be in the range of [ 0, cycle ]. + // This approach ensures a smooth water flow and avoids "reset" effects. - config.value.x = 0; - config.value.y = halfCycle; + if ( config.value.x >= cycle ) { - } else if ( config.value.y >= cycle ) { + config.value.x = 0; + config.value.y = halfCycle; - config.value.y = config.value.y - cycle; + } else if ( config.value.y >= cycle ) { - } + config.value.y = config.value.y - cycle; } - // + } - this.onBeforeRender = function ( renderer, scene, camera ) { + // - updateTextureMatrix( camera ); - updateFlow(); + this.onBeforeRender = function ( renderer, scene, camera ) { - scope.visible = false; + updateTextureMatrix( camera ); + updateFlow(); - reflector.matrixWorld.copy( scope.matrixWorld ); - refractor.matrixWorld.copy( scope.matrixWorld ); + scope.visible = false; - reflector.onBeforeRender( renderer, scene, camera ); - refractor.onBeforeRender( renderer, scene, camera ); + reflector.matrixWorld.copy( scope.matrixWorld ); + refractor.matrixWorld.copy( scope.matrixWorld ); - scope.visible = true; + reflector.onBeforeRender( renderer, scene, camera ); + refractor.onBeforeRender( renderer, scene, camera ); - }; + scope.visible = true; - } + }; } - Water.WaterShader = { - - uniforms: { +} - 'color': { - type: 'c', - value: null - }, +Water.WaterShader = { - 'reflectivity': { - type: 'f', - value: 0 - }, + uniforms: { - 'tReflectionMap': { - type: 't', - value: null - }, + 'color': { + type: 'c', + value: null + }, - 'tRefractionMap': { - type: 't', - value: null - }, + 'reflectivity': { + type: 'f', + value: 0 + }, - 'tNormalMap0': { - type: 't', - value: null - }, + 'tReflectionMap': { + type: 't', + value: null + }, - 'tNormalMap1': { - type: 't', - value: null - }, + 'tRefractionMap': { + type: 't', + value: null + }, - 'textureMatrix': { - type: 'm4', - value: null - }, + 'tNormalMap0': { + type: 't', + value: null + }, - 'config': { - type: 'v4', - value: /* @__PURE__ */ new Vector4() - } + 'tNormalMap1': { + type: 't', + value: null + }, + 'textureMatrix': { + type: 'm4', + value: null }, - vertexShader: /* glsl */` + 'config': { + type: 'v4', + value: /* @__PURE__ */ new Vector4() + } - #include - #include - #include + }, - uniform mat4 textureMatrix; + vertexShader: /* glsl */` - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; + #include + #include + #include - void main() { + uniform mat4 textureMatrix; - vUv = uv; - vCoord = textureMatrix * vec4( position, 1.0 ); + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vToEye = cameraPosition - worldPosition.xyz; + void main() { - vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex - gl_Position = projectionMatrix * mvPosition; + vUv = uv; + vCoord = textureMatrix * vec4( position, 1.0 ); - #include - #include + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vToEye = cameraPosition - worldPosition.xyz; - }`, + vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex + gl_Position = projectionMatrix * mvPosition; - fragmentShader: /* glsl */` + #include + #include - #include - #include - #include + }`, - uniform sampler2D tReflectionMap; - uniform sampler2D tRefractionMap; - uniform sampler2D tNormalMap0; - uniform sampler2D tNormalMap1; + fragmentShader: /* glsl */` - #ifdef USE_FLOWMAP - uniform sampler2D tFlowMap; - #else - uniform vec2 flowDirection; - #endif + #include + #include + #include + + uniform sampler2D tReflectionMap; + uniform sampler2D tRefractionMap; + uniform sampler2D tNormalMap0; + uniform sampler2D tNormalMap1; - uniform vec3 color; - uniform float reflectivity; - uniform vec4 config; + #ifdef USE_FLOWMAP + uniform sampler2D tFlowMap; + #else + uniform vec2 flowDirection; + #endif - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; + uniform vec3 color; + uniform float reflectivity; + uniform vec4 config; - void main() { + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; - #include + void main() { - float flowMapOffset0 = config.x; - float flowMapOffset1 = config.y; - float halfCycle = config.z; - float scale = config.w; + #include - vec3 toEye = normalize( vToEye ); + float flowMapOffset0 = config.x; + float flowMapOffset1 = config.y; + float halfCycle = config.z; + float scale = config.w; - // determine flow direction - vec2 flow; - #ifdef USE_FLOWMAP - flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; - #else - flow = flowDirection; - #endif - flow.x *= - 1.0; + vec3 toEye = normalize( vToEye ); + + // determine flow direction + vec2 flow; + #ifdef USE_FLOWMAP + flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; + #else + flow = flowDirection; + #endif + flow.x *= - 1.0; - // sample normal maps (distort uvs with flowdata) - vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); - vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); + // sample normal maps (distort uvs with flowdata) + vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); + vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); - // linear interpolate to get the final normal color - float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; - vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); + // linear interpolate to get the final normal color + float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; + vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); - // calculate normal vector - vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); + // calculate normal vector + vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); - // calculate the fresnel term to blend reflection and refraction maps - float theta = max( dot( toEye, normal ), 0.0 ); - float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); + // calculate the fresnel term to blend reflection and refraction maps + float theta = max( dot( toEye, normal ), 0.0 ); + float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); - // calculate final uv coords - vec3 coord = vCoord.xyz / vCoord.w; - vec2 uv = coord.xy + coord.z * normal.xz * 0.05; + // calculate final uv coords + vec3 coord = vCoord.xyz / vCoord.w; + vec2 uv = coord.xy + coord.z * normal.xz * 0.05; - vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); - vec4 refractColor = texture2D( tRefractionMap, uv ); + vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); + vec4 refractColor = texture2D( tRefractionMap, uv ); - // multiply water color with the mix of both textures - gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); + // multiply water color with the mix of both textures + gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); - #include - #include - #include + #include + #include + #include - }` + }` - }; +}; - return Water; +return Water; } )(); diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index 19b284ae920f63..905102337b6c54 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -11,167 +11,167 @@ import { ConvolutionShader } from '../shaders/ConvolutionShader.js'; const BloomPass = /* @__PURE__ */ ( () => { - class BloomPass extends Pass { +class BloomPass extends Pass { - constructor( strength = 1, kernelSize = 25, sigma = 4 ) { + constructor( strength = 1, kernelSize = 25, sigma = 4 ) { - super(); + super(); - // render targets + // render targets - this.renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later - this.renderTargetX.texture.name = 'BloomPass.x'; - this.renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later - this.renderTargetY.texture.name = 'BloomPass.y'; + this.renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this.renderTargetX.texture.name = 'BloomPass.x'; + this.renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this.renderTargetY.texture.name = 'BloomPass.y'; - // combine material + // combine material - this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms ); + this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms ); - this.combineUniforms[ 'strength' ].value = strength; + this.combineUniforms[ 'strength' ].value = strength; - this.materialCombine = new ShaderMaterial( { + this.materialCombine = new ShaderMaterial( { - name: CombineShader.name, - uniforms: this.combineUniforms, - vertexShader: CombineShader.vertexShader, - fragmentShader: CombineShader.fragmentShader, - blending: AdditiveBlending, - transparent: true + name: CombineShader.name, + uniforms: this.combineUniforms, + vertexShader: CombineShader.vertexShader, + fragmentShader: CombineShader.fragmentShader, + blending: AdditiveBlending, + transparent: true - } ); + } ); - // convolution material + // convolution material - const convolutionShader = ConvolutionShader; + const convolutionShader = ConvolutionShader; - this.convolutionUniforms = UniformsUtils.clone( convolutionShader.uniforms ); + this.convolutionUniforms = UniformsUtils.clone( convolutionShader.uniforms ); - this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; - this.convolutionUniforms[ 'cKernel' ].value = ConvolutionShader.buildKernel( sigma ); + this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; + this.convolutionUniforms[ 'cKernel' ].value = ConvolutionShader.buildKernel( sigma ); - this.materialConvolution = new ShaderMaterial( { + this.materialConvolution = new ShaderMaterial( { - name: convolutionShader.name, - uniforms: this.convolutionUniforms, - vertexShader: convolutionShader.vertexShader, - fragmentShader: convolutionShader.fragmentShader, - defines: { - 'KERNEL_SIZE_FLOAT': kernelSize.toFixed( 1 ), - 'KERNEL_SIZE_INT': kernelSize.toFixed( 0 ) - } + name: convolutionShader.name, + uniforms: this.convolutionUniforms, + vertexShader: convolutionShader.vertexShader, + fragmentShader: convolutionShader.fragmentShader, + defines: { + 'KERNEL_SIZE_FLOAT': kernelSize.toFixed( 1 ), + 'KERNEL_SIZE_INT': kernelSize.toFixed( 0 ) + } - } ); + } ); - this.needsSwap = false; + this.needsSwap = false; - this.fsQuad = new FullScreenQuad( null ); + this.fsQuad = new FullScreenQuad( null ); - } - - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + } - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - // Render quad with blured scene into texture (convolution pass 1) + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - this.fsQuad.material = this.materialConvolution; + // Render quad with blured scene into texture (convolution pass 1) - this.convolutionUniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; + this.fsQuad.material = this.materialConvolution; - renderer.setRenderTarget( this.renderTargetX ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.convolutionUniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; + renderer.setRenderTarget( this.renderTargetX ); + renderer.clear(); + this.fsQuad.render( renderer ); - // Render quad with blured scene into texture (convolution pass 2) - this.convolutionUniforms[ 'tDiffuse' ].value = this.renderTargetX.texture; - this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurY; + // Render quad with blured scene into texture (convolution pass 2) - renderer.setRenderTarget( this.renderTargetY ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.convolutionUniforms[ 'tDiffuse' ].value = this.renderTargetX.texture; + this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurY; - // Render original scene with superimposed blur to texture + renderer.setRenderTarget( this.renderTargetY ); + renderer.clear(); + this.fsQuad.render( renderer ); - this.fsQuad.material = this.materialCombine; + // Render original scene with superimposed blur to texture - this.combineUniforms[ 'tDiffuse' ].value = this.renderTargetY.texture; + this.fsQuad.material = this.materialCombine; - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + this.combineUniforms[ 'tDiffuse' ].value = this.renderTargetY.texture; - renderer.setRenderTarget( readBuffer ); - if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - } + renderer.setRenderTarget( readBuffer ); + if ( this.clear ) renderer.clear(); + this.fsQuad.render( renderer ); - setSize( width, height ) { + } - this.renderTargetX.setSize( width, height ); - this.renderTargetY.setSize( width, height ); + setSize( width, height ) { - } + this.renderTargetX.setSize( width, height ); + this.renderTargetY.setSize( width, height ); - dispose() { + } - this.renderTargetX.dispose(); - this.renderTargetY.dispose(); + dispose() { - this.materialCombine.dispose(); - this.materialConvolution.dispose(); + this.renderTargetX.dispose(); + this.renderTargetY.dispose(); - this.fsQuad.dispose(); + this.materialCombine.dispose(); + this.materialConvolution.dispose(); - } + this.fsQuad.dispose(); } - const CombineShader = { +} + +const CombineShader = { - name: 'CombineShader', + name: 'CombineShader', - uniforms: { + uniforms: { - 'tDiffuse': { value: null }, - 'strength': { value: 1.0 } + 'tDiffuse': { value: null }, + 'strength': { value: 1.0 } - }, + }, - vertexShader: /* glsl */` + vertexShader: /* glsl */` - varying vec2 vUv; + varying vec2 vUv; - void main() { + void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, + }`, - fragmentShader: /* glsl */` + fragmentShader: /* glsl */` - uniform float strength; + uniform float strength; - uniform sampler2D tDiffuse; + uniform sampler2D tDiffuse; - varying vec2 vUv; + varying vec2 vUv; - void main() { + void main() { - vec4 texel = texture2D( tDiffuse, vUv ); - gl_FragColor = strength * texel; + vec4 texel = texture2D( tDiffuse, vUv ); + gl_FragColor = strength * texel; - }` + }` - }; +}; - BloomPass.blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); - BloomPass.blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); +BloomPass.blurX = /* @__PURE__ */ new Vector2( 0.001953125, 0.0 ); +BloomPass.blurY = /* @__PURE__ */ new Vector2( 0.0, 0.001953125 ); - return BloomPass; +return BloomPass; } )(); diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index df855293233acb..9169f39fcde615 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -18,642 +18,642 @@ import { CopyShader } from '../shaders/CopyShader.js'; const OutlinePass = /* @__PURE__ */ ( () => { - class OutlinePass extends Pass { +class OutlinePass extends Pass { - constructor( resolution, scene, camera, selectedObjects ) { + constructor( resolution, scene, camera, selectedObjects ) { - super(); + super(); - this.renderScene = scene; - this.renderCamera = camera; - this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; - this.visibleEdgeColor = new Color( 1, 1, 1 ); - this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 ); - this.edgeGlow = 0.0; - this.usePatternTexture = false; - this.edgeThickness = 1.0; - this.edgeStrength = 3.0; - this.downSampleRatio = 2; - this.pulsePeriod = 0; + this.renderScene = scene; + this.renderCamera = camera; + this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; + this.visibleEdgeColor = new Color( 1, 1, 1 ); + this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 ); + this.edgeGlow = 0.0; + this.usePatternTexture = false; + this.edgeThickness = 1.0; + this.edgeStrength = 3.0; + this.downSampleRatio = 2; + this.pulsePeriod = 0; - this._visibilityCache = new Map(); + this._visibilityCache = new Map(); - this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); + this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - const resx = Math.round( this.resolution.x / this.downSampleRatio ); - const resy = Math.round( this.resolution.y / this.downSampleRatio ); + const resx = Math.round( this.resolution.x / this.downSampleRatio ); + const resy = Math.round( this.resolution.y / this.downSampleRatio ); - this.renderTargetMaskBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y ); - this.renderTargetMaskBuffer.texture.name = 'OutlinePass.mask'; - this.renderTargetMaskBuffer.texture.generateMipmaps = false; + this.renderTargetMaskBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y ); + this.renderTargetMaskBuffer.texture.name = 'OutlinePass.mask'; + this.renderTargetMaskBuffer.texture.generateMipmaps = false; - this.depthMaterial = new MeshDepthMaterial(); - this.depthMaterial.side = DoubleSide; - this.depthMaterial.depthPacking = RGBADepthPacking; - this.depthMaterial.blending = NoBlending; + this.depthMaterial = new MeshDepthMaterial(); + this.depthMaterial.side = DoubleSide; + this.depthMaterial.depthPacking = RGBADepthPacking; + this.depthMaterial.blending = NoBlending; - this.prepareMaskMaterial = this.getPrepareMaskMaterial(); - this.prepareMaskMaterial.side = DoubleSide; - this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); + this.prepareMaskMaterial = this.getPrepareMaskMaterial(); + this.prepareMaskMaterial.side = DoubleSide; + this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); - this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); - this.renderTargetDepthBuffer.texture.name = 'OutlinePass.depth'; - this.renderTargetDepthBuffer.texture.generateMipmaps = false; + this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); + this.renderTargetDepthBuffer.texture.name = 'OutlinePass.depth'; + this.renderTargetDepthBuffer.texture.generateMipmaps = false; - this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetMaskDownSampleBuffer.texture.name = 'OutlinePass.depthDownSample'; - this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; + this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetMaskDownSampleBuffer.texture.name = 'OutlinePass.depthDownSample'; + this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; - this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1'; - this.renderTargetBlurBuffer1.texture.generateMipmaps = false; - this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); - this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2'; - this.renderTargetBlurBuffer2.texture.generateMipmaps = false; + this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1'; + this.renderTargetBlurBuffer1.texture.generateMipmaps = false; + this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); + this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2'; + this.renderTargetBlurBuffer2.texture.generateMipmaps = false; - this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); - this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1'; - this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; - this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); - this.renderTargetEdgeBuffer2.texture.name = 'OutlinePass.edge2'; - this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; + this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); + this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1'; + this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; + this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); + this.renderTargetEdgeBuffer2.texture.name = 'OutlinePass.edge2'; + this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; - const MAX_EDGE_THICKNESS = 4; - const MAX_EDGE_GLOW = 4; + const MAX_EDGE_THICKNESS = 4; + const MAX_EDGE_GLOW = 4; - this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); - this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); - this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1; - this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); - this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) ); - this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW; + this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); + this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); + this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1; + this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); + this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) ); + this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW; - // Overlay material - this.overlayMaterial = this.getOverlayMaterial(); + // Overlay material + this.overlayMaterial = this.getOverlayMaterial(); - // copy material + // copy material - const copyShader = CopyShader; + const copyShader = CopyShader; - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - this.materialCopy = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: NoBlending, - depthTest: false, - depthWrite: false - } ); + this.materialCopy = new ShaderMaterial( { + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + blending: NoBlending, + depthTest: false, + depthWrite: false + } ); - this.enabled = true; - this.needsSwap = false; + this.enabled = true; + this.needsSwap = false; - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this._oldClearColor = new Color(); + this.oldClearAlpha = 1; - this.fsQuad = new FullScreenQuad( null ); + this.fsQuad = new FullScreenQuad( null ); - this.tempPulseColor1 = new Color(); - this.tempPulseColor2 = new Color(); - this.textureMatrix = new Matrix4(); + this.tempPulseColor1 = new Color(); + this.tempPulseColor2 = new Color(); + this.textureMatrix = new Matrix4(); - function replaceDepthToViewZ( string, camera ) { + function replaceDepthToViewZ( string, camera ) { - const type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic'; + const type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic'; - return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' ); - - } + return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' ); } - dispose() { + } - this.renderTargetMaskBuffer.dispose(); - this.renderTargetDepthBuffer.dispose(); - this.renderTargetMaskDownSampleBuffer.dispose(); - this.renderTargetBlurBuffer1.dispose(); - this.renderTargetBlurBuffer2.dispose(); - this.renderTargetEdgeBuffer1.dispose(); - this.renderTargetEdgeBuffer2.dispose(); + dispose() { - this.depthMaterial.dispose(); - this.prepareMaskMaterial.dispose(); - this.edgeDetectionMaterial.dispose(); - this.separableBlurMaterial1.dispose(); - this.separableBlurMaterial2.dispose(); - this.overlayMaterial.dispose(); - this.materialCopy.dispose(); + this.renderTargetMaskBuffer.dispose(); + this.renderTargetDepthBuffer.dispose(); + this.renderTargetMaskDownSampleBuffer.dispose(); + this.renderTargetBlurBuffer1.dispose(); + this.renderTargetBlurBuffer2.dispose(); + this.renderTargetEdgeBuffer1.dispose(); + this.renderTargetEdgeBuffer2.dispose(); - this.fsQuad.dispose(); + this.depthMaterial.dispose(); + this.prepareMaskMaterial.dispose(); + this.edgeDetectionMaterial.dispose(); + this.separableBlurMaterial1.dispose(); + this.separableBlurMaterial2.dispose(); + this.overlayMaterial.dispose(); + this.materialCopy.dispose(); - } + this.fsQuad.dispose(); - setSize( width, height ) { + } - this.renderTargetMaskBuffer.setSize( width, height ); - this.renderTargetDepthBuffer.setSize( width, height ); + setSize( width, height ) { - let resx = Math.round( width / this.downSampleRatio ); - let resy = Math.round( height / this.downSampleRatio ); - this.renderTargetMaskDownSampleBuffer.setSize( resx, resy ); - this.renderTargetBlurBuffer1.setSize( resx, resy ); - this.renderTargetEdgeBuffer1.setSize( resx, resy ); - this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); + this.renderTargetMaskBuffer.setSize( width, height ); + this.renderTargetDepthBuffer.setSize( width, height ); - resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); + let resx = Math.round( width / this.downSampleRatio ); + let resy = Math.round( height / this.downSampleRatio ); + this.renderTargetMaskDownSampleBuffer.setSize( resx, resy ); + this.renderTargetBlurBuffer1.setSize( resx, resy ); + this.renderTargetEdgeBuffer1.setSize( resx, resy ); + this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); - this.renderTargetBlurBuffer2.setSize( resx, resy ); - this.renderTargetEdgeBuffer2.setSize( resx, resy ); + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); - this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( resx, resy ); + this.renderTargetBlurBuffer2.setSize( resx, resy ); + this.renderTargetEdgeBuffer2.setSize( resx, resy ); - } + this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( resx, resy ); - changeVisibilityOfSelectedObjects( bVisible ) { + } - const cache = this._visibilityCache; + changeVisibilityOfSelectedObjects( bVisible ) { - function gatherSelectedMeshesCallBack( object ) { + const cache = this._visibilityCache; - if ( object.isMesh ) { + function gatherSelectedMeshesCallBack( object ) { - if ( bVisible === true ) { + if ( object.isMesh ) { - object.visible = cache.get( object ); + if ( bVisible === true ) { - } else { + object.visible = cache.get( object ); - cache.set( object, object.visible ); - object.visible = bVisible; + } else { - } + cache.set( object, object.visible ); + object.visible = bVisible; } } - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + } - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - } + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( gatherSelectedMeshesCallBack ); } - changeVisibilityOfNonSelectedObjects( bVisible ) { + } - const cache = this._visibilityCache; - const selectedMeshes = []; + changeVisibilityOfNonSelectedObjects( bVisible ) { - function gatherSelectedMeshesCallBack( object ) { + const cache = this._visibilityCache; + const selectedMeshes = []; - if ( object.isMesh ) selectedMeshes.push( object ); + function gatherSelectedMeshesCallBack( object ) { - } + if ( object.isMesh ) selectedMeshes.push( object ); - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + } - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - } + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( gatherSelectedMeshesCallBack ); - function VisibilityChangeCallBack( object ) { + } - if ( object.isMesh || object.isSprite ) { + function VisibilityChangeCallBack( object ) { - // only meshes and sprites are supported by OutlinePass + if ( object.isMesh || object.isSprite ) { - let bFound = false; + // only meshes and sprites are supported by OutlinePass - for ( let i = 0; i < selectedMeshes.length; i ++ ) { + let bFound = false; - const selectedObjectId = selectedMeshes[ i ].id; + for ( let i = 0; i < selectedMeshes.length; i ++ ) { - if ( selectedObjectId === object.id ) { + const selectedObjectId = selectedMeshes[ i ].id; - bFound = true; - break; + if ( selectedObjectId === object.id ) { - } + bFound = true; + break; } - if ( bFound === false ) { - - const visibility = object.visible; + } - if ( bVisible === false || cache.get( object ) === true ) { + if ( bFound === false ) { - object.visible = bVisible; + const visibility = object.visible; - } + if ( bVisible === false || cache.get( object ) === true ) { - cache.set( object, visibility ); + object.visible = bVisible; } - } else if ( object.isPoints || object.isLine ) { + cache.set( object, visibility ); - // the visibilty of points and lines is always set to false in order to - // not affect the outline computation + } - if ( bVisible === true ) { + } else if ( object.isPoints || object.isLine ) { - object.visible = cache.get( object ); // restore + // the visibilty of points and lines is always set to false in order to + // not affect the outline computation - } else { + if ( bVisible === true ) { - cache.set( object, object.visible ); - object.visible = bVisible; + object.visible = cache.get( object ); // restore - } + } else { + + cache.set( object, object.visible ); + object.visible = bVisible; } } - this.renderScene.traverse( VisibilityChangeCallBack ); - } - updateTextureMatrix() { + this.renderScene.traverse( VisibilityChangeCallBack ); - this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 ); - this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); - this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); + } - } + updateTextureMatrix() { - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 ); + this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); + this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); - if ( this.selectedObjects.length > 0 ) { + } - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - renderer.autoClear = false; + if ( this.selectedObjects.length > 0 ) { - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + renderer.getClearColor( this._oldClearColor ); + this.oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; - renderer.setClearColor( 0xffffff, 1 ); + renderer.autoClear = false; - // Make selected objects invisible - this.changeVisibilityOfSelectedObjects( false ); + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - const currentBackground = this.renderScene.background; - this.renderScene.background = null; + renderer.setClearColor( 0xffffff, 1 ); - // 1. Draw Non Selected objects in the depth buffer - this.renderScene.overrideMaterial = this.depthMaterial; - renderer.setRenderTarget( this.renderTargetDepthBuffer ); - renderer.clear(); - renderer.render( this.renderScene, this.renderCamera ); + // Make selected objects invisible + this.changeVisibilityOfSelectedObjects( false ); - // Make selected objects visible - this.changeVisibilityOfSelectedObjects( true ); - this._visibilityCache.clear(); + const currentBackground = this.renderScene.background; + this.renderScene.background = null; - // Update Texture Matrix for Depth compare - this.updateTextureMatrix(); + // 1. Draw Non Selected objects in the depth buffer + this.renderScene.overrideMaterial = this.depthMaterial; + renderer.setRenderTarget( this.renderTargetDepthBuffer ); + renderer.clear(); + renderer.render( this.renderScene, this.renderCamera ); - // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects - this.changeVisibilityOfNonSelectedObjects( false ); - this.renderScene.overrideMaterial = this.prepareMaskMaterial; - this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far ); - this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture; - this.prepareMaskMaterial.uniforms[ 'textureMatrix' ].value = this.textureMatrix; - renderer.setRenderTarget( this.renderTargetMaskBuffer ); - renderer.clear(); - renderer.render( this.renderScene, this.renderCamera ); - this.renderScene.overrideMaterial = null; - this.changeVisibilityOfNonSelectedObjects( true ); - this._visibilityCache.clear(); + // Make selected objects visible + this.changeVisibilityOfSelectedObjects( true ); + this._visibilityCache.clear(); - this.renderScene.background = currentBackground; + // Update Texture Matrix for Depth compare + this.updateTextureMatrix(); - // 2. Downsample to Half resolution - this.fsQuad.material = this.materialCopy; - this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture; - renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer ); - renderer.clear(); - this.fsQuad.render( renderer ); + // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects + this.changeVisibilityOfNonSelectedObjects( false ); + this.renderScene.overrideMaterial = this.prepareMaskMaterial; + this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far ); + this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture; + this.prepareMaskMaterial.uniforms[ 'textureMatrix' ].value = this.textureMatrix; + renderer.setRenderTarget( this.renderTargetMaskBuffer ); + renderer.clear(); + renderer.render( this.renderScene, this.renderCamera ); + this.renderScene.overrideMaterial = null; + this.changeVisibilityOfNonSelectedObjects( true ); + this._visibilityCache.clear(); - this.tempPulseColor1.copy( this.visibleEdgeColor ); - this.tempPulseColor2.copy( this.hiddenEdgeColor ); + this.renderScene.background = currentBackground; - if ( this.pulsePeriod > 0 ) { + // 2. Downsample to Half resolution + this.fsQuad.material = this.materialCopy; + this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture; + renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer ); + renderer.clear(); + this.fsQuad.render( renderer ); - const scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2; - this.tempPulseColor1.multiplyScalar( scalar ); - this.tempPulseColor2.multiplyScalar( scalar ); + this.tempPulseColor1.copy( this.visibleEdgeColor ); + this.tempPulseColor2.copy( this.hiddenEdgeColor ); - } + if ( this.pulsePeriod > 0 ) { - // 3. Apply Edge Detection Pass - this.fsQuad.material = this.edgeDetectionMaterial; - this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture; - this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); - this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1; - this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2; - renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); - renderer.clear(); - this.fsQuad.render( renderer ); - - // 4. Apply Blur on Half res - this.fsQuad.material = this.separableBlurMaterial1; - this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; - this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; - this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness; - renderer.setRenderTarget( this.renderTargetBlurBuffer1 ); - renderer.clear(); - this.fsQuad.render( renderer ); - this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture; - this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); - renderer.clear(); - this.fsQuad.render( renderer ); - - // Apply Blur on quarter res - this.fsQuad.material = this.separableBlurMaterial2; - this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; - this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; - renderer.setRenderTarget( this.renderTargetBlurBuffer2 ); - renderer.clear(); - this.fsQuad.render( renderer ); - this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture; - this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetEdgeBuffer2 ); - renderer.clear(); - this.fsQuad.render( renderer ); - - // Blend it additively over the input texture - this.fsQuad.material = this.overlayMaterial; - this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture; - this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture; - this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture; - this.overlayMaterial.uniforms[ 'patternTexture' ].value = this.patternTexture; - this.overlayMaterial.uniforms[ 'edgeStrength' ].value = this.edgeStrength; - this.overlayMaterial.uniforms[ 'edgeGlow' ].value = this.edgeGlow; - this.overlayMaterial.uniforms[ 'usePatternTexture' ].value = this.usePatternTexture; - - - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - - renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); - - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; + const scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2; + this.tempPulseColor1.multiplyScalar( scalar ); + this.tempPulseColor2.multiplyScalar( scalar ); } - if ( this.renderToScreen ) { + // 3. Apply Edge Detection Pass + this.fsQuad.material = this.edgeDetectionMaterial; + this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture; + this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); + this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1; + this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2; + renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); + renderer.clear(); + this.fsQuad.render( renderer ); + + // 4. Apply Blur on Half res + this.fsQuad.material = this.separableBlurMaterial1; + this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; + this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; + this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness; + renderer.setRenderTarget( this.renderTargetBlurBuffer1 ); + renderer.clear(); + this.fsQuad.render( renderer ); + this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture; + this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); + renderer.clear(); + this.fsQuad.render( renderer ); + + // Apply Blur on quarter res + this.fsQuad.material = this.separableBlurMaterial2; + this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; + this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; + renderer.setRenderTarget( this.renderTargetBlurBuffer2 ); + renderer.clear(); + this.fsQuad.render( renderer ); + this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture; + this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetEdgeBuffer2 ); + renderer.clear(); + this.fsQuad.render( renderer ); + + // Blend it additively over the input texture + this.fsQuad.material = this.overlayMaterial; + this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture; + this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture; + this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture; + this.overlayMaterial.uniforms[ 'patternTexture' ].value = this.patternTexture; + this.overlayMaterial.uniforms[ 'edgeStrength' ].value = this.edgeStrength; + this.overlayMaterial.uniforms[ 'edgeGlow' ].value = this.edgeGlow; + this.overlayMaterial.uniforms[ 'usePatternTexture' ].value = this.usePatternTexture; + + + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + + renderer.setRenderTarget( readBuffer ); + this.fsQuad.render( renderer ); + + renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.autoClear = oldAutoClear; - this.fsQuad.material = this.materialCopy; - this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture; - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + } - } + if ( this.renderToScreen ) { + + this.fsQuad.material = this.materialCopy; + this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture; + renderer.setRenderTarget( null ); + this.fsQuad.render( renderer ); } - getPrepareMaskMaterial() { + } - return new ShaderMaterial( { + getPrepareMaskMaterial() { - uniforms: { - 'depthTexture': { value: null }, - 'cameraNearFar': { value: new Vector2( 0.5, 0.5 ) }, - 'textureMatrix': { value: null } - }, + return new ShaderMaterial( { - vertexShader: - `#include - #include + uniforms: { + 'depthTexture': { value: null }, + 'cameraNearFar': { value: new Vector2( 0.5, 0.5 ) }, + 'textureMatrix': { value: null } + }, - varying vec4 projTexCoord; - varying vec4 vPosition; - uniform mat4 textureMatrix; + vertexShader: + `#include + #include - void main() { + varying vec4 projTexCoord; + varying vec4 vPosition; + uniform mat4 textureMatrix; - #include - #include - #include - #include - #include + void main() { - vPosition = mvPosition; + #include + #include + #include + #include + #include - vec4 worldPosition = vec4( transformed, 1.0 ); + vPosition = mvPosition; - #ifdef USE_INSTANCING + vec4 worldPosition = vec4( transformed, 1.0 ); - worldPosition = instanceMatrix * worldPosition; + #ifdef USE_INSTANCING - #endif - - worldPosition = modelMatrix * worldPosition; + worldPosition = instanceMatrix * worldPosition; - projTexCoord = textureMatrix * worldPosition; + #endif + + worldPosition = modelMatrix * worldPosition; - }`, + projTexCoord = textureMatrix * worldPosition; - fragmentShader: - `#include - varying vec4 vPosition; - varying vec4 projTexCoord; - uniform sampler2D depthTexture; - uniform vec2 cameraNearFar; + }`, - void main() { + fragmentShader: + `#include + varying vec4 vPosition; + varying vec4 projTexCoord; + uniform sampler2D depthTexture; + uniform vec2 cameraNearFar; - float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord )); - float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y ); - float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0; - gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0); + void main() { - }` + float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord )); + float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y ); + float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0; + gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0); - } ); + }` - } + } ); - getEdgeDetectionMaterial() { - - return new ShaderMaterial( { - - uniforms: { - 'maskTexture': { value: null }, - 'texSize': { value: new Vector2( 0.5, 0.5 ) }, - 'visibleEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, - 'hiddenEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, - }, - - vertexShader: - `varying vec2 vUv; - - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `varying vec2 vUv; - - uniform sampler2D maskTexture; - uniform vec2 texSize; - uniform vec3 visibleEdgeColor; - uniform vec3 hiddenEdgeColor; - - void main() { - vec2 invSize = 1.0 / texSize; - vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize); - vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy); - vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy); - vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw); - vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw); - float diff1 = (c1.r - c2.r)*0.5; - float diff2 = (c3.r - c4.r)*0.5; - float d = length( vec2(diff1, diff2) ); - float a1 = min(c1.g, c2.g); - float a2 = min(c3.g, c4.g); - float visibilityFactor = min(a1, a2); - vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor; - gl_FragColor = vec4(edgeColor, 1.0) * vec4(d); - }` - } ); + } - } + getEdgeDetectionMaterial() { + + return new ShaderMaterial( { + + uniforms: { + 'maskTexture': { value: null }, + 'texSize': { value: new Vector2( 0.5, 0.5 ) }, + 'visibleEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, + 'hiddenEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) }, + }, + + vertexShader: + `varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `varying vec2 vUv; + + uniform sampler2D maskTexture; + uniform vec2 texSize; + uniform vec3 visibleEdgeColor; + uniform vec3 hiddenEdgeColor; + + void main() { + vec2 invSize = 1.0 / texSize; + vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize); + vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy); + vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy); + vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw); + vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw); + float diff1 = (c1.r - c2.r)*0.5; + float diff2 = (c3.r - c4.r)*0.5; + float d = length( vec2(diff1, diff2) ); + float a1 = min(c1.g, c2.g); + float a2 = min(c3.g, c4.g); + float visibilityFactor = min(a1, a2); + vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor; + gl_FragColor = vec4(edgeColor, 1.0) * vec4(d); + }` + } ); - getSeperableBlurMaterial( maxRadius ) { + } - return new ShaderMaterial( { + getSeperableBlurMaterial( maxRadius ) { - defines: { - 'MAX_RADIUS': maxRadius, - }, + return new ShaderMaterial( { - uniforms: { - 'colorTexture': { value: null }, - 'texSize': { value: new Vector2( 0.5, 0.5 ) }, - 'direction': { value: new Vector2( 0.5, 0.5 ) }, - 'kernelRadius': { value: 1.0 } - }, + defines: { + 'MAX_RADIUS': maxRadius, + }, - vertexShader: - `varying vec2 vUv; + uniforms: { + 'colorTexture': { value: null }, + 'texSize': { value: new Vector2( 0.5, 0.5 ) }, + 'direction': { value: new Vector2( 0.5, 0.5 ) }, + 'kernelRadius': { value: 1.0 } + }, - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, + vertexShader: + `varying vec2 vUv; - fragmentShader: - `#include - varying vec2 vUv; - uniform sampler2D colorTexture; - uniform vec2 texSize; - uniform vec2 direction; - uniform float kernelRadius; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, - float gaussianPdf(in float x, in float sigma) { - return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; - } + fragmentShader: + `#include + varying vec2 vUv; + uniform sampler2D colorTexture; + uniform vec2 texSize; + uniform vec2 direction; + uniform float kernelRadius; - void main() { - vec2 invSize = 1.0 / texSize; - float sigma = kernelRadius/2.0; - float weightSum = gaussianPdf(0.0, sigma); - vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum; - vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS); - vec2 uvOffset = delta; - for( int i = 1; i <= MAX_RADIUS; i ++ ) { - float x = kernelRadius * float(i) / float(MAX_RADIUS); - float w = gaussianPdf(x, sigma); - vec4 sample1 = texture2D( colorTexture, vUv + uvOffset); - vec4 sample2 = texture2D( colorTexture, vUv - uvOffset); - diffuseSum += ((sample1 + sample2) * w); - weightSum += (2.0 * w); - uvOffset += delta; - } - gl_FragColor = diffuseSum/weightSum; - }` - } ); + float gaussianPdf(in float x, in float sigma) { + return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; + } - } + void main() { + vec2 invSize = 1.0 / texSize; + float sigma = kernelRadius/2.0; + float weightSum = gaussianPdf(0.0, sigma); + vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum; + vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS); + vec2 uvOffset = delta; + for( int i = 1; i <= MAX_RADIUS; i ++ ) { + float x = kernelRadius * float(i) / float(MAX_RADIUS); + float w = gaussianPdf(x, sigma); + vec4 sample1 = texture2D( colorTexture, vUv + uvOffset); + vec4 sample2 = texture2D( colorTexture, vUv - uvOffset); + diffuseSum += ((sample1 + sample2) * w); + weightSum += (2.0 * w); + uvOffset += delta; + } + gl_FragColor = diffuseSum/weightSum; + }` + } ); - getOverlayMaterial() { - - return new ShaderMaterial( { - - uniforms: { - 'maskTexture': { value: null }, - 'edgeTexture1': { value: null }, - 'edgeTexture2': { value: null }, - 'patternTexture': { value: null }, - 'edgeStrength': { value: 1.0 }, - 'edgeGlow': { value: 1.0 }, - 'usePatternTexture': { value: 0.0 } - }, - - vertexShader: - `varying vec2 vUv; - - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `varying vec2 vUv; - - uniform sampler2D maskTexture; - uniform sampler2D edgeTexture1; - uniform sampler2D edgeTexture2; - uniform sampler2D patternTexture; - uniform float edgeStrength; - uniform float edgeGlow; - uniform bool usePatternTexture; - - void main() { - vec4 edgeValue1 = texture2D(edgeTexture1, vUv); - vec4 edgeValue2 = texture2D(edgeTexture2, vUv); - vec4 maskColor = texture2D(maskTexture, vUv); - vec4 patternColor = texture2D(patternTexture, 6.0 * vUv); - float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5; - vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow; - vec4 finalColor = edgeStrength * maskColor.r * edgeValue; - if(usePatternTexture) - finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r); - gl_FragColor = finalColor; - }`, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true - } ); + } - } + getOverlayMaterial() { + + return new ShaderMaterial( { + + uniforms: { + 'maskTexture': { value: null }, + 'edgeTexture1': { value: null }, + 'edgeTexture2': { value: null }, + 'patternTexture': { value: null }, + 'edgeStrength': { value: 1.0 }, + 'edgeGlow': { value: 1.0 }, + 'usePatternTexture': { value: 0.0 } + }, + + vertexShader: + `varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `varying vec2 vUv; + + uniform sampler2D maskTexture; + uniform sampler2D edgeTexture1; + uniform sampler2D edgeTexture2; + uniform sampler2D patternTexture; + uniform float edgeStrength; + uniform float edgeGlow; + uniform bool usePatternTexture; + + void main() { + vec4 edgeValue1 = texture2D(edgeTexture1, vUv); + vec4 edgeValue2 = texture2D(edgeTexture2, vUv); + vec4 maskColor = texture2D(maskTexture, vUv); + vec4 patternColor = texture2D(patternTexture, 6.0 * vUv); + float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5; + vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow; + vec4 finalColor = edgeStrength * maskColor.r * edgeValue; + if(usePatternTexture) + finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r); + gl_FragColor = finalColor; + }`, + blending: AdditiveBlending, + depthTest: false, + depthWrite: false, + transparent: true + } ); } - OutlinePass.BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); - OutlinePass.BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); +} + +OutlinePass.BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); +OutlinePass.BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); - return OutlinePass; +return OutlinePass; } )(); diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index 3906dd888e2662..68681523a52bc5 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -29,313 +29,313 @@ import { CopyShader } from '../shaders/CopyShader.js'; const SAOPass = /* @__PURE__ */ ( () => { - class SAOPass extends Pass { - - constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { - - super(); - - this.scene = scene; - this.camera = camera; - - this.clear = true; - this.needsSwap = false; - - this.originalClearColor = new Color(); - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; - - this.params = { - output: 0, - saoBias: 0.5, - saoIntensity: 0.18, - saoScale: 1, - saoKernelRadius: 100, - saoMinResolution: 0, - saoBlur: true, - saoBlurRadius: 8, - saoBlurStdDev: 4, - saoBlurDepthCutoff: 0.01 - }; - - this.resolution = new Vector2( resolution.x, resolution.y ); - - this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); - this.blurIntermediateRenderTarget = this.saoRenderTarget.clone(); - - const depthTexture = new DepthTexture(); - depthTexture.format = DepthStencilFormat; - depthTexture.type = UnsignedInt248Type; - - this.normalRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - depthTexture: depthTexture - } ); - - this.normalMaterial = new MeshNormalMaterial(); - this.normalMaterial.blending = NoBlending; - - this.saoMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SAOShader.defines ), - fragmentShader: SAOShader.fragmentShader, - vertexShader: SAOShader.vertexShader, - uniforms: UniformsUtils.clone( SAOShader.uniforms ) - } ); - this.saoMaterial.extensions.derivatives = true; - this.saoMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.saoMaterial.uniforms[ 'tDepth' ].value = depthTexture; - this.saoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; - this.saoMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); - this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; - this.saoMaterial.blending = NoBlending; - - this.vBlurMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), - defines: Object.assign( {}, DepthLimitedBlurShader.defines ), - vertexShader: DepthLimitedBlurShader.vertexShader, - fragmentShader: DepthLimitedBlurShader.fragmentShader - } ); - this.vBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; - this.vBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.vBlurMaterial.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; - this.vBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; - this.vBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); - this.vBlurMaterial.blending = NoBlending; - - this.hBlurMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), - defines: Object.assign( {}, DepthLimitedBlurShader.defines ), - vertexShader: DepthLimitedBlurShader.vertexShader, - fragmentShader: DepthLimitedBlurShader.fragmentShader - } ); - this.hBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; - this.hBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.hBlurMaterial.uniforms[ 'tDiffuse' ].value = this.blurIntermediateRenderTarget.texture; - this.hBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; - this.hBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); - this.hBlurMaterial.blending = NoBlending; - - this.materialCopy = new ShaderMaterial( { - uniforms: UniformsUtils.clone( CopyShader.uniforms ), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - blending: NoBlending - } ); - this.materialCopy.transparent = true; - this.materialCopy.depthTest = false; - this.materialCopy.depthWrite = false; - this.materialCopy.blending = CustomBlending; - this.materialCopy.blendSrc = DstColorFactor; - this.materialCopy.blendDst = ZeroFactor; - this.materialCopy.blendEquation = AddEquation; - this.materialCopy.blendSrcAlpha = DstAlphaFactor; - this.materialCopy.blendDstAlpha = ZeroFactor; - this.materialCopy.blendEquationAlpha = AddEquation; - - this.fsQuad = new FullScreenQuad( null ); +class SAOPass extends Pass { + + constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { + + super(); + + this.scene = scene; + this.camera = camera; + + this.clear = true; + this.needsSwap = false; + + this.originalClearColor = new Color(); + this._oldClearColor = new Color(); + this.oldClearAlpha = 1; + + this.params = { + output: 0, + saoBias: 0.5, + saoIntensity: 0.18, + saoScale: 1, + saoKernelRadius: 100, + saoMinResolution: 0, + saoBlur: true, + saoBlurRadius: 8, + saoBlurStdDev: 4, + saoBlurDepthCutoff: 0.01 + }; + + this.resolution = new Vector2( resolution.x, resolution.y ); + + this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); + this.blurIntermediateRenderTarget = this.saoRenderTarget.clone(); + + const depthTexture = new DepthTexture(); + depthTexture.format = DepthStencilFormat; + depthTexture.type = UnsignedInt248Type; + + this.normalRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture + } ); + + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; + + this.saoMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SAOShader.defines ), + fragmentShader: SAOShader.fragmentShader, + vertexShader: SAOShader.vertexShader, + uniforms: UniformsUtils.clone( SAOShader.uniforms ) + } ); + this.saoMaterial.extensions.derivatives = true; + this.saoMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; + this.saoMaterial.uniforms[ 'tDepth' ].value = depthTexture; + this.saoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; + this.saoMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); + this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; + this.saoMaterial.blending = NoBlending; + + this.vBlurMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), + defines: Object.assign( {}, DepthLimitedBlurShader.defines ), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader + } ); + this.vBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; + this.vBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; + this.vBlurMaterial.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; + this.vBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; + this.vBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); + this.vBlurMaterial.blending = NoBlending; + + this.hBlurMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( DepthLimitedBlurShader.uniforms ), + defines: Object.assign( {}, DepthLimitedBlurShader.defines ), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader + } ); + this.hBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; + this.hBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; + this.hBlurMaterial.uniforms[ 'tDiffuse' ].value = this.blurIntermediateRenderTarget.texture; + this.hBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; + this.hBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); + this.hBlurMaterial.blending = NoBlending; + + this.materialCopy = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: NoBlending + } ); + this.materialCopy.transparent = true; + this.materialCopy.depthTest = false; + this.materialCopy.depthWrite = false; + this.materialCopy.blending = CustomBlending; + this.materialCopy.blendSrc = DstColorFactor; + this.materialCopy.blendDst = ZeroFactor; + this.materialCopy.blendEquation = AddEquation; + this.materialCopy.blendSrcAlpha = DstAlphaFactor; + this.materialCopy.blendDstAlpha = ZeroFactor; + this.materialCopy.blendEquationAlpha = AddEquation; + + this.fsQuad = new FullScreenQuad( null ); - } - - render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { - - // Rendering readBuffer first when rendering to screen - if ( this.renderToScreen ) { - - this.materialCopy.blending = NoBlending; - this.materialCopy.uniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.materialCopy.needsUpdate = true; - this.renderPass( renderer, this.materialCopy, null ); - - } - - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; - - this.saoMaterial.uniforms[ 'bias' ].value = this.params.saoBias; - this.saoMaterial.uniforms[ 'intensity' ].value = this.params.saoIntensity; - this.saoMaterial.uniforms[ 'scale' ].value = this.params.saoScale; - this.saoMaterial.uniforms[ 'kernelRadius' ].value = this.params.saoKernelRadius; - this.saoMaterial.uniforms[ 'minResolution' ].value = this.params.saoMinResolution; - this.saoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.saoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); - - const depthCutoff = this.params.saoBlurDepthCutoff * ( this.camera.far - this.camera.near ); - this.vBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; - this.hBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; - - this.vBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.vBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.hBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.hBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + } - this.params.saoBlurRadius = Math.floor( this.params.saoBlurRadius ); - if ( ( this.prevStdDev !== this.params.saoBlurStdDev ) || ( this.prevNumSamples !== this.params.saoBlurRadius ) ) { + render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { - BlurShaderUtils.configure( this.vBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 0, 1 ) ); - BlurShaderUtils.configure( this.hBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 1, 0 ) ); - this.prevStdDev = this.params.saoBlurStdDev; - this.prevNumSamples = this.params.saoBlurRadius; + // Rendering readBuffer first when rendering to screen + if ( this.renderToScreen ) { - } + this.materialCopy.blending = NoBlending; + this.materialCopy.uniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.materialCopy.needsUpdate = true; + this.renderPass( renderer, this.materialCopy, null ); - // render normal and depth - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + } - // Rendering SAO texture - this.renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + renderer.getClearColor( this._oldClearColor ); + this.oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; + + this.saoMaterial.uniforms[ 'bias' ].value = this.params.saoBias; + this.saoMaterial.uniforms[ 'intensity' ].value = this.params.saoIntensity; + this.saoMaterial.uniforms[ 'scale' ].value = this.params.saoScale; + this.saoMaterial.uniforms[ 'kernelRadius' ].value = this.params.saoKernelRadius; + this.saoMaterial.uniforms[ 'minResolution' ].value = this.params.saoMinResolution; + this.saoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.saoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); + + const depthCutoff = this.params.saoBlurDepthCutoff * ( this.camera.far - this.camera.near ); + this.vBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; + this.hBlurMaterial.uniforms[ 'depthCutoff' ].value = depthCutoff; + + this.vBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.vBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.hBlurMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.hBlurMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + + this.params.saoBlurRadius = Math.floor( this.params.saoBlurRadius ); + if ( ( this.prevStdDev !== this.params.saoBlurStdDev ) || ( this.prevNumSamples !== this.params.saoBlurRadius ) ) { + + BlurShaderUtils.configure( this.vBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 0, 1 ) ); + BlurShaderUtils.configure( this.hBlurMaterial, this.params.saoBlurRadius, this.params.saoBlurStdDev, new Vector2( 1, 0 ) ); + this.prevStdDev = this.params.saoBlurStdDev; + this.prevNumSamples = this.params.saoBlurRadius; - // Blurring SAO texture - if ( this.params.saoBlur ) { + } - this.renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); - this.renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + // render normal and depth + this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - } + // Rendering SAO texture + this.renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); - const outputMaterial = this.materialCopy; + // Blurring SAO texture + if ( this.params.saoBlur ) { - // Setting up SAO rendering - if ( this.params.output === SAOPass.OUTPUT.Normal ) { + this.renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); + this.renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); - this.materialCopy.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; - this.materialCopy.needsUpdate = true; + } - } else { + const outputMaterial = this.materialCopy; - this.materialCopy.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; - this.materialCopy.needsUpdate = true; + // Setting up SAO rendering + if ( this.params.output === SAOPass.OUTPUT.Normal ) { - } + this.materialCopy.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; + this.materialCopy.needsUpdate = true; - // Blending depends on output - if ( this.params.output === SAOPass.OUTPUT.Default ) { + } else { - outputMaterial.blending = CustomBlending; + this.materialCopy.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; + this.materialCopy.needsUpdate = true; - } else { + } - outputMaterial.blending = NoBlending; + // Blending depends on output + if ( this.params.output === SAOPass.OUTPUT.Default ) { - } + outputMaterial.blending = CustomBlending; - // Rendering SAOPass result on top of previous pass - this.renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); + } else { - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; + outputMaterial.blending = NoBlending; } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + // Rendering SAOPass result on top of previous pass + this.renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); - // save original state - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.autoClear = oldAutoClear; - renderer.setRenderTarget( renderTarget ); + } - // setup pass state - renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + // save original state + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + // setup pass state + renderer.autoClear = false; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + this.fsQuad.material = passMaterial; + this.fsQuad.render( renderer ); - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + } - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - this.scene.overrideMaterial = overrideMaterial; - renderer.render( this.scene, this.camera ); - this.scene.overrideMaterial = null; + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); } - setSize( width, height ) { + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; - this.saoRenderTarget.setSize( width, height ); - this.blurIntermediateRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); - this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; - this.saoMaterial.needsUpdate = true; + } - this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.vBlurMaterial.needsUpdate = true; + setSize( width, height ) { - this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.hBlurMaterial.needsUpdate = true; + this.saoRenderTarget.setSize( width, height ); + this.blurIntermediateRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); - } + this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); + this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; + this.saoMaterial.needsUpdate = true; - dispose() { + this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.vBlurMaterial.needsUpdate = true; - this.saoRenderTarget.dispose(); - this.blurIntermediateRenderTarget.dispose(); - this.normalRenderTarget.dispose(); + this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.hBlurMaterial.needsUpdate = true; - this.depthMaterial.dispose(); - this.normalMaterial.dispose(); - this.saoMaterial.dispose(); - this.vBlurMaterial.dispose(); - this.hBlurMaterial.dispose(); - this.materialCopy.dispose(); + } - this.fsQuad.dispose(); + dispose() { - } + this.saoRenderTarget.dispose(); + this.blurIntermediateRenderTarget.dispose(); + this.normalRenderTarget.dispose(); + + this.depthMaterial.dispose(); + this.normalMaterial.dispose(); + this.saoMaterial.dispose(); + this.vBlurMaterial.dispose(); + this.hBlurMaterial.dispose(); + this.materialCopy.dispose(); + + this.fsQuad.dispose(); } - SAOPass.OUTPUT = { - 'Default': 0, - 'SAO': 1, - 'Normal': 2 - }; +} + +SAOPass.OUTPUT = { + 'Default': 0, + 'SAO': 1, + 'Normal': 2 +}; - return SAOPass; +return SAOPass; } )(); diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index b4d08acf02ebc0..26f16c5448394e 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -32,394 +32,394 @@ import { CopyShader } from '../shaders/CopyShader.js'; const SSAOPass = /* @__PURE__ */ ( () => { - class SSAOPass extends Pass { +class SSAOPass extends Pass { - constructor( scene, camera, width, height, kernelSize = 32 ) { + constructor( scene, camera, width, height, kernelSize = 32 ) { - super(); + super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + this.width = ( width !== undefined ) ? width : 512; + this.height = ( height !== undefined ) ? height : 512; - this.clear = true; + this.clear = true; - this.camera = camera; - this.scene = scene; + this.camera = camera; + this.scene = scene; - this.kernelRadius = 8; - this.kernel = []; - this.noiseTexture = null; - this.output = 0; + this.kernelRadius = 8; + this.kernel = []; + this.noiseTexture = null; + this.output = 0; - this.minDistance = 0.005; - this.maxDistance = 0.1; + this.minDistance = 0.005; + this.maxDistance = 0.1; - this._visibilityCache = new Map(); + this._visibilityCache = new Map(); - // + // - this.generateSampleKernel( kernelSize ); - this.generateRandomKernelRotations(); + this.generateSampleKernel( kernelSize ); + this.generateRandomKernelRotations(); - // depth texture + // depth texture - const depthTexture = new DepthTexture(); - depthTexture.format = DepthStencilFormat; - depthTexture.type = UnsignedInt248Type; + const depthTexture = new DepthTexture(); + depthTexture.format = DepthStencilFormat; + depthTexture.type = UnsignedInt248Type; - // normal render target with depth buffer + // normal render target with depth buffer - this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - depthTexture: depthTexture - } ); + this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture + } ); - // ssao render target + // ssao render target - this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); + this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); - this.blurRenderTarget = this.ssaoRenderTarget.clone(); + this.blurRenderTarget = this.ssaoRenderTarget.clone(); - // ssao material + // ssao material - this.ssaoMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSAOShader.defines ), - uniforms: UniformsUtils.clone( SSAOShader.uniforms ), - vertexShader: SSAOShader.vertexShader, - fragmentShader: SSAOShader.fragmentShader, - blending: NoBlending - } ); + this.ssaoMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSAOShader.defines ), + uniforms: UniformsUtils.clone( SSAOShader.uniforms ), + vertexShader: SSAOShader.vertexShader, + fragmentShader: SSAOShader.fragmentShader, + blending: NoBlending + } ); - this.ssaoMaterial.defines[ 'KERNEL_SIZE' ] = kernelSize; + this.ssaoMaterial.defines[ 'KERNEL_SIZE' ] = kernelSize; - this.ssaoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; - this.ssaoMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; - this.ssaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture; - this.ssaoMaterial.uniforms[ 'kernel' ].value = this.kernel; - this.ssaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.ssaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.ssaoMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.ssaoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; + this.ssaoMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; + this.ssaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture; + this.ssaoMaterial.uniforms[ 'kernel' ].value = this.kernel; + this.ssaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.ssaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.ssaoMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - // normal material + // normal material - this.normalMaterial = new MeshNormalMaterial(); - this.normalMaterial.blending = NoBlending; + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; - // blur material + // blur material - this.blurMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSAOBlurShader.defines ), - uniforms: UniformsUtils.clone( SSAOBlurShader.uniforms ), - vertexShader: SSAOBlurShader.vertexShader, - fragmentShader: SSAOBlurShader.fragmentShader - } ); - this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; - this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + this.blurMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSAOBlurShader.defines ), + uniforms: UniformsUtils.clone( SSAOBlurShader.uniforms ), + vertexShader: SSAOBlurShader.vertexShader, + fragmentShader: SSAOBlurShader.fragmentShader + } ); + this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; + this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - // material for rendering the depth + // material for rendering the depth - this.depthRenderMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSAODepthShader.defines ), - uniforms: UniformsUtils.clone( SSAODepthShader.uniforms ), - vertexShader: SSAODepthShader.vertexShader, - fragmentShader: SSAODepthShader.fragmentShader, - blending: NoBlending - } ); - this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; - this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.depthRenderMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSAODepthShader.defines ), + uniforms: UniformsUtils.clone( SSAODepthShader.uniforms ), + vertexShader: SSAODepthShader.vertexShader, + fragmentShader: SSAODepthShader.fragmentShader, + blending: NoBlending + } ); + this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; + this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - // material for rendering the content of a render target + // material for rendering the content of a render target - this.copyMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( CopyShader.uniforms ), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - transparent: true, - depthTest: false, - depthWrite: false, - blendSrc: DstColorFactor, - blendDst: ZeroFactor, - blendEquation: AddEquation, - blendSrcAlpha: DstAlphaFactor, - blendDstAlpha: ZeroFactor, - blendEquationAlpha: AddEquation - } ); + this.copyMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blendSrc: DstColorFactor, + blendDst: ZeroFactor, + blendEquation: AddEquation, + blendSrcAlpha: DstAlphaFactor, + blendDstAlpha: ZeroFactor, + blendEquationAlpha: AddEquation + } ); - this.fsQuad = new FullScreenQuad( null ); + this.fsQuad = new FullScreenQuad( null ); - this.originalClearColor = new Color(); + this.originalClearColor = new Color(); - } - - dispose() { + } - // dispose render targets + dispose() { - this.normalRenderTarget.dispose(); - this.ssaoRenderTarget.dispose(); - this.blurRenderTarget.dispose(); + // dispose render targets - // dispose materials + this.normalRenderTarget.dispose(); + this.ssaoRenderTarget.dispose(); + this.blurRenderTarget.dispose(); - this.normalMaterial.dispose(); - this.blurMaterial.dispose(); - this.copyMaterial.dispose(); - this.depthRenderMaterial.dispose(); + // dispose materials - // dipsose full screen quad + this.normalMaterial.dispose(); + this.blurMaterial.dispose(); + this.copyMaterial.dispose(); + this.depthRenderMaterial.dispose(); - this.fsQuad.dispose(); + // dipsose full screen quad - } + this.fsQuad.dispose(); - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + } - if ( renderer.capabilities.isWebGL2 === false ) this.noiseTexture.format = LuminanceFormat; + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - // render normals and depth (honor only meshes, points and lines do not contribute to SSAO) + if ( renderer.capabilities.isWebGL2 === false ) this.noiseTexture.format = LuminanceFormat; - this.overrideVisibility(); - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - this.restoreVisibility(); + // render normals and depth (honor only meshes, points and lines do not contribute to SSAO) - // render SSAO + this.overrideVisibility(); + this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this.restoreVisibility(); - this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius; - this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance; - this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; - this.renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); + // render SSAO - // render blur + this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius; + this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance; + this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; + this.renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + // render blur - // output result to screen + this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); - switch ( this.output ) { + // output result to screen - case SSAOPass.OUTPUT.SSAO: + switch ( this.output ) { - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + case SSAOPass.OUTPUT.SSAO: - break; + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - case SSAOPass.OUTPUT.Blur: + break; - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + case SSAOPass.OUTPUT.Blur: - break; + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - case SSAOPass.OUTPUT.Depth: + break; - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + case SSAOPass.OUTPUT.Depth: - break; + this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); - case SSAOPass.OUTPUT.Normal: + break; - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + case SSAOPass.OUTPUT.Normal: - break; + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - case SSAOPass.OUTPUT.Default: + break; - this.copyMaterial.uniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + case SSAOPass.OUTPUT.Default: - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; - this.copyMaterial.blending = CustomBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - break; + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; + this.copyMaterial.blending = CustomBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - default: - console.warn( 'THREE.SSAOPass: Unknown output type.' ); + break; - } + default: + console.warn( 'THREE.SSAOPass: Unknown output type.' ); } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + } - // save original state - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setRenderTarget( renderTarget ); + // save original state + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - // setup pass state - renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderer.setRenderTarget( renderTarget ); - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + // setup pass state + renderer.autoClear = false; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - } + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + } - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + this.fsQuad.material = passMaterial; + this.fsQuad.render( renderer ); - } + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + } - renderer.getClearColor( this.originalClearColor ); - const originalClearAlpha = renderer.getClearAlpha(); - const originalAutoClear = renderer.autoClear; + renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + renderer.getClearColor( this.originalClearColor ); + const originalClearAlpha = renderer.getClearAlpha(); + const originalAutoClear = renderer.autoClear; - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - } + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - this.scene.overrideMaterial = overrideMaterial; - renderer.render( this.scene, this.camera ); - this.scene.overrideMaterial = null; + } - // restore original state + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + // restore original state - } + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - setSize( width, height ) { + } - this.width = width; - this.height = height; + setSize( width, height ) { - this.ssaoRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); + this.width = width; + this.height = height; - this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.ssaoRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - } + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - generateSampleKernel( kernelSize ) { + } - const kernel = this.kernel; + generateSampleKernel( kernelSize ) { - for ( let i = 0; i < kernelSize; i ++ ) { + const kernel = this.kernel; - const sample = new Vector3(); - sample.x = ( Math.random() * 2 ) - 1; - sample.y = ( Math.random() * 2 ) - 1; - sample.z = Math.random(); + for ( let i = 0; i < kernelSize; i ++ ) { - sample.normalize(); + const sample = new Vector3(); + sample.x = ( Math.random() * 2 ) - 1; + sample.y = ( Math.random() * 2 ) - 1; + sample.z = Math.random(); - let scale = i / kernelSize; - scale = MathUtils.lerp( 0.1, 1, scale * scale ); - sample.multiplyScalar( scale ); + sample.normalize(); - kernel.push( sample ); + let scale = i / kernelSize; + scale = MathUtils.lerp( 0.1, 1, scale * scale ); + sample.multiplyScalar( scale ); - } + kernel.push( sample ); } - generateRandomKernelRotations() { - - const width = 4, height = 4; + } - const simplex = new SimplexNoise(); + generateRandomKernelRotations() { - const size = width * height; - const data = new Float32Array( size ); + const width = 4, height = 4; - for ( let i = 0; i < size; i ++ ) { + const simplex = new SimplexNoise(); - const x = ( Math.random() * 2 ) - 1; - const y = ( Math.random() * 2 ) - 1; - const z = 0; + const size = width * height; + const data = new Float32Array( size ); - data[ i ] = simplex.noise3d( x, y, z ); + for ( let i = 0; i < size; i ++ ) { - } + const x = ( Math.random() * 2 ) - 1; + const y = ( Math.random() * 2 ) - 1; + const z = 0; - this.noiseTexture = new DataTexture( data, width, height, RedFormat, FloatType ); - this.noiseTexture.wrapS = RepeatWrapping; - this.noiseTexture.wrapT = RepeatWrapping; - this.noiseTexture.needsUpdate = true; + data[ i ] = simplex.noise3d( x, y, z ); } - overrideVisibility() { + this.noiseTexture = new DataTexture( data, width, height, RedFormat, FloatType ); + this.noiseTexture.wrapS = RepeatWrapping; + this.noiseTexture.wrapT = RepeatWrapping; + this.noiseTexture.needsUpdate = true; - const scene = this.scene; - const cache = this._visibilityCache; + } - scene.traverse( function ( object ) { + overrideVisibility() { - cache.set( object, object.visible ); + const scene = this.scene; + const cache = this._visibilityCache; - if ( object.isPoints || object.isLine ) object.visible = false; + scene.traverse( function ( object ) { - } ); + cache.set( object, object.visible ); - } + if ( object.isPoints || object.isLine ) object.visible = false; - restoreVisibility() { + } ); - const scene = this.scene; - const cache = this._visibilityCache; + } - scene.traverse( function ( object ) { + restoreVisibility() { - const visible = cache.get( object ); - object.visible = visible; + const scene = this.scene; + const cache = this._visibilityCache; - } ); + scene.traverse( function ( object ) { - cache.clear(); + const visible = cache.get( object ); + object.visible = visible; - } + } ); + + cache.clear(); } - SSAOPass.OUTPUT = { - 'Default': 0, - 'SSAO': 1, - 'Blur': 2, - 'Depth': 3, - 'Normal': 4 - }; +} + +SSAOPass.OUTPUT = { + 'Default': 0, + 'SSAO': 1, + 'Blur': 2, + 'Depth': 3, + 'Normal': 4 +}; - return SSAOPass; +return SSAOPass; } )(); diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index 5609bfee0b3dc2..7eb47709645a67 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -23,624 +23,624 @@ import { CopyShader } from '../shaders/CopyShader.js'; const SSRPass = /* @__PURE__ */ ( () => { - class SSRPass extends Pass { +class SSRPass extends Pass { - constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { + constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { - super(); + super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + this.width = ( width !== undefined ) ? width : 512; + this.height = ( height !== undefined ) ? height : 512; - this.clear = true; + this.clear = true; - this.renderer = renderer; - this.scene = scene; - this.camera = camera; - this.groundReflector = groundReflector; + this.renderer = renderer; + this.scene = scene; + this.camera = camera; + this.groundReflector = groundReflector; - this.opacity = SSRShader.uniforms.opacity.value; - this.output = 0; + this.opacity = SSRShader.uniforms.opacity.value; + this.output = 0; - this.maxDistance = SSRShader.uniforms.maxDistance.value; - this.thickness = SSRShader.uniforms.thickness.value; + this.maxDistance = SSRShader.uniforms.maxDistance.value; + this.thickness = SSRShader.uniforms.thickness.value; - this.tempColor = new Color(); + this.tempColor = new Color(); - this._selects = selects; - this.selective = Array.isArray( this._selects ); - Object.defineProperty( this, 'selects', { - get() { + this._selects = selects; + this.selective = Array.isArray( this._selects ); + Object.defineProperty( this, 'selects', { + get() { - return this._selects; + return this._selects; - }, - set( val ) { + }, + set( val ) { - if ( this._selects === val ) return; - this._selects = val; - if ( Array.isArray( val ) ) { + if ( this._selects === val ) return; + this._selects = val; + if ( Array.isArray( val ) ) { - this.selective = true; - this.ssrMaterial.defines.SELECTIVE = true; - this.ssrMaterial.needsUpdate = true; - - } else { + this.selective = true; + this.ssrMaterial.defines.SELECTIVE = true; + this.ssrMaterial.needsUpdate = true; - this.selective = false; - this.ssrMaterial.defines.SELECTIVE = false; - this.ssrMaterial.needsUpdate = true; + } else { - } + this.selective = false; + this.ssrMaterial.defines.SELECTIVE = false; + this.ssrMaterial.needsUpdate = true; } - } ); - this._bouncing = bouncing; - Object.defineProperty( this, 'bouncing', { - get() { + } + } ); - return this._bouncing; + this._bouncing = bouncing; + Object.defineProperty( this, 'bouncing', { + get() { - }, - set( val ) { + return this._bouncing; - if ( this._bouncing === val ) return; - this._bouncing = val; - if ( val ) { + }, + set( val ) { - this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; + if ( this._bouncing === val ) return; + this._bouncing = val; + if ( val ) { - } else { + this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + } else { - } + this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; } - } ); - this.blur = true; - - this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION; - Object.defineProperty( this, 'distanceAttenuation', { - get() { + } + } ); - return this._distanceAttenuation; + this.blur = true; - }, - set( val ) { + this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION; + Object.defineProperty( this, 'distanceAttenuation', { + get() { - if ( this._distanceAttenuation === val ) return; - this._distanceAttenuation = val; - this.ssrMaterial.defines.DISTANCE_ATTENUATION = val; - this.ssrMaterial.needsUpdate = true; - - } - } ); + return this._distanceAttenuation; + }, + set( val ) { - this._fresnel = SSRShader.defines.FRESNEL; - Object.defineProperty( this, 'fresnel', { - get() { + if ( this._distanceAttenuation === val ) return; + this._distanceAttenuation = val; + this.ssrMaterial.defines.DISTANCE_ATTENUATION = val; + this.ssrMaterial.needsUpdate = true; - return this._fresnel; + } + } ); - }, - set( val ) { - if ( this._fresnel === val ) return; - this._fresnel = val; - this.ssrMaterial.defines.FRESNEL = val; - this.ssrMaterial.needsUpdate = true; + this._fresnel = SSRShader.defines.FRESNEL; + Object.defineProperty( this, 'fresnel', { + get() { - } - } ); + return this._fresnel; - this._infiniteThick = SSRShader.defines.INFINITE_THICK; - Object.defineProperty( this, 'infiniteThick', { - get() { + }, + set( val ) { - return this._infiniteThick; + if ( this._fresnel === val ) return; + this._fresnel = val; + this.ssrMaterial.defines.FRESNEL = val; + this.ssrMaterial.needsUpdate = true; - }, - set( val ) { + } + } ); - if ( this._infiniteThick === val ) return; - this._infiniteThick = val; - this.ssrMaterial.defines.INFINITE_THICK = val; - this.ssrMaterial.needsUpdate = true; + this._infiniteThick = SSRShader.defines.INFINITE_THICK; + Object.defineProperty( this, 'infiniteThick', { + get() { - } - } ); - - // beauty render target with depth buffer - - const depthTexture = new DepthTexture(); - depthTexture.type = UnsignedShortType; - depthTexture.minFilter = NearestFilter; - depthTexture.magFilter = NearestFilter; - - this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - depthTexture: depthTexture, - depthBuffer: true - } ); - - //for bouncing - this.prevRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter - } ); - - // normal render target - - this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - } ); - - // metalness render target - - this.metalnessRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - type: HalfFloatType, - } ); - - - - // ssr render target - - this.ssrRenderTarget = new WebGLRenderTarget( this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter - } ); - - this.blurRenderTarget = this.ssrRenderTarget.clone(); - this.blurRenderTarget2 = this.ssrRenderTarget.clone(); - // this.blurRenderTarget3 = this.ssrRenderTarget.clone(); - - // ssr material - - this.ssrMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSRShader.defines, { - MAX_STEP: Math.sqrt( this.width * this.width + this.height * this.height ) - } ), - uniforms: UniformsUtils.clone( SSRShader.uniforms ), - vertexShader: SSRShader.vertexShader, - fragmentShader: SSRShader.fragmentShader, - blending: NoBlending - } ); - - this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; - this.ssrMaterial.defines.SELECTIVE = this.selective; - this.ssrMaterial.needsUpdate = true; - this.ssrMaterial.uniforms[ 'tMetalness' ].value = this.metalnessRenderTarget.texture; - this.ssrMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; - this.ssrMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.ssrMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; - this.ssrMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - - // normal material - - this.normalMaterial = new MeshNormalMaterial(); - this.normalMaterial.blending = NoBlending; - - // metalnessOn material - - this.metalnessOnMaterial = new MeshBasicMaterial( { - color: 'white' - } ); - - // metalnessOff material - - this.metalnessOffMaterial = new MeshBasicMaterial( { - color: 'black' - } ); - - // blur material - - this.blurMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSRBlurShader.defines ), - uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), - vertexShader: SSRBlurShader.vertexShader, - fragmentShader: SSRBlurShader.fragmentShader - } ); - this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); - - // blur material 2 - - this.blurMaterial2 = new ShaderMaterial( { - defines: Object.assign( {}, SSRBlurShader.defines ), - uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), - vertexShader: SSRBlurShader.vertexShader, - fragmentShader: SSRBlurShader.fragmentShader - } ); - this.blurMaterial2.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; - this.blurMaterial2.uniforms[ 'resolution' ].value.set( this.width, this.height ); - - // // blur material 3 - - // this.blurMaterial3 = new ShaderMaterial({ - // defines: Object.assign({}, SSRBlurShader.defines), - // uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), - // vertexShader: SSRBlurShader.vertexShader, - // fragmentShader: SSRBlurShader.fragmentShader - // }); - // this.blurMaterial3.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture; - // this.blurMaterial3.uniforms['resolution'].value.set(this.width, this.height); - - // material for rendering the depth - - this.depthRenderMaterial = new ShaderMaterial( { - defines: Object.assign( {}, SSRDepthShader.defines ), - uniforms: UniformsUtils.clone( SSRDepthShader.uniforms ), - vertexShader: SSRDepthShader.vertexShader, - fragmentShader: SSRDepthShader.fragmentShader, - blending: NoBlending - } ); - this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; - this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; - this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; - - // material for rendering the content of a render target - - this.copyMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( CopyShader.uniforms ), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - transparent: true, - depthTest: false, - depthWrite: false, - blendSrc: SrcAlphaFactor, - blendDst: OneMinusSrcAlphaFactor, - blendEquation: AddEquation, - blendSrcAlpha: SrcAlphaFactor, - blendDstAlpha: OneMinusSrcAlphaFactor, - blendEquationAlpha: AddEquation, - // premultipliedAlpha:true, - } ); - - this.fsQuad = new FullScreenQuad( null ); - - this.originalClearColor = new Color(); + return this._infiniteThick; - } + }, + set( val ) { - dispose() { + if ( this._infiniteThick === val ) return; + this._infiniteThick = val; + this.ssrMaterial.defines.INFINITE_THICK = val; + this.ssrMaterial.needsUpdate = true; - // dispose render targets + } + } ); + + // beauty render target with depth buffer + + const depthTexture = new DepthTexture(); + depthTexture.type = UnsignedShortType; + depthTexture.minFilter = NearestFilter; + depthTexture.magFilter = NearestFilter; + + this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture, + depthBuffer: true + } ); + + //for bouncing + this.prevRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter + } ); + + // normal render target + + this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + } ); + + // metalness render target + + this.metalnessRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + } ); + + + + // ssr render target + + this.ssrRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter + } ); + + this.blurRenderTarget = this.ssrRenderTarget.clone(); + this.blurRenderTarget2 = this.ssrRenderTarget.clone(); + // this.blurRenderTarget3 = this.ssrRenderTarget.clone(); + + // ssr material + + this.ssrMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSRShader.defines, { + MAX_STEP: Math.sqrt( this.width * this.width + this.height * this.height ) + } ), + uniforms: UniformsUtils.clone( SSRShader.uniforms ), + vertexShader: SSRShader.vertexShader, + fragmentShader: SSRShader.fragmentShader, + blending: NoBlending + } ); + + this.ssrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.ssrMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; + this.ssrMaterial.defines.SELECTIVE = this.selective; + this.ssrMaterial.needsUpdate = true; + this.ssrMaterial.uniforms[ 'tMetalness' ].value = this.metalnessRenderTarget.texture; + this.ssrMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; + this.ssrMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.ssrMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; + this.ssrMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + // normal material + + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; + + // metalnessOn material + + this.metalnessOnMaterial = new MeshBasicMaterial( { + color: 'white' + } ); + + // metalnessOff material + + this.metalnessOffMaterial = new MeshBasicMaterial( { + color: 'black' + } ); + + // blur material + + this.blurMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSRBlurShader.defines ), + uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), + vertexShader: SSRBlurShader.vertexShader, + fragmentShader: SSRBlurShader.fragmentShader + } ); + this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height ); + + // blur material 2 + + this.blurMaterial2 = new ShaderMaterial( { + defines: Object.assign( {}, SSRBlurShader.defines ), + uniforms: UniformsUtils.clone( SSRBlurShader.uniforms ), + vertexShader: SSRBlurShader.vertexShader, + fragmentShader: SSRBlurShader.fragmentShader + } ); + this.blurMaterial2.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; + this.blurMaterial2.uniforms[ 'resolution' ].value.set( this.width, this.height ); + + // // blur material 3 + + // this.blurMaterial3 = new ShaderMaterial({ + // defines: Object.assign({}, SSRBlurShader.defines), + // uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), + // vertexShader: SSRBlurShader.vertexShader, + // fragmentShader: SSRBlurShader.fragmentShader + // }); + // this.blurMaterial3.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture; + // this.blurMaterial3.uniforms['resolution'].value.set(this.width, this.height); + + // material for rendering the depth + + this.depthRenderMaterial = new ShaderMaterial( { + defines: Object.assign( {}, SSRDepthShader.defines ), + uniforms: UniformsUtils.clone( SSRDepthShader.uniforms ), + vertexShader: SSRDepthShader.vertexShader, + fragmentShader: SSRDepthShader.fragmentShader, + blending: NoBlending + } ); + this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture; + this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near; + this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far; + + // material for rendering the content of a render target + + this.copyMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blendSrc: SrcAlphaFactor, + blendDst: OneMinusSrcAlphaFactor, + blendEquation: AddEquation, + blendSrcAlpha: SrcAlphaFactor, + blendDstAlpha: OneMinusSrcAlphaFactor, + blendEquationAlpha: AddEquation, + // premultipliedAlpha:true, + } ); + + this.fsQuad = new FullScreenQuad( null ); + + this.originalClearColor = new Color(); - this.beautyRenderTarget.dispose(); - this.prevRenderTarget.dispose(); - this.normalRenderTarget.dispose(); - this.metalnessRenderTarget.dispose(); - this.ssrRenderTarget.dispose(); - this.blurRenderTarget.dispose(); - this.blurRenderTarget2.dispose(); - // this.blurRenderTarget3.dispose(); + } - // dispose materials + dispose() { - this.normalMaterial.dispose(); - this.metalnessOnMaterial.dispose(); - this.metalnessOffMaterial.dispose(); - this.blurMaterial.dispose(); - this.blurMaterial2.dispose(); - this.copyMaterial.dispose(); - this.depthRenderMaterial.dispose(); + // dispose render targets - // dipsose full screen quad + this.beautyRenderTarget.dispose(); + this.prevRenderTarget.dispose(); + this.normalRenderTarget.dispose(); + this.metalnessRenderTarget.dispose(); + this.ssrRenderTarget.dispose(); + this.blurRenderTarget.dispose(); + this.blurRenderTarget2.dispose(); + // this.blurRenderTarget3.dispose(); - this.fsQuad.dispose(); + // dispose materials - } + this.normalMaterial.dispose(); + this.metalnessOnMaterial.dispose(); + this.metalnessOffMaterial.dispose(); + this.blurMaterial.dispose(); + this.blurMaterial2.dispose(); + this.copyMaterial.dispose(); + this.depthRenderMaterial.dispose(); - render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { + // dipsose full screen quad - // render beauty and depth + this.fsQuad.dispose(); - renderer.setRenderTarget( this.beautyRenderTarget ); - renderer.clear(); - if ( this.groundReflector ) { + } - this.groundReflector.visible = false; - this.groundReflector.doRender( this.renderer, this.scene, this.camera ); - this.groundReflector.visible = true; + render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { - } + // render beauty and depth - renderer.render( this.scene, this.camera ); - if ( this.groundReflector ) this.groundReflector.visible = false; + renderer.setRenderTarget( this.beautyRenderTarget ); + renderer.clear(); + if ( this.groundReflector ) { - // render normals + this.groundReflector.visible = false; + this.groundReflector.doRender( this.renderer, this.scene, this.camera ); + this.groundReflector.visible = true; - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); + } - // render metalnesses + renderer.render( this.scene, this.camera ); + if ( this.groundReflector ) this.groundReflector.visible = false; - if ( this.selective ) { + // render normals - this.renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); + this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); - } + // render metalnesses - // render SSR + if ( this.selective ) { - this.ssrMaterial.uniforms[ 'opacity' ].value = this.opacity; - this.ssrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; - this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; - this.renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); + this.renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); + } - // render blur + // render SSR - if ( this.blur ) { + this.ssrMaterial.uniforms[ 'opacity' ].value = this.opacity; + this.ssrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; + this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; + this.renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); - this.renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); - // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); - } + // render blur - // output result to screen + if ( this.blur ) { - switch ( this.output ) { + this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + this.renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); + // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); - case SSRPass.OUTPUT.Default: + } - if ( this.bouncing ) { + // output result to screen - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + switch ( this.output ) { - if ( this.blur ) - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; - else - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + case SSRPass.OUTPUT.Default: - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + if ( this.bouncing ) { - } else { + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + if ( this.blur ) + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; + else + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.copyMaterial.blending = NormalBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - if ( this.blur ) - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; - else - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - } + } else { - break; - case SSRPass.OUTPUT.SSR: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); if ( this.blur ) this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NoBlending; + this.copyMaterial.blending = NormalBlending; this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - if ( this.bouncing ) { + } - if ( this.blur ) - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; - else - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + break; + case SSRPass.OUTPUT.SSR: - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; - this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + if ( this.blur ) + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; + else + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - } + if ( this.bouncing ) { - break; + if ( this.blur ) + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; + else + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - case SSRPass.OUTPUT.Beauty: + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; + this.copyMaterial.blending = NormalBlending; + this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + } - break; + break; - case SSRPass.OUTPUT.Depth: + case SSRPass.OUTPUT.Beauty: - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - break; + break; - case SSRPass.OUTPUT.Normal: + case SSRPass.OUTPUT.Depth: - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); - break; + break; - case SSRPass.OUTPUT.Metalness: + case SSRPass.OUTPUT.Normal: - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.metalnessRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - break; + break; - default: - console.warn( 'THREE.SSRPass: Unknown output type.' ); + case SSRPass.OUTPUT.Metalness: - } + this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.metalnessRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - } + break; - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + default: + console.warn( 'THREE.SSRPass: Unknown output type.' ); - // save original state - this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); - const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); - const originalAutoClear = renderer.autoClear; + } - renderer.setRenderTarget( renderTarget ); + } - // setup pass state - renderer.autoClear = false; - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + // save original state + this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); + const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + // setup pass state + renderer.autoClear = false; + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - // restore original state - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - - this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); - const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); - const originalAutoClear = renderer.autoClear; + this.fsQuad.material = passMaterial; + this.fsQuad.render( renderer ); - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + } - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); + const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - this.scene.overrideMaterial = overrideMaterial; - renderer.render( this.scene, this.camera ); - this.scene.overrideMaterial = null; + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - // restore original state + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); } - renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; - this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); - const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); - const originalAutoClear = renderer.autoClear; + // restore original state - renderer.setRenderTarget( renderTarget ); - renderer.autoClear = false; + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - clearColor = overrideMaterial.clearColor || clearColor; - clearAlpha = overrideMaterial.clearAlpha || clearAlpha; + } - if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { + renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.setClearColor( clearColor ); - renderer.setClearAlpha( clearAlpha || 0.0 ); - renderer.clear(); + this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); + const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); + const originalAutoClear = renderer.autoClear; - } + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = false; - this.scene.traverseVisible( child => { + clearColor = overrideMaterial.clearColor || clearColor; + clearAlpha = overrideMaterial.clearAlpha || clearAlpha; - child._SSRPassBackupMaterial = child.material; - if ( this._selects.includes( child ) ) { + if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { - child.material = this.metalnessOnMaterial; + renderer.setClearColor( clearColor ); + renderer.setClearAlpha( clearAlpha || 0.0 ); + renderer.clear(); - } else { + } - child.material = this.metalnessOffMaterial; + this.scene.traverseVisible( child => { - } + child._SSRPassBackupMaterial = child.material; + if ( this._selects.includes( child ) ) { - } ); - renderer.render( this.scene, this.camera ); - this.scene.traverseVisible( child => { + child.material = this.metalnessOnMaterial; - child.material = child._SSRPassBackupMaterial; + } else { - } ); + child.material = this.metalnessOffMaterial; - // restore original state + } - renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); - renderer.setClearAlpha( originalClearAlpha ); + } ); + renderer.render( this.scene, this.camera ); + this.scene.traverseVisible( child => { - } + child.material = child._SSRPassBackupMaterial; + + } ); - setSize( width, height ) { + // restore original state - this.width = width; - this.height = height; + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this.originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); - this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); - this.ssrMaterial.needsUpdate = true; - this.beautyRenderTarget.setSize( width, height ); - this.prevRenderTarget.setSize( width, height ); - this.ssrRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.metalnessRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); - this.blurRenderTarget2.setSize( width, height ); - // this.blurRenderTarget3.setSize(width, height); + } - this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + setSize( width, height ) { - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); + this.width = width; + this.height = height; - } + this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); + this.ssrMaterial.needsUpdate = true; + this.beautyRenderTarget.setSize( width, height ); + this.prevRenderTarget.setSize( width, height ); + this.ssrRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.metalnessRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); + this.blurRenderTarget2.setSize( width, height ); + // this.blurRenderTarget3.setSize(width, height); + + this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); } - SSRPass.OUTPUT = { - 'Default': 0, - 'SSR': 1, - 'Beauty': 3, - 'Depth': 4, - 'Normal': 5, - 'Metalness': 7, - }; +} + +SSRPass.OUTPUT = { + 'Default': 0, + 'SSR': 1, + 'Beauty': 3, + 'Depth': 4, + 'Normal': 5, + 'Metalness': 7, +}; - return SSRPass; +return SSRPass; } )(); diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index d097dbcaa207ce..badcc48360494e 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -25,397 +25,397 @@ import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js const UnrealBloomPass = /* @__PURE__ */ ( () => { - class UnrealBloomPass extends Pass { +class UnrealBloomPass extends Pass { - constructor( resolution, strength, radius, threshold ) { + constructor( resolution, strength, radius, threshold ) { - super(); + super(); - this.strength = ( strength !== undefined ) ? strength : 1; - this.radius = radius; - this.threshold = threshold; - this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); + this.strength = ( strength !== undefined ) ? strength : 1; + this.radius = radius; + this.threshold = threshold; + this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - // create color only once here, reuse it later inside the render function - this.clearColor = new Color( 0, 0, 0 ); + // create color only once here, reuse it later inside the render function + this.clearColor = new Color( 0, 0, 0 ); - // render targets - this.renderTargetsHorizontal = []; - this.renderTargetsVertical = []; - this.nMips = 5; - let resx = Math.round( this.resolution.x / 2 ); - let resy = Math.round( this.resolution.y / 2 ); + // render targets + this.renderTargetsHorizontal = []; + this.renderTargetsVertical = []; + this.nMips = 5; + let resx = Math.round( this.resolution.x / 2 ); + let resy = Math.round( this.resolution.y / 2 ); - this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; - this.renderTargetBright.texture.generateMipmaps = false; + this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; + this.renderTargetBright.texture.generateMipmaps = false; - for ( let i = 0; i < this.nMips; i ++ ) { + for ( let i = 0; i < this.nMips; i ++ ) { - const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; - renderTargetHorizonal.texture.generateMipmaps = false; + renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; + renderTargetHorizonal.texture.generateMipmaps = false; - this.renderTargetsHorizontal.push( renderTargetHorizonal ); + this.renderTargetsHorizontal.push( renderTargetHorizonal ); - const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; - renderTargetVertical.texture.generateMipmaps = false; + renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; + renderTargetVertical.texture.generateMipmaps = false; - this.renderTargetsVertical.push( renderTargetVertical ); + this.renderTargetsVertical.push( renderTargetVertical ); - resx = Math.round( resx / 2 ); + resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); + resy = Math.round( resy / 2 ); - } + } + + // luminosity high pass material + + const highPassShader = LuminosityHighPassShader; + this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); + + this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; + this.highPassUniforms[ 'smoothWidth' ].value = 0.01; - // luminosity high pass material + this.materialHighPassFilter = new ShaderMaterial( { + uniforms: this.highPassUniforms, + vertexShader: highPassShader.vertexShader, + fragmentShader: highPassShader.fragmentShader + } ); - const highPassShader = LuminosityHighPassShader; - this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); + // gaussian blur materials - this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; - this.highPassUniforms[ 'smoothWidth' ].value = 0.01; + this.separableBlurMaterials = []; + const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; + resx = Math.round( this.resolution.x / 2 ); + resy = Math.round( this.resolution.y / 2 ); - this.materialHighPassFilter = new ShaderMaterial( { - uniforms: this.highPassUniforms, - vertexShader: highPassShader.vertexShader, - fragmentShader: highPassShader.fragmentShader - } ); + for ( let i = 0; i < this.nMips; i ++ ) { - // gaussian blur materials + this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); - this.separableBlurMaterials = []; - const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; - resx = Math.round( this.resolution.x / 2 ); - resy = Math.round( this.resolution.y / 2 ); + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - for ( let i = 0; i < this.nMips; i ++ ) { + resx = Math.round( resx / 2 ); - this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); + resy = Math.round( resy / 2 ); - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); + } + + // composite material - resx = Math.round( resx / 2 ); + this.compositeMaterial = this.getCompositeMaterial( this.nMips ); + this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; + this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; + this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; - resy = Math.round( resy / 2 ); + const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; + this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; + this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; + this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - } + // blend material - // composite material + const copyShader = CopyShader; - this.compositeMaterial = this.getCompositeMaterial( this.nMips ); - this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; + this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; - this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; - this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; + this.blendMaterial = new ShaderMaterial( { + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + blending: AdditiveBlending, + depthTest: false, + depthWrite: false, + transparent: true + } ); - // blend material + this.enabled = true; + this.needsSwap = false; - const copyShader = CopyShader; + this._oldClearColor = new Color(); + this.oldClearAlpha = 1; - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + this.basic = new MeshBasicMaterial(); - this.blendMaterial = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true - } ); + this.fsQuad = new FullScreenQuad( null ); - this.enabled = true; - this.needsSwap = false; + } - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + dispose() { - this.basic = new MeshBasicMaterial(); + for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { - this.fsQuad = new FullScreenQuad( null ); + this.renderTargetsHorizontal[ i ].dispose(); } - dispose() { + for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { - for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { + this.renderTargetsVertical[ i ].dispose(); - this.renderTargetsHorizontal[ i ].dispose(); + } - } + this.renderTargetBright.dispose(); - for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { + // - this.renderTargetsVertical[ i ].dispose(); + for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { - } + this.separableBlurMaterials[ i ].dispose(); - this.renderTargetBright.dispose(); + } - // + this.compositeMaterial.dispose(); + this.blendMaterial.dispose(); + this.basic.dispose(); - for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { + // - this.separableBlurMaterials[ i ].dispose(); + this.fsQuad.dispose(); - } + } - this.compositeMaterial.dispose(); - this.blendMaterial.dispose(); - this.basic.dispose(); + setSize( width, height ) { - // + let resx = Math.round( width / 2 ); + let resy = Math.round( height / 2 ); - this.fsQuad.dispose(); + this.renderTargetBright.setSize( resx, resy ); - } + for ( let i = 0; i < this.nMips; i ++ ) { - setSize( width, height ) { + this.renderTargetsHorizontal[ i ].setSize( resx, resy ); + this.renderTargetsVertical[ i ].setSize( resx, resy ); - let resx = Math.round( width / 2 ); - let resy = Math.round( height / 2 ); + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - this.renderTargetBright.setSize( resx, resy ); + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); - for ( let i = 0; i < this.nMips; i ++ ) { + } - this.renderTargetsHorizontal[ i ].setSize( resx, resy ); - this.renderTargetsVertical[ i ].setSize( resx, resy ); + } - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); + renderer.getClearColor( this._oldClearColor ); + this.oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; - } + renderer.setClearColor( this.clearColor, 0 ); - } + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + // Render input to screen - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; + if ( this.renderToScreen ) { - renderer.setClearColor( this.clearColor, 0 ); + this.fsQuad.material = this.basic; + this.basic.map = readBuffer.texture; - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + renderer.setRenderTarget( null ); + renderer.clear(); + this.fsQuad.render( renderer ); - // Render input to screen + } + + // 1. Extract Bright Areas - if ( this.renderToScreen ) { + this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; + this.fsQuad.material = this.materialHighPassFilter; - this.fsQuad.material = this.basic; - this.basic.map = readBuffer.texture; + renderer.setRenderTarget( this.renderTargetBright ); + renderer.clear(); + this.fsQuad.render( renderer ); - renderer.setRenderTarget( null ); - renderer.clear(); - this.fsQuad.render( renderer ); + // 2. Blur All the mips progressively - } + let inputRenderTarget = this.renderTargetBright; - // 1. Extract Bright Areas + for ( let i = 0; i < this.nMips; i ++ ) { - this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this.fsQuad.material = this.materialHighPassFilter; + this.fsQuad.material = this.separableBlurMaterials[ i ]; - renderer.setRenderTarget( this.renderTargetBright ); + this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; + this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; + renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); renderer.clear(); this.fsQuad.render( renderer ); - // 2. Blur All the mips progressively + this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; + this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); + renderer.clear(); + this.fsQuad.render( renderer ); - let inputRenderTarget = this.renderTargetBright; + inputRenderTarget = this.renderTargetsVertical[ i ]; - for ( let i = 0; i < this.nMips; i ++ ) { + } - this.fsQuad.material = this.separableBlurMaterials[ i ]; + // Composite All the mips - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; - renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.fsQuad.material = this.compositeMaterial; + this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; + this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; + this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); + renderer.clear(); + this.fsQuad.render( renderer ); - inputRenderTarget = this.renderTargetsVertical[ i ]; + // Blend it additively over the input texture - } + this.fsQuad.material = this.blendMaterial; + this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; - // Composite All the mips + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - this.fsQuad.material = this.compositeMaterial; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; + if ( this.renderToScreen ) { - renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); - renderer.clear(); + renderer.setRenderTarget( null ); this.fsQuad.render( renderer ); - // Blend it additively over the input texture - - this.fsQuad.material = this.blendMaterial; - this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; - - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + } else { - if ( this.renderToScreen ) { + renderer.setRenderTarget( readBuffer ); + this.fsQuad.render( renderer ); - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + } - } else { + // Restore renderer settings - renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.autoClear = oldAutoClear; - } + } - // Restore renderer settings + getSeperableBlurMaterial( kernelRadius ) { - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; + const coefficients = []; - } + for ( let i = 0; i < kernelRadius; i ++ ) { - getSeperableBlurMaterial( kernelRadius ) { - - const coefficients = []; - - for ( let i = 0; i < kernelRadius; i ++ ) { - - coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); - - } - - return new ShaderMaterial( { - - defines: { - 'KERNEL_RADIUS': kernelRadius - }, - - uniforms: { - 'colorTexture': { value: null }, - 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size - 'direction': { value: new Vector2( 0.5, 0.5 ) }, - 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients - }, - - vertexShader: - `varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `#include - varying vec2 vUv; - uniform sampler2D colorTexture; - uniform vec2 invSize; - uniform vec2 direction; - uniform float gaussianCoefficients[KERNEL_RADIUS]; - - void main() { - float weightSum = gaussianCoefficients[0]; - vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; - for( int i = 1; i < KERNEL_RADIUS; i ++ ) { - float x = float(i); - float w = gaussianCoefficients[i]; - vec2 uvOffset = direction * invSize * x; - vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; - vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; - diffuseSum += (sample1 + sample2) * w; - weightSum += 2.0 * w; - } - gl_FragColor = vec4(diffuseSum/weightSum, 1.0); - }` - } ); + coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); } - getCompositeMaterial( nMips ) { - - return new ShaderMaterial( { - - defines: { - 'NUM_MIPS': nMips - }, - - uniforms: { - 'blurTexture1': { value: null }, - 'blurTexture2': { value: null }, - 'blurTexture3': { value: null }, - 'blurTexture4': { value: null }, - 'blurTexture5': { value: null }, - 'bloomStrength': { value: 1.0 }, - 'bloomFactors': { value: null }, - 'bloomTintColors': { value: null }, - 'bloomRadius': { value: 0.0 } - }, - - vertexShader: - `varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - - fragmentShader: - `varying vec2 vUv; - uniform sampler2D blurTexture1; - uniform sampler2D blurTexture2; - uniform sampler2D blurTexture3; - uniform sampler2D blurTexture4; - uniform sampler2D blurTexture5; - uniform float bloomStrength; - uniform float bloomRadius; - uniform float bloomFactors[NUM_MIPS]; - uniform vec3 bloomTintColors[NUM_MIPS]; - - float lerpBloomFactor(const in float factor) { - float mirrorFactor = 1.2 - factor; - return mix(factor, mirrorFactor, bloomRadius); + return new ShaderMaterial( { + + defines: { + 'KERNEL_RADIUS': kernelRadius + }, + + uniforms: { + 'colorTexture': { value: null }, + 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size + 'direction': { value: new Vector2( 0.5, 0.5 ) }, + 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients + }, + + vertexShader: + `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `#include + varying vec2 vUv; + uniform sampler2D colorTexture; + uniform vec2 invSize; + uniform vec2 direction; + uniform float gaussianCoefficients[KERNEL_RADIUS]; + + void main() { + float weightSum = gaussianCoefficients[0]; + vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; + for( int i = 1; i < KERNEL_RADIUS; i ++ ) { + float x = float(i); + float w = gaussianCoefficients[i]; + vec2 uvOffset = direction * invSize * x; + vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; + vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; + diffuseSum += (sample1 + sample2) * w; + weightSum += 2.0 * w; } + gl_FragColor = vec4(diffuseSum/weightSum, 1.0); + }` + } ); - void main() { - gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + - lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + - lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + - lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + - lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); - }` - } ); + } - } + getCompositeMaterial( nMips ) { + + return new ShaderMaterial( { + + defines: { + 'NUM_MIPS': nMips + }, + + uniforms: { + 'blurTexture1': { value: null }, + 'blurTexture2': { value: null }, + 'blurTexture3': { value: null }, + 'blurTexture4': { value: null }, + 'blurTexture5': { value: null }, + 'bloomStrength': { value: 1.0 }, + 'bloomFactors': { value: null }, + 'bloomTintColors': { value: null }, + 'bloomRadius': { value: 0.0 } + }, + + vertexShader: + `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: + `varying vec2 vUv; + uniform sampler2D blurTexture1; + uniform sampler2D blurTexture2; + uniform sampler2D blurTexture3; + uniform sampler2D blurTexture4; + uniform sampler2D blurTexture5; + uniform float bloomStrength; + uniform float bloomRadius; + uniform float bloomFactors[NUM_MIPS]; + uniform vec3 bloomTintColors[NUM_MIPS]; + + float lerpBloomFactor(const in float factor) { + float mirrorFactor = 1.2 - factor; + return mix(factor, mirrorFactor, bloomRadius); + } + + void main() { + gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + + lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + + lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + + lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + + lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); + }` + } ); } - UnrealBloomPass.BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); - UnrealBloomPass.BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); +} + +UnrealBloomPass.BlurDirectionX = /* @__PURE__ */ new Vector2( 1.0, 0.0 ); +UnrealBloomPass.BlurDirectionY = /* @__PURE__ */ new Vector2( 0.0, 1.0 ); - return UnrealBloomPass; +return UnrealBloomPass; } )(); diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index 281047322074d5..28447868677f6c 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -1,205 +1,205 @@ const VRButton = /* @__PURE__ */ ( () => { - class VRButton { +class VRButton { - static createButton( renderer ) { + static createButton( renderer ) { - const button = document.createElement( 'button' ); + const button = document.createElement( 'button' ); - function showEnterVR( /*device*/ ) { + function showEnterVR( /*device*/ ) { - let currentSession = null; + let currentSession = null; - async function onSessionStarted( session ) { + async function onSessionStarted( session ) { - session.addEventListener( 'end', onSessionEnded ); + session.addEventListener( 'end', onSessionEnded ); - await renderer.xr.setSession( session ); - button.textContent = 'EXIT VR'; + await renderer.xr.setSession( session ); + button.textContent = 'EXIT VR'; - currentSession = session; + currentSession = session; - } + } - function onSessionEnded( /*event*/ ) { + function onSessionEnded( /*event*/ ) { - currentSession.removeEventListener( 'end', onSessionEnded ); + currentSession.removeEventListener( 'end', onSessionEnded ); - button.textContent = 'ENTER VR'; + button.textContent = 'ENTER VR'; - currentSession = null; + currentSession = null; - } + } - // + // - button.style.display = ''; + button.style.display = ''; - button.style.cursor = 'pointer'; - button.style.left = 'calc(50% - 50px)'; - button.style.width = '100px'; + button.style.cursor = 'pointer'; + button.style.left = 'calc(50% - 50px)'; + button.style.width = '100px'; - button.textContent = 'ENTER VR'; + button.textContent = 'ENTER VR'; - button.onmouseenter = function () { + button.onmouseenter = function () { - button.style.opacity = '1.0'; + button.style.opacity = '1.0'; - }; + }; - button.onmouseleave = function () { + button.onmouseleave = function () { - button.style.opacity = '0.5'; + button.style.opacity = '0.5'; - }; + }; - button.onclick = function () { + button.onclick = function () { - if ( currentSession === null ) { + if ( currentSession === null ) { - // WebXR's requestReferenceSpace only works if the corresponding feature - // was requested at session creation time. For simplicity, just ask for - // the interesting ones as optional features, but be aware that the - // requestReferenceSpace call will fail if it turns out to be unavailable. - // ('local' is always available for immersive sessions and doesn't need to - // be requested separately.) + // WebXR's requestReferenceSpace only works if the corresponding feature + // was requested at session creation time. For simplicity, just ask for + // the interesting ones as optional features, but be aware that the + // requestReferenceSpace call will fail if it turns out to be unavailable. + // ('local' is always available for immersive sessions and doesn't need to + // be requested separately.) - const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; - navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); + const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; + navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); - } else { + } else { - currentSession.end(); + currentSession.end(); - } + } - }; + }; - } + } - function disableButton() { + function disableButton() { - button.style.display = ''; + button.style.display = ''; - button.style.cursor = 'auto'; - button.style.left = 'calc(50% - 75px)'; - button.style.width = '150px'; + button.style.cursor = 'auto'; + button.style.left = 'calc(50% - 75px)'; + button.style.width = '150px'; - button.onmouseenter = null; - button.onmouseleave = null; + button.onmouseenter = null; + button.onmouseleave = null; - button.onclick = null; + button.onclick = null; - } + } - function showWebXRNotFound() { + function showWebXRNotFound() { - disableButton(); + disableButton(); - button.textContent = 'VR NOT SUPPORTED'; + button.textContent = 'VR NOT SUPPORTED'; - } + } - function showVRNotAllowed( exception ) { + function showVRNotAllowed( exception ) { - disableButton(); + disableButton(); - console.warn( 'Exception when trying to call xr.isSessionSupported', exception ); + console.warn( 'Exception when trying to call xr.isSessionSupported', exception ); - button.textContent = 'VR NOT ALLOWED'; + button.textContent = 'VR NOT ALLOWED'; - } - - function stylizeElement( element ) { - - element.style.position = 'absolute'; - element.style.bottom = '20px'; - element.style.padding = '12px 6px'; - element.style.border = '1px solid #fff'; - element.style.borderRadius = '4px'; - element.style.background = 'rgba(0,0,0,0.1)'; - element.style.color = '#fff'; - element.style.font = 'normal 13px sans-serif'; - element.style.textAlign = 'center'; - element.style.opacity = '0.5'; - element.style.outline = 'none'; - element.style.zIndex = '999'; + } - } + function stylizeElement( element ) { + + element.style.position = 'absolute'; + element.style.bottom = '20px'; + element.style.padding = '12px 6px'; + element.style.border = '1px solid #fff'; + element.style.borderRadius = '4px'; + element.style.background = 'rgba(0,0,0,0.1)'; + element.style.color = '#fff'; + element.style.font = 'normal 13px sans-serif'; + element.style.textAlign = 'center'; + element.style.opacity = '0.5'; + element.style.outline = 'none'; + element.style.zIndex = '999'; - if ( 'xr' in navigator ) { + } - button.id = 'VRButton'; - button.style.display = 'none'; + if ( 'xr' in navigator ) { - stylizeElement( button ); + button.id = 'VRButton'; + button.style.display = 'none'; - navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) { + stylizeElement( button ); - supported ? showEnterVR() : showWebXRNotFound(); + navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) { - if ( supported && VRButton.xrSessionIsGranted ) { + supported ? showEnterVR() : showWebXRNotFound(); - button.click(); + if ( supported && VRButton.xrSessionIsGranted ) { - } + button.click(); - } ).catch( showVRNotAllowed ); + } - return button; + } ).catch( showVRNotAllowed ); - } else { + return button; - const message = document.createElement( 'a' ); + } else { - if ( window.isSecureContext === false ) { + const message = document.createElement( 'a' ); - message.href = document.location.href.replace( /^http:/, 'https:' ); - message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message + if ( window.isSecureContext === false ) { - } else { + message.href = document.location.href.replace( /^http:/, 'https:' ); + message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message - message.href = 'https://immersiveweb.dev/'; - message.innerHTML = 'WEBXR NOT AVAILABLE'; + } else { - } + message.href = 'https://immersiveweb.dev/'; + message.innerHTML = 'WEBXR NOT AVAILABLE'; - message.style.left = 'calc(50% - 90px)'; - message.style.width = '180px'; - message.style.textDecoration = 'none'; + } - stylizeElement( message ); + message.style.left = 'calc(50% - 90px)'; + message.style.width = '180px'; + message.style.textDecoration = 'none'; - return message; + stylizeElement( message ); - } + return message; } - static registerSessionGrantedListener() { + } - if ( typeof navigator !== 'undefined' && 'xr' in navigator ) { + static registerSessionGrantedListener() { - // WebXRViewer (based on Firefox) has a bug where addEventListener - // throws a silent exception and aborts execution entirely. - if ( /WebXRViewer\//i.test( navigator.userAgent ) ) return; + if ( typeof navigator !== 'undefined' && 'xr' in navigator ) { - navigator.xr.addEventListener( 'sessiongranted', () => { + // WebXRViewer (based on Firefox) has a bug where addEventListener + // throws a silent exception and aborts execution entirely. + if ( /WebXRViewer\//i.test( navigator.userAgent ) ) return; - VRButton.xrSessionIsGranted = true; + navigator.xr.addEventListener( 'sessiongranted', () => { - } ); + VRButton.xrSessionIsGranted = true; - } + } ); } } - VRButton.xrSessionIsGranted = false; - VRButton.registerSessionGrantedListener(); +} + +VRButton.xrSessionIsGranted = false; +VRButton.registerSessionGrantedListener(); - return VRButton; +return VRButton; } )(); From 7e3bc6937bc293ac7fe11098c6300e830a2793d0 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 05:16:00 -0500 Subject: [PATCH 38/45] Addons: cleanup --- examples/jsm/exporters/GLTFExporter.js | 54 ++++---- examples/jsm/loaders/KTX2Loader.js | 18 +-- examples/jsm/objects/Lensflare.js | 52 +++---- .../jsm/shaders/SubsurfaceScatteringShader.js | 131 +++++++++--------- 4 files changed, 123 insertions(+), 132 deletions(-) diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 904fe51c6ed2aa..e2ec5b4f56e370 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -263,7 +263,7 @@ const PATH_PROPERTIES = { morphTargetInfluences: 'weights' }; -const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color(); +const DEFAULT_SPECULAR_COLOR = new Color(); // GLB constants // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification @@ -343,7 +343,7 @@ function getMinMax( attribute, start, count ) { if ( attribute.itemSize > 4 ) { - // no support for interleaved data for itemSize > 4 + // no support for interleaved data for itemSize > 4 value = attribute.array[ i * attribute.itemSize + a ]; @@ -521,7 +521,7 @@ class GLTFWriter { async write( input, onDone, options = {} ) { this.options = Object.assign( { - // default options + // default options binary: false, trs: false, onlyVisible: true, @@ -588,8 +588,8 @@ class GLTFWriter { headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); headerView.setUint32( 4, GLB_VERSION, true ); const totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + + jsonChunkPrefix.byteLength + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + binaryChunk.byteLength; headerView.setUint32( 8, totalByteLength, true ); const glbBlob = new Blob( [ @@ -672,7 +672,7 @@ class GLTFWriter { } catch ( error ) { console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); } @@ -989,7 +989,7 @@ class GLTFWriter { if ( attribute.itemSize > 4 ) { - // no support for interleaved data for itemSize > 4 + // no support for interleaved data for itemSize > 4 value = attribute.array[ i * attribute.itemSize + a ]; @@ -1211,13 +1211,13 @@ class GLTFWriter { } /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (RGBAFormat) - * @param {Boolean} flipY before writing out the image - * @param {String} mimeType export format - * @return {Integer} Index of the processed texture in the "images" array - */ + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (RGBAFormat) + * @param {Boolean} flipY before writing out the image + * @param {String} mimeType export format + * @return {Integer} Index of the processed texture in the "images" array + */ processImage( image, format, flipY, mimeType = 'image/png' ) { if ( image !== null ) { @@ -1679,7 +1679,7 @@ class GLTFWriter { // Prefix all geometry attributes except the ones specifically // listed in the spec; non-spec attributes are considered custom. const validVertexAttributes = - /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; @@ -1695,8 +1695,8 @@ class GLTFWriter { const array = attribute.array; if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { + ! ( array instanceof Uint16Array ) && + ! ( array instanceof Uint8Array ) ) { console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); @@ -2115,7 +2115,7 @@ class GLTFWriter { * @param {THREE.Object3D} object * @return {number|null} */ - processSkin( object ) { + processSkin( object ) { const json = this.json; const nodeMap = this.nodeMap; @@ -2456,18 +2456,18 @@ class GLTFLightExtension { if ( light.decay !== undefined && light.decay !== 2 ) { console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); + + 'and expects light.decay=2.' ); } if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { + && ( light.target.parent !== light + || light.target.position.x !== 0 + || light.target.position.y !== 0 + || light.target.position.z !== - 1 ) ) { console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); + + 'make light.target a child of the light with position 0,0,-1.' ); } @@ -2796,8 +2796,8 @@ class GLTFMaterialsSpecularExtension { writeMaterial( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && - material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && - ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; + material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && + ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; const writer = this.writer; const extensionsUsed = writer.extensionsUsed; @@ -3143,7 +3143,7 @@ GLTFExporter.Utils = { } if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index 07449cddb8a524..5edbbc6d3cbaff 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -98,7 +98,7 @@ class KTX2Loader extends Loader { console.warn( 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' - + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' + + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' ); @@ -144,7 +144,7 @@ class KTX2Loader extends Loader { dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) - || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) }; if ( renderer.capabilities.isWebGL2 ) { @@ -216,7 +216,7 @@ class KTX2Loader extends Loader { console.warn( 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' - + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' + + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' ); @@ -298,10 +298,10 @@ class KTX2Loader extends Loader { } /** - * @param {ArrayBuffer} buffer - * @param {object?} config - * @return {Promise} - */ + * @param {ArrayBuffer} buffer + * @param {object?} config + * @return {Promise} + */ async _createTexture( buffer, config = {} ) { const container = read( new Uint8Array( buffer ) ); @@ -867,8 +867,8 @@ async function createRawTexture( container ) { if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) { texture = container.pixelDepth === 0 - ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) - : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); + ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) + : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); } else { diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index 2df3bbacc62a65..f2cd4997dada2d 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -15,7 +15,7 @@ import { Vector4 } from 'three'; -const Lensflare = /* @__PURE__ */ ( () => { +const { Lensflare, LensflareElement } = /* @__PURE__ */ ( () => { class Lensflare extends Mesh { @@ -285,35 +285,8 @@ class Lensflare extends Mesh { } -Lensflare.Geometry = /* @__PURE__ */ ( function () { - - const geometry = new BufferGeometry(); - - const float32Array = new Float32Array( [ - - 1, - 1, 0, 0, 0, - 1, - 1, 0, 1, 0, - 1, 1, 0, 1, 1, - - 1, 1, 0, 0, 1 - ] ); - - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - - geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - - return geometry; - -} )(); - -return Lensflare; - -} )(); - // -const LensflareElement = /* @__PURE__ */ ( () => { - class LensflareElement { constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { @@ -399,7 +372,28 @@ LensflareElement.Shader = { }; -return LensflareElement; +Lensflare.Geometry = ( function () { + + const geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + return geometry; + +} )(); + +return { Lensflare, LensflareElement }; } )(); diff --git a/examples/jsm/shaders/SubsurfaceScatteringShader.js b/examples/jsm/shaders/SubsurfaceScatteringShader.js index 2d13b934ebe5ad..884a31dcc75ac0 100644 --- a/examples/jsm/shaders/SubsurfaceScatteringShader.js +++ b/examples/jsm/shaders/SubsurfaceScatteringShader.js @@ -13,85 +13,82 @@ import { *------------------------------------------------------------------------------------------ */ +const SubsurfaceScatteringShader = /* @__PURE__ */ ( () => { + function replaceAll( string, find, replace ) { return string.split( find ).join( replace ); } -const SubsurfaceScatteringShader = /* @__PURE__ */ ( () => { +const meshphong_frag_head = ShaderChunk[ 'meshphong_frag' ].slice( 0, ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); +const meshphong_frag_body = ShaderChunk[ 'meshphong_frag' ].slice( ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); + +const SubsurfaceScatteringShader = { + + uniforms: UniformsUtils.merge( [ + ShaderLib[ 'phong' ].uniforms, + { + 'thicknessMap': { value: null }, + 'thicknessColor': { value: new Color( 0xffffff ) }, + 'thicknessDistortion': { value: 0.1 }, + 'thicknessAmbient': { value: 0.0 }, + 'thicknessAttenuation': { value: 0.1 }, + 'thicknessPower': { value: 2.0 }, + 'thicknessScale': { value: 10.0 } + } + + ] ), + + vertexShader: [ + '#define USE_UV', + ShaderChunk[ 'meshphong_vert' ], + ].join( '\n' ), + + fragmentShader: [ + '#define USE_UV', + '#define SUBSURFACE', + + meshphong_frag_head, + + 'uniform sampler2D thicknessMap;', + 'uniform float thicknessPower;', + 'uniform float thicknessScale;', + 'uniform float thicknessDistortion;', + 'uniform float thicknessAmbient;', + 'uniform float thicknessAttenuation;', + 'uniform vec3 thicknessColor;', + + 'void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {', + ' vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r;', + ' vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));', + ' float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;', + ' vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness;', + ' reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;', + '}', + + meshphong_frag_body.replace( '#include ', + + replaceAll( + ShaderChunk[ 'lights_fragment_begin' ], + 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', + [ + 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', + + '#if defined( SUBSURFACE ) && defined( USE_UV )', + ' RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);', + '#endif', + ].join( '\n' ) + ), - const meshphong_frag_head = ShaderChunk[ 'meshphong_frag' ].slice( 0, ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); - const meshphong_frag_body = ShaderChunk[ 'meshphong_frag' ].slice( ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); - - const SubsurfaceScatteringShader = { - - uniforms: /* @__PURE__ */ UniformsUtils.merge( [ - ShaderLib[ 'phong' ].uniforms, - { - 'thicknessMap': { value: null }, - 'thicknessColor': { value: /* @__PURE__ */ new Color( 0xffffff ) }, - 'thicknessDistortion': { value: 0.1 }, - 'thicknessAmbient': { value: 0.0 }, - 'thicknessAttenuation': { value: 0.1 }, - 'thicknessPower': { value: 2.0 }, - 'thicknessScale': { value: 10.0 } - } - - ] ), - - vertexShader: /* glsl */ ` - #define USE_UV - ${ShaderChunk[ 'meshphong_vert' ]} - `, - - fragmentShader: /* glsl */ ` - #define USE_UV - #define SUBSURFACE - - ${meshphong_frag_head} - - uniform sampler2D thicknessMap; - uniform float thicknessPower; - uniform float thicknessScale; - uniform float thicknessDistortion; - uniform float thicknessAmbient; - uniform float thicknessAttenuation; - uniform vec3 thicknessColor; - - void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) { - vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r; - vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion)); - float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale; - vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness; - reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color; - } - - ${meshphong_frag_body.replace( - - '#include ', - - replaceAll( - ShaderChunk[ 'lights_fragment_begin' ], - 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', - /* glsl */ ` - RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); - - #if defined( SUBSURFACE ) && defined( USE_UV ) - RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight); - #endif - ` ), - )} + ].join( '\n' ), - `, +}; - }; - - return SubsurfaceScatteringShader; +return SubsurfaceScatteringShader; } )(); - export { SubsurfaceScatteringShader }; From b9d5666c97b682e26aeb3467bc4cf8ed28d234ef Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 05:25:13 -0500 Subject: [PATCH 39/45] VRMLLoader: refactor parser back to class from factory --- examples/jsm/loaders/VRMLLoader.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/jsm/loaders/VRMLLoader.js b/examples/jsm/loaders/VRMLLoader.js index 916e46a53e1b61..7db34fa2d76acf 100644 --- a/examples/jsm/loaders/VRMLLoader.js +++ b/examples/jsm/loaders/VRMLLoader.js @@ -91,7 +91,7 @@ class VRMLLoader extends Loader { const tokenData = createTokens(); const lexer = new VRMLLexer( tokenData.tokens ); - const parser = VRMLParser.createParser( tokenData.tokenVocabulary ); + const parser = new VRMLParser( tokenData.tokenVocabulary ); const visitor = createVisitor( parser.getBaseCstVisitorConstructor() ); // lexing @@ -3275,11 +3275,15 @@ class VRMLLexer { } -class VRMLParser { +const { CstParser } = chevrotain; - static createParser( tokenVocabulary ) { +class VRMLParser extends CstParser { - const $ = new chevrotain.CstParser( tokenVocabulary ); + constructor( tokenVocabulary ) { + + super( tokenVocabulary ); + + const $ = this; const Version = tokenVocabulary[ 'Version' ]; const LCurly = tokenVocabulary[ 'LCurly' ]; @@ -3500,9 +3504,7 @@ class VRMLParser { } ); - $.performSelfAnalysis(); - - return $; + this.performSelfAnalysis(); } From ab42802c10b4a42092c6330d4158aebb4d29f3ec Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 06:40:49 -0500 Subject: [PATCH 40/45] Addons: cleanup --- examples/jsm/libs/mmdparser.module.js | 4 ++-- examples/jsm/modifiers/EdgeSplitModifier.js | 2 ++ examples/jsm/postprocessing/UnrealBloomPass.js | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/jsm/libs/mmdparser.module.js b/examples/jsm/libs/mmdparser.module.js index 20314272a9b8d3..468025713df516 100644 --- a/examples/jsm/libs/mmdparser.module.js +++ b/examples/jsm/libs/mmdparser.module.js @@ -11525,8 +11525,8 @@ Parser.prototype.leftToRightVpd = function ( vpd ) { }; var MMDParser = { - CharsetEncoder, - Parser + CharsetEncoder: CharsetEncoder, + Parser: Parser }; return { MMDParser, CharsetEncoder, Parser }; diff --git a/examples/jsm/modifiers/EdgeSplitModifier.js b/examples/jsm/modifiers/EdgeSplitModifier.js index 48210b00bf4ded..0505ccebe00c5c 100644 --- a/examples/jsm/modifiers/EdgeSplitModifier.js +++ b/examples/jsm/modifiers/EdgeSplitModifier.js @@ -274,4 +274,6 @@ class EdgeSplitModifier { } + + export { EdgeSplitModifier }; diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index badcc48360494e..c49b6e4d0f57a3 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -22,7 +22,6 @@ import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js * Reference: * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ */ - const UnrealBloomPass = /* @__PURE__ */ ( () => { class UnrealBloomPass extends Pass { From 962b57c044dc7cefd56491e26c087c5bb7ec8b2c Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 06:41:31 -0500 Subject: [PATCH 41/45] Addons: destructure use of built-in shaders and uniforms --- examples/jsm/csm/CSMShader.js | 8 +++-- examples/jsm/materials/MeshGouraudMaterial.js | 31 +++++++++++++------ examples/jsm/shaders/VelocityShader.js | 10 +++--- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/examples/jsm/csm/CSMShader.js b/examples/jsm/csm/CSMShader.js index e7a4b44a75468a..9aace2d94632c8 100644 --- a/examples/jsm/csm/CSMShader.js +++ b/examples/jsm/csm/CSMShader.js @@ -1,6 +1,8 @@ import { ShaderChunk } from 'three'; -const CSMShader = /* @__PURE__ */ ( () => ( { +const { lights_pars_begin } = ShaderChunk; + +const CSMShader = { lights_fragment_begin: /* glsl */` vec3 geometryPosition = - vViewPosition; vec3 geometryNormal = normal; @@ -284,7 +286,7 @@ uniform vec2 CSM_cascades[CSM_CASCADES]; uniform float cameraNear; uniform float shadowFar; #endif - ` + ShaderChunk.lights_pars_begin -} ) )(); + ` + lights_pars_begin +}; export { CSMShader }; diff --git a/examples/jsm/materials/MeshGouraudMaterial.js b/examples/jsm/materials/MeshGouraudMaterial.js index b3edfba1fa3685..75087383e64065 100644 --- a/examples/jsm/materials/MeshGouraudMaterial.js +++ b/examples/jsm/materials/MeshGouraudMaterial.js @@ -7,21 +7,32 @@ import { UniformsUtils, UniformsLib, ShaderMaterial, Color, MultiplyOperation } from 'three'; +const { + common, + specularmap, + envmap, + aomap, + lightmap, + emissivemap, + fog, + lights, +} = UniformsLib; + const GouraudShader = { - uniforms: /* @__PURE__ */ ( () => UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.fog, - UniformsLib.lights, + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ + common, + specularmap, + envmap, + aomap, + lightmap, + emissivemap, + fog, + lights, { emissive: { value: /* @__PURE__ */ new Color( 0x000000 ) } } - ] ) )(), + ] ), vertexShader: /* glsl */` diff --git a/examples/jsm/shaders/VelocityShader.js b/examples/jsm/shaders/VelocityShader.js index 288c208fe7ee16..61cecc577be26e 100644 --- a/examples/jsm/shaders/VelocityShader.js +++ b/examples/jsm/shaders/VelocityShader.js @@ -4,21 +4,23 @@ import { Matrix4 } from 'three'; +const { common, displacementmap } = UniformsLib; + /** * Mesh Velocity Shader @bhouston */ const VelocityShader = { - uniforms: /* @__PURE__ */ ( () => UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.displacementmap, + uniforms: /* @__PURE__ */ UniformsUtils.merge( [ + common, + displacementmap, { modelMatrixPrev: { value: /* @__PURE__ */ new Matrix4() }, currentProjectionViewMatrix: { value: /* @__PURE__ */ new Matrix4() }, previousProjectionViewMatrix: { value: /* @__PURE__ */ new Matrix4() } } - ] ) )(), + ] ), vertexShader: /* glsl */` #define NORMAL From 5b56afd3cbe9d56455e4aa1045b3480138a639f7 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:30:21 -0500 Subject: [PATCH 42/45] utif: cleanup --- examples/jsm/libs/utif.module.js | 2576 +++++++++++++++--------------- 1 file changed, 1288 insertions(+), 1288 deletions(-) diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index 9129b35d794e67..4869ca70fd3609 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -28,429 +28,429 @@ if(this.p>4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p UTIF.encodeImage = function(rgba, w, h, metadata) { -var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset - "t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts - "t282":[[72,1]], "t283":[[72,1]], "t284":[1], "t286":[[0,1]], "t287":[[0,1]], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1] - }; -if (metadata) for (var i in metadata) idf[i] = metadata[i]; - -var prfx = new Uint8Array(UTIF.encode([idf])); -var img = new Uint8Array(rgba); -var data = new Uint8Array(1000+w*h*4); -for(var i=0; i probably not an image -img.isLE = id=="II"; -img.width = img["t256"][0]; //delete img["t256"]; -img.height = img["t257"][0]; //delete img["t257"]; - -var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; -var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; -if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); -if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); - -var spp = img["t277"]?img["t277"][0]:1; -var bps = img["t258"]?img["t258"][0]:1; -var bipp = bps*spp; // bits per pixel -/* -var bipp; // bits per pixel -if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; -else bipp = (img["t277"]?img["t277"][0]:1); -*/ -// Some .NEF files have t258==14, even though they use 16 bits per pixel -if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { - bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); -} -if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 -var bipl = Math.ceil(img.width*bipp/8)*8; -var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; -var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; -//bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" -var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; - -if(img["t322"]!=null) // tiled -{ - var tw = img["t322"][0], th = img["t323"][0]; - var tx = Math.floor((img.width + tw - 1) / tw); - var ty = Math.floor((img.height + th - 1) / th); - var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); - console.log("====", tx,ty); - for(var y=0; y probably not an image + img.isLE = id=="II"; + img.width = img["t256"][0]; //delete img["t256"]; + img.height = img["t257"][0]; //delete img["t257"]; + + var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; + var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; + if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); + if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); + + var spp = img["t277"]?img["t277"][0]:1; + var bps = img["t258"]?img["t258"][0]:1; + var bipp = bps*spp; // bits per pixel + /* + var bipp; // bits per pixel + if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; + else bipp = (img["t277"]?img["t277"][0]:1); + */ + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { + bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); + } + if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 + var bipl = Math.ceil(img.width*bipp/8)*8; + var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; + var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; + + if(img["t322"]!=null) // tiled + { + var tw = img["t322"][0], th = img["t323"][0]; + var tx = Math.floor((img.width + tw - 1) / tw); + var ty = Math.floor((img.height + th - 1) / th); + var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); + console.log("====", tx,ty); + for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); - -// convert to Little Endian /* -if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG - for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); + + // convert to Little Endian /* + if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG + for(var y=0; y>>8)&255; - } - else if(noc==3) for(var j= 3; j>>8)&255; + } + else if(noc==3) for(var j= 3; j> 3 ^ 0x3ff0; - return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); - } - } - // Raw Format 6 - function getBufferDataRW6(i) { - return buffer[vpos + 15 - i]; - } - function readPageRW6() { - bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit - bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; - bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); - bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); - bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); - bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; - bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); - bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); - bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; - bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); - bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; - bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; - vpos += 16; - byte = 0; - } - function readPageRw6_bps12() { - bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); - bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; - bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); - bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); - bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); - bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; - bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); - bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); - bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); - bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); - bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); - bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); - bytes[14] = getBufferDataRW6(12) & 0x3; - bytes[15] = getBufferDataRW6(13); - bytes[16] = getBufferDataRW6(14); - bytes[17] = getBufferDataRW6(15); - - vpos += 16; - byte = 0; - } - // Main loop - function resetPredNonzeros(){ - pred[0]=0; pred[1]=0; - nonz[0]=0; nonz[1]=0; - } - if (RW2_Format == 7) { - throw RW2_Format; - - // Skatch of version 7 - /* - var pixels_per_block = bitsPerSample == 14 ? 9 : 10; - rowbytes = 0|(rawWidth / pixels_per_block * 16); - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - i = 0; - for (crow = 0; crow < rowstoread; crow++) { - idx = (row + crow) * rawWidth; - for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { - for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; - if (bitsPerSample == 12) { - result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - } - */ - } - else if(RW2_Format == 6) { - var is12bit = bitsPerSample == 12, - readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, - pixelsPerBlock = is12bit ? 14 : 11, - pixelbase0 = is12bit ? 0x80 : 0x200, - pixelbase_compare = is12bit ? 0x800 : 0x2000, - spix_compare = is12bit ? 0x3fff : 0xffff, - pixel_mask = is12bit ? 0xfff : 0x3fff, - blocksperrow = rawWidth / pixelsPerBlock, - rowbytes = blocksperrow * 16, - bufferSize = is12bit ? 18 : 14; - - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { - idx = (row + crow) * rawWidth; - for (var rblock = 0; rblock < blocksperrow; rblock++) { - readPageRw6Fn(); - resetPredNonzeros(); - sh=0; pixel_base=0; - for (i = 0; i < pixelsPerBlock; i++){ - isOdd = i & 1; - if (i % 3 == 2) { - var base = byte < bufferSize ? bytes[byte++] : 0; - if (base == 3) base = 4; - pixel_base = pixelbase0 << base; - sh = 1 << base; - } - var epixel = byte < bufferSize ? bytes[byte++] : 0; - if (pred[isOdd]) { - epixel *= sh; - if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) - epixel += nonz[isOdd] - pixel_base; - nonz[isOdd] = epixel; - } else { - pred[isOdd] = epixel; - if (epixel) - nonz[isOdd] = epixel; - else - epixel = nonz[isOdd]; - } - result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; - } - } - } - } - } - else if (RW2_Format == 5) { - var blockSize = bitsPerSample == 12 ? 10 : 9; - for (row = 0; row < rawHeight; row++) { - for (col = 0; col < rawWidth; col+=blockSize) { - getDataRaw(0); - // Tuhle podminku pouziva i RW2_Format 7 - if (bitsPerSample == 12) { - result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } - } - } - //console.log(result[1000000 - 1]) - } else if(RW2_Format == 4) { - for (row = 0; row < rawHeight; row++){ - for(col = 0; col < rawWidth; col++){ - i = col % 14; - isOdd = i & 1; - if (i==0) resetPredNonzeros(); - if (i%3 == 2) - sh = 4 >> (3 - getDataRaw(2)); - if (nonz[isOdd]) { - j = getDataRaw(8); - if(j != 0){ - pred[isOdd] -= 0x80 << sh; - if (pred[isOdd] < 0 || sh == 4) - pred[isOdd] &= ~((-1) << sh); - pred[isOdd] += j << sh; - } - } else { - nonz[isOdd] = getDataRaw(8); - if(nonz[isOdd] || i > 11) - pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); - } - result[idx++] = pred[col & 1]; - } - } - } - else throw RW2_Format; - } + UTIF.decode._decodePanasonic = function(img, data, off, len, tgt, toff) { + + var img_buffer = data.buffer; + + var rawWidth = img["t2"][0]; + var rawHeight = img["t3"][0]; + var bitsPerSample = img["t10"][0]; + var RW2_Format = img["t45"][0]; + + var bidx = 0; + var imageIndex = 0; + var vpos = 0; + var byte = 0; + var arr_a, arr_b; + var bytes = (RW2_Format == 6 ? new Uint32Array(18) : new Uint8Array(16)); + var i, j, sh, pred=[0,0], nonz=[0,0], isOdd, idx = 0, pixel_base; + var row, col, crow; + var buffer = new Uint8Array(0x4000); + var result = new Uint16Array(tgt.buffer); + + function getDataRaw(bits){ + if (vpos == 0) { + var arr_a = new Uint8Array(img_buffer, off+imageIndex + 0x1ff8, 0x4000-0x1ff8); + var arr_b = new Uint8Array(img_buffer, off+imageIndex, 0x1ff8); + buffer.set(arr_a); + buffer.set(arr_b, arr_a.length); + imageIndex += 0x4000; + } + if(RW2_Format == 5) { + for (i = 0; i < 16; i++){ + bytes[i] = buffer[vpos++]; + vpos &= 0x3FFF; + } + } else { + vpos = (vpos - bits) & 0x1ffff; + byte = vpos >> 3 ^ 0x3ff0; + return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); + } + } + // Raw Format 6 + function getBufferDataRW6(i) { + return buffer[vpos + 15 - i]; + } + function readPageRW6() { + bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit + bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; + bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); + bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); + bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); + bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; + bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); + bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); + bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; + bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); + bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; + bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; + vpos += 16; + byte = 0; + } + function readPageRw6_bps12() { + bytes[0] = (getBufferDataRW6(0) << 4) | (getBufferDataRW6(1) >> 4); + bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; + bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); + bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); + bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); + bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; + bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); + bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); + bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); + bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); + bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); + bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); + bytes[14] = getBufferDataRW6(12) & 0x3; + bytes[15] = getBufferDataRW6(13); + bytes[16] = getBufferDataRW6(14); + bytes[17] = getBufferDataRW6(15); + + vpos += 16; + byte = 0; + } + // Main loop + function resetPredNonzeros(){ + pred[0]=0; pred[1]=0; + nonz[0]=0; nonz[1]=0; + } + if (RW2_Format == 7) { + throw RW2_Format; + + // Skatch of version 7 + /* + var pixels_per_block = bitsPerSample == 14 ? 9 : 10; + rowbytes = 0|(rawWidth / pixels_per_block * 16); + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + i = 0; + for (crow = 0; crow < rowstoread; crow++) { + idx = (row + crow) * rawWidth; + for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { + for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; + if (bitsPerSample == 12) { + result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + } + */ + } + else if(RW2_Format == 6) { + var is12bit = bitsPerSample == 12, + readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, + pixelsPerBlock = is12bit ? 14 : 11, + pixelbase0 = is12bit ? 0x80 : 0x200, + pixelbase_compare = is12bit ? 0x800 : 0x2000, + spix_compare = is12bit ? 0x3fff : 0xffff, + pixel_mask = is12bit ? 0xfff : 0x3fff, + blocksperrow = rawWidth / pixelsPerBlock, + rowbytes = blocksperrow * 16, + bufferSize = is12bit ? 18 : 14; + + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { + idx = (row + crow) * rawWidth; + for (var rblock = 0; rblock < blocksperrow; rblock++) { + readPageRw6Fn(); + resetPredNonzeros(); + sh=0; pixel_base=0; + for (i = 0; i < pixelsPerBlock; i++){ + isOdd = i & 1; + if (i % 3 == 2) { + var base = byte < bufferSize ? bytes[byte++] : 0; + if (base == 3) base = 4; + pixel_base = pixelbase0 << base; + sh = 1 << base; + } + var epixel = byte < bufferSize ? bytes[byte++] : 0; + if (pred[isOdd]) { + epixel *= sh; + if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) + epixel += nonz[isOdd] - pixel_base; + nonz[isOdd] = epixel; + } else { + pred[isOdd] = epixel; + if (epixel) + nonz[isOdd] = epixel; + else + epixel = nonz[isOdd]; + } + result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; + } + } + } + } + } + else if (RW2_Format == 5) { + var blockSize = bitsPerSample == 12 ? 10 : 9; + for (row = 0; row < rawHeight; row++) { + for (col = 0; col < rawWidth; col+=blockSize) { + getDataRaw(0); + // Tuhle podminku pouziva i RW2_Format 7 + if (bitsPerSample == 12) { + result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } + } + } + //console.log(result[1000000 - 1]) + } else if(RW2_Format == 4) { + for (row = 0; row < rawHeight; row++){ + for(col = 0; col < rawWidth; col++){ + i = col % 14; + isOdd = i & 1; + if (i==0) resetPredNonzeros(); + if (i%3 == 2) + sh = 4 >> (3 - getDataRaw(2)); + if (nonz[isOdd]) { + j = getDataRaw(8); + if(j != 0){ + pred[isOdd] -= 0x80 << sh; + if (pred[isOdd] < 0 || sh == 4) + pred[isOdd] &= ~((-1) << sh); + pred[isOdd] += j << sh; + } + } else { + nonz[isOdd] = getDataRaw(8); + if(nonz[isOdd] || i > 11) + pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); + } + result[idx++] = pred[col & 1]; + } + } + } + else throw RW2_Format; + } UTIF.decode._decodeVC5 = function(){var x=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],o,C,k,P=[3,3,3,3,2,2,2,1,1,1],V=24576,ar=16384,H=8192,az=ar|H; @@ -483,668 +483,668 @@ L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==92 UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { -var w = img.width, qw=w*4; -var io = 0, out = new Uint8Array(qw); - -while(io>> (tab[i] >>> 8); - for(var c=0; c>> (tab[i] >>> 8); + for(var c=0; c>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } + return; } - for (col = raw_width; col--; ) - for (row=0; row < height+1; row+=2) { - if (row == height) row = 1; - sum += ljpeg_diff(inp, prm, huff); - if (row < height) { - var clr = (sum)&4095; + + var pix = new Uint16Array(16); + var row, col, val, max, min, imax, imin, sh, bit, i, dp; + + var data = new Uint8Array(raw_width+1); + for (row=0; row < height; row++) { + //fread (data, 1, raw_width, ifp); + for(var j=0; j>> 11); + imax = 0x0f & (val >>> 22); + imin = 0x0f & (val >>> 26); + for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); + for (bit=30, i=0; i < 16; i++) + if (i == imax) pix[i] = max; + else if (i == imin) pix[i] = min; + else { + pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; + if (pix[i] > 0x7ff) pix[i] = 0x7ff; + bit += 7; + } + for (i=0; i < 16; i++, col+=2) { + //RAW(row,col) = curve[pix[i] << 1] >> 2; + var clr = pix[i]<<1; //clr = 0xffff; UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); } + col -= col & 1 ? 1:31; } - return; -} -if(raw_width*height*1.5==src_length) { - //console.log("weird compression"); - for(var i=0; i>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } - return; -} - -var pix = new Uint16Array(16); -var row, col, val, max, min, imax, imin, sh, bit, i, dp; - -var data = new Uint8Array(raw_width+1); -for (row=0; row < height; row++) { - //fread (data, 1, raw_width, ifp); - for(var j=0; j>> 11); - imax = 0x0f & (val >>> 22); - imin = 0x0f & (val >>> 26); - for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); - for (bit=30, i=0; i < 16; i++) - if (i == imax) pix[i] = max; - else if (i == imin) pix[i] = min; - else { - pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; - if (pix[i] > 0x7ff) pix[i] = 0x7ff; - bit += 7; - } - for (i=0; i < 16; i++, col+=2) { - //RAW(row,col) = curve[pix[i] << 1] >> 2; - var clr = pix[i]<<1; //clr = 0xffff; - UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); - } - col -= col & 1 ? 1:31; } } -} UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) { -var nikon_tree = [ - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ - 5,4,3,6,2,7,1,0,8,9,11,10,12 ], - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ - 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], - [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ - 5,4,6,3,7,2,8,1,9,0,10,11,12 ], - [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ - 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], - [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ - 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], - [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ - 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; + var nikon_tree = [ + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ + 5,4,3,6,2,7,1,0,8,9,11,10,12 ], + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ + 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], + [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ + 5,4,6,3,7,2,8,1,9,0,10,11,12 ], + [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ + 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], + [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ + 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], + [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ + 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; + + var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; -var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; - -var tree = 0, split = 0; -var make_decoder = UTIF.decode._make_decoder; -var getbithuff = UTIF.decode._getbithuff; - -var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); -//console.log(md[0].toString(16), md[1].toString(16), tiff_bps); -var ver0 = md[mdo++], ver1 = md[mdo++]; -if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; -if (ver0 == 0x46) tree = 2; -if (tiff_bps == 14) tree += 3; - -var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); -for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] -//console.log(vpred); - - -var max = 1 << tiff_bps & 0x7fff, step=0; -var csize = bin.readShort(md,mdo); mdo+=2; -if (csize > 1) step = Math.floor(max / (csize-1)); -if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); - - -var i; -var row, col; -var len, shl, diff; -var min_v = 0; -var hpred = [0,0]; -var huff = make_decoder(nikon_tree[tree]); - -//var g_input_offset=0, bitbuf=0, vbits=0, reset=0; -var prm = [off,0,0,0]; -//console.log(split); split = 170; - -for (min_v=row=0; row < height; row++) { - if (split && row == split) { - //free (huff); - huff = make_decoder (nikon_tree[tree+1]); - //max_v += (min_v = 16) << 1; - } - for (col=0; col < raw_width; col++) { - i = getbithuff(data,prm,huff[0],huff); - len = i & 15; - shl = i >>> 4; - diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; - if ((diff & (1 << (len-1))) == 0) - diff -= (1 << len) - (shl==0?1:0); - if (col < 2) hpred[col] = vpred[row & 1][col] += diff; - else hpred[col & 1] += diff; - - var clr = Math.min(Math.max(hpred[col & 1],0),(1< 1) step = Math.floor(max / (csize-1)); + if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); + + + var i; + var row, col; + var len, shl, diff; + var min_v = 0; + var hpred = [0,0]; + var huff = make_decoder(nikon_tree[tree]); + + //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; + var prm = [off,0,0,0]; + //console.log(split); split = 170; + + for (min_v=row=0; row < height; row++) { + if (split && row == split) { + //free (huff); + huff = make_decoder (nikon_tree[tree+1]); + //max_v += (min_v = 16) << 1; + } + for (col=0; col < raw_width; col++) { + i = getbithuff(data,prm,huff[0],huff); + len = i & 15; + shl = i >>> 4; + diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - (shl==0?1:0); + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + + var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } UTIF.decode._getbithuff = function(data,prm,nbits, huff) { -var zero_after_ff = 0; -var get_byte = UTIF.decode._get_byte; -var c; - -var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; - -//if (nbits > 25) return 0; -//if (nbits < 0) return bitbuf = vbits = reset = 0; -if (nbits == 0 || vbits < 0) return 0; -while (!reset && vbits < nbits && (c = data[off++]) != -1 && - !(reset = zero_after_ff && c == 0xff && data[off++])) { - //console.log("byte read into c"); - bitbuf = (bitbuf << 8) + c; - vbits += 8; -} -c = (bitbuf << (32-vbits)) >>> (32-nbits); -if (huff) { - vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); - c = huff[c+1]&255; -} else - vbits -= nbits; -if (vbits < 0) throw "e"; - -prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; - -return c; + var zero_after_ff = 0; + var get_byte = UTIF.decode._get_byte; + var c; + + var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; + + //if (nbits > 25) return 0; + //if (nbits < 0) return bitbuf = vbits = reset = 0; + if (nbits == 0 || vbits < 0) return 0; + while (!reset && vbits < nbits && (c = data[off++]) != -1 && + !(reset = zero_after_ff && c == 0xff && data[off++])) { + //console.log("byte read into c"); + bitbuf = (bitbuf << 8) + c; + vbits += 8; + } + c = (bitbuf << (32-vbits)) >>> (32-nbits); + if (huff) { + vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); + c = huff[c+1]&255; + } else + vbits -= nbits; + if (vbits < 0) throw "e"; + + prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; + + return c; } UTIF.decode._make_decoder = function(source) { -var max, len, h, i, j; -var huff = []; - -for (max=16; max!=0 && !source[max]; max--); -var si=17; - -huff[0] = max; -for (h=len=1; len <= max; len++) - for (i=0; i < source[len]; i++, ++si) - for (j=0; j < 1 << (max-len); j++) - if (h <= 1 << max) - huff[h++] = (len << 8) | source[si]; -return huff; + var max, len, h, i, j; + var huff = []; + + for (max=16; max!=0 && !source[max]; max--); + var si=17; + + huff[0] = max; + for (h=len=1; len <= max; len++) + for (i=0; i < source[len]; i++, ++si) + for (j=0; j < 1 << (max-len); j++) + if (h <= 1 << max) + huff[h++] = (len << 8) | source[si]; + return huff; } UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff) { -len = Math.min(len, data.length-off); -var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); + len = Math.min(len, data.length-off); + var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); + + if (tables) { + var SOI = 216, EOI = 217, boff = 0; + for (var i=0; i<(tlen-1); i++) + { + // Skip EOI marker from JPEGTables + if (tables[i]==255 && tables[i+1]==EOI) break; + buff[boff++] = tables[i]; + } -if (tables) { - var SOI = 216, EOI = 217, boff = 0; - for (var i=0; i<(tlen-1); i++) - { - // Skip EOI marker from JPEGTables - if (tables[i]==255 && tables[i+1]==EOI) break; - buff[boff++] = tables[i]; + // Skip SOI marker from data + var byte1 = data[off], byte2 = data[off + 1]; + if (byte1!=255 || byte2!=SOI) + { + buff[boff++] = byte1; + buff[boff++] = byte2; + } + for (var i=2; i>>8); } - else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } - } - else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 - var rst = 16-bps; - for(var i=0; i>>8); } + else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } + } + else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 + var rst = 16-bps; + for(var i=0; i 1); -} + var SOI = 216, EOI = 217, DQT = 219, DHT = 196, DRI = 221, SOF0 = 192, SOS = 218; + var joff = 0, soff = 0, tables, sosMarker, isTiled = false, i, j, k; + var jpgIchgFmt = img["t513"], jifoff = jpgIchgFmt ? jpgIchgFmt[0] : 0; + var jpgIchgFmtLen = img["t514"], jiflen = jpgIchgFmtLen ? jpgIchgFmtLen[0] : 0; + var soffTag = img["t324"] || img["t273"] || jpgIchgFmt; + var ycbcrss = img["t530"], ssx = 0, ssy = 0; + var spp = img["t277"]?img["t277"][0]:1; + var jpgresint = img["t515"]; + + if(soffTag) + { + soff = soffTag[0]; + isTiled = (soffTag.length > 1); + } -if(!isTiled) -{ - if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; - if(jpgIchgFmt!=null) + if(!isTiled) { - if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; - else log("JPEGInterchangeFormat does not point to SOI"); + if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; + if(jpgIchgFmt!=null) + { + if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; + else log("JPEGInterchangeFormat does not point to SOI"); - if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); - else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); + if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); + else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); - if(joff != null) return { jpegOffset: joff }; + if(joff != null) return { jpegOffset: joff }; + } } -} -if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } + if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } -if(jpgIchgFmt!=null) - if(jpgIchgFmtLen!=null) - if(jiflen >= 2 && (jifoff+jiflen) <= soff) - { - if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); - else tables = new Uint8Array(jiflen); + if(jpgIchgFmt!=null) + if(jpgIchgFmtLen!=null) + if(jiflen >= 2 && (jifoff+jiflen) <= soff) + { + if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); + else tables = new Uint8Array(jiflen); + + for(i=0; i offset to first strip or tile"); - for(i=0; i offset to first strip or tile"); -if(tables == null) -{ - var ooff = 0, out = []; - out[ooff++] = 255; out[ooff++] = SOI; + for(k=0; k<2; k++) + { + var htables = img[(k == 0) ? "t520" : "t521"]; + if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing"); + for(i=0; i>> 8); out[ooff++] = nc & 255; + out[ooff++] = (i | (k << 4)); + for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; + for(j=0; j>> 8) & 255; out[ooff++] = img.height & 255; + out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; + out[ooff++] = spp; + if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } + else for(i=0; i<3; i++) + { + out[ooff++] = i + 1; + out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + out[ooff++] = i; + } - for(k=0; k<2; k++) - { - var htables = img[(k == 0) ? "t520" : "t521"]; - if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing"); - for(i=0; i>> 8); out[ooff++] = nc & 255; - out[ooff++] = (i | (k << 4)); - for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; - for(j=0; j>> 8) & 255; + out[ooff++] = jpgresint[0] & 255; } + + tables = new Uint8Array(out); } - out[ooff++] = 255; out[ooff++] = SOF0; - out[ooff++] = 0; out[ooff++] = 8 + 3*spp; out[ooff++] = 8; - out[ooff++] = (img.height >>> 8) & 255; out[ooff++] = img.height & 255; - out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; - out[ooff++] = spp; - if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } - else for(i=0; i<3; i++) - { - out[ooff++] = i + 1; - out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - out[ooff++] = i; + var sofpos = -1; + i = 0; + while(i < (tables.length - 1)) { + if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } + i++; } - if(jpgresint!=null && jpgresint[0]!=0) + if(sofpos == -1) { - out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; - out[ooff++] = (jpgresint[0] >>> 8) & 255; - out[ooff++] = jpgresint[0] & 255; + var tmptab = new Uint8Array(tables.length + 10 + 3*spp); + tmptab.set(tables); + var tmpoff = tables.length; + sofpos = tables.length; + tables = tmptab; + + tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; + tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; + tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; + tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; + tables[tmpoff++] = spp; + if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } + else for(i=0; i<3; i++) + { + tables[tmpoff++] = i + 1; + tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + tables[tmpoff++] = i; + } } - tables = new Uint8Array(out); -} - -var sofpos = -1; -i = 0; -while(i < (tables.length - 1)) { - if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } - i++; -} - -if(sofpos == -1) -{ - var tmptab = new Uint8Array(tables.length + 10 + 3*spp); - tmptab.set(tables); - var tmpoff = tables.length; - sofpos = tables.length; - tables = tmptab; - - tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; - tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; - tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; - tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; - tables[tmpoff++] = spp; - if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } - else for(i=0; i<3; i++) + if(data[soff]==255 && data[soff+1]==SOS) { - tables[tmpoff++] = i + 1; - tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - tables[tmpoff++] = i; + var soslen = (data[soff+2]<<8) | data[soff+3]; + sosMarker = new Uint8Array(soslen+2); + sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; + for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; } -} - -if(data[soff]==255 && data[soff+1]==SOS) -{ - var soslen = (data[soff+2]<<8) | data[soff+3]; - sosMarker = new Uint8Array(soslen+2); - sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; - for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; -} -else -{ - sosMarker = new Uint8Array(2 + 6 + 2*spp); - var sosoff = 0; - sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; - if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } - else for(i=0; i<3; i++) + else { - sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; + sosMarker = new Uint8Array(2 + 6 + 2*spp); + var sosoff = 0; + sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; + if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } + else for(i=0; i<3; i++) + { + sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; + } + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; } - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; -} -return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; + return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; } UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) { -var i, dlen, tlen, buff, buffoff; -var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); + var i, dlen, tlen, buff, buffoff; + var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); -if(jpegData.jpegOffset!=null) -{ - dlen = off+len-jpegData.jpegOffset; - buff = new Uint8Array(dlen); - for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; - buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; + buff[jpegData.sofPosition+5] = (img.height >>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; + buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; - if(data[off]!=255 || data[off+1]!=SOS) - { - buff.set(jpegData.sosMarker, buffoff); - buffoff += sosMarker.length; + if(data[off]!=255 || data[off+1]!=SOS) + { + buff.set(jpegData.sosMarker, buffoff); + buffoff += sosMarker.length; + } + for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } - if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } -} -return toff; + var sa = new Int8Array(data.buffer), ta = new Int8Array(tgt.buffer), lim = off+len; + while(off=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } + if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } + } + return toff; } UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) { -var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; -var lim = off+len, qoff = toff*2, px = 0; -while(off>>6), n = (b&63); off++; - if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } -} + var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; + var lim = off+len, qoff = toff*2, px = 0; + while(off>>6), n = (b&63); off++; + if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + } } UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 }; UTIF.decode._lens = ( function() { -var addKeys = function(lens, arr, i0, inc) { for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - if(mode=="H") + while((boff>>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + if(mode=="H") { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + } } + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + } + if(line.length==w && mode=="") + { + U._writeBits(line, tgt, toff*8+y*bipl); + clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; + } + //if(wrd.length>150) { log(wrd); break; throw "e"; } } - else - { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } - } - if(line.length==w && mode=="") - { - U._writeBits(line, tgt, toff*8+y*bipl); - clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; - } - //if(wrd.length>150) { log(wrd); break; throw "e"; } -} } UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } UTIF.decode._makeDiff = function(line) { -var out = []; if(line[0]==1) out.push(0,1); -for(var i=1; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; + var U = UTIF.decode, boff=off<<3, len=0, wrd=""; + var line=[]; + var clr = 0; + var y=0; + var bipl = Math.ceil(w/8)*8; - len = U._lens[clr][wrd]; - if(len!=null) { - U._addNtimes(line,len,clr); wrd=""; - if(len<64) clr = 1-clr; - if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } + while((boff>>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + len = U._lens[clr][wrd]; + if(len!=null) { + U._addNtimes(line,len,clr); wrd=""; + if(len<64) clr = 1-clr; + if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } + } } } -} UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) { -var U = UTIF.decode, boff=off<<3, len=0, wrd=""; -var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - if(is1D) - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } - } - } - else + var U = UTIF.decode, boff=off<<3, len=0, wrd=""; + var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + if(is1D) { if(U._lens[clr][wrd]!=null) { var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } } } else { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + if(mode=="H") + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + } + } + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + } } - } - if(wrd.endsWith("000000000001")) // needed for some files - { - if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); - if(twoDim) { - if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; - if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; - boff++; + if(wrd.endsWith("000000000001")) // needed for some files + { + if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); + if(twoDim) { + if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; + if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; + boff++; + } + //log("EOL",y, "next 1D:", is1D); + wrd=""; clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; } - //log("EOL",y, "next 1D:", is1D); - wrd=""; clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; } -} -if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); + if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); } UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); + for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); } UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>>----------------"); -for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } - if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); - if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); - else arr = new Uint8Array(data.buffer, o0, len); } - if(type== 3) { for(var j=0; j>>----------------"); + for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } + if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); + if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); + else arr = new Uint8Array(data.buffer, o0, len); } + if(type== 3) { for(var j=0; j4) { bin.writeUint(data, offset, eoff); toff=eoff; } + + if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } + offset += 4; } - if(type==2) val=val[0]+"\u0000"; var num = val.length; - bin.writeUshort(data, offset, tag ); offset+=2; - bin.writeUshort(data, offset, type); offset+=2; - bin.writeUint (data, offset, num ); offset+=4; - - var dlen = [-1, 1, 1, 2, 4, 8, 0, 1, 0, 4, 8, 0, 8][type] * num; //if(dlen<1) throw "e"; - var toff = offset; - if(dlen>4) { bin.writeUint(data, offset, eoff); toff=eoff; } - - if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } - offset += 4; -} -return [offset, eoff]; + return [offset, eoff]; } UTIF.toRGBA8 = function(out, scl) { -function gamma(x) { return x < 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; } - - -var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; -var img = new Uint8Array(area*4); -//console.log(out); -// 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK -var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); -if(out["t262"]==null && bps==1) intp=0; - -var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); -var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format -var bpl = Math.ceil(smpls*bps*w/8); - -//log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); - -if(false) {} -else if(intp==0) -{ - scl = 1/256; // "Photopeatest.tif" - for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } - if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } - if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } - } -} -else if(intp==2) -{ - if(bps== 8) + + var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; + var img = new Uint8Array(area*4); + //console.log(out); + // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK + var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); + if(out["t262"]==null && bps==1) intp=0; + + var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); + var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format + var bpl = Math.ceil(smpls*bps*w/8); + + //log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); + + if(false) {} + else if(intp==0) { - if(smpls==1) for(var i=0; i=4) for(var i=0; i>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } + if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } + if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } + } } - else throw bps; -} -else if(intp==3) -{ - var map = out["t320"]; - var cn = 1<1 && out["t338"] && out["t338"][0]!=0; - - for(var y=0; y>>3)]>>>(7- (x&7)))& 1; - else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; - else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; - else if(bps==8) mi= data[dof+x*smpls]; - else throw bps; - img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; + else if(intp==2) + { + if(bps== 8) + { + if(smpls==1) for(var i=0; i=4) for(var i=0; i4 ? 1 : 0; - for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; - for(var j=0; j>>1); - var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; + for(var y=0; y>>3)]>>>(7- (x&7)))& 1; + else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; + else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; + else if(bps==8) mi= data[dof+x*smpls]; + else throw bps; + img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; + } + } + else if(intp==5) + { + var gotAlpha = smpls>4 ? 1 : 0; + for(var i=0; i> 2) + (Cr >> 3) + (Cr >> 5) ) ; - var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; - var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; + if(window.UDOC) { + var C=data[si], M=data[si+1], Y=data[si+2], K=data[si+3]; + var c = UDOC.C.cmykToRgb([C*(1/255), M*(1/255), Y*(1/255), K*(1/255)]); + img[qi] = ~~(0.5+255*c[0]); img[qi+1] = ~~(0.5+255*c[1]); img[qi+2] = ~~(0.5+255*c[2]); + } + else { + var C=255-data[si], M=255-data[si+1], Y=255-data[si+2], K=(255-data[si+3])*(1/255); + img[qi]=~~(C*K+0.5); img[qi+1]=~~(M*K+0.5); img[qi+2]=~~(Y*K+0.5); + } - img[qi ]=Math.max(0,Math.min(255,r)); - img[qi+1]=Math.max(0,Math.min(255,g)); - img[qi+2]=Math.max(0,Math.min(255,b)); - img[qi+3]=255; + img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha; } } -} -else if(intp==32845) { - - for(var y=0; y>>1); + var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; + + var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; + var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; + var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; + + img[qi ]=Math.max(0,Math.min(255,r)); + img[qi+1]=Math.max(0,Math.min(255,g)); + img[qi+2]=Math.max(0,Math.min(255,b)); + img[qi+3]=255; + } } -} -else log("Unknown Photometric interpretation: "+intp); -return img; + } + else if(intp==32845) { + + for(var y=0; yma) { ma=ar; page=img; } -} -UTIF.decodeImage(buff, page, ifds); -var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; - -var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; -var ctx = cnv.getContext("2d"); -var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); -ctx.putImageData(imgd,0,0); -return cnv.toDataURL(); + var ifds = UTIF.decode(buff); //console.log(ifds); + var vsns = ifds, ma=0, page=vsns[0]; if(ifds[0].subIFD) vsns = vsns.concat(ifds[0].subIFD); + for(var i=0; ima) { ma=ar; page=img; } + } + UTIF.decodeImage(buff, page, ifds); + var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; + + var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; + var ctx = cnv.getContext("2d"); + var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); + ctx.putImageData(imgd,0,0); + return cnv.toDataURL(); } UTIF._binBE = { -nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, -readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, -readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, -readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, -readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, -readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, -writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, -writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, -writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, -writeDouble: function(buff, p, n) -{ - UTIF._binBE.fl64[0] = n; - for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; -} + nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, + readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, + readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, + writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, + writeDouble: function(buff, p, n) + { + UTIF._binBE.fl64[0] = n; + for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; + } } UTIF._binBE.ui8 = new Uint8Array (8); UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); @@ -1539,66 +1539,66 @@ UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); UTIF._binLE = { -nextZero : UTIF._binBE.nextZero, -readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, -readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, -readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, -readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, -readASCII : UTIF._binBE.readASCII, -readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, -readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, - -writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, -writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, -writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, -writeASCII : UTIF._binBE.writeASCII + nextZero : UTIF._binBE.nextZero, + readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, + readASCII : UTIF._binBE.readASCII, + readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, + readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, + + writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, + writeASCII : UTIF._binBE.writeASCII } UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) { -//log("copyTile", tw, th, w, h, xoff, yoff); -var xlim = Math.min(tw, w-xoff); -var ylim = Math.min(th, h-yoff); -for(var y=0; y>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); -var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; -w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; -h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; -cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; -d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; -if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); -d+=Y&15;while(w>>4; -if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); -n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; -while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; -H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; -H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; -return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); -(function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; -V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; -N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; -N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); -H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); -n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); - - + if(N[0]==3&&N[1]==0)return W?W:new R(0);var V=H.H,n=V.b,A=V.e,l=V.R,M=V.n,I=V.A,e=V.Z,b=V.m,Z=W==null; + if(Z)W=new R(N.length>>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); + var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; + w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; + h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; + cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; + d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; + if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); + d+=Y&15;while(w>>4; + if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); + n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; + while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; + H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; + H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; + return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); + (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; + V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; + N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; + N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); + H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); + n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); + + UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x Date: Tue, 10 Oct 2023 17:36:12 -0500 Subject: [PATCH 43/45] Addons: cleanup random git formatting --- examples/jsm/libs/opentype.module.js | 20 ++++++++++---------- examples/jsm/offscreen/offscreen.js | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/jsm/libs/opentype.module.js b/examples/jsm/libs/opentype.module.js index 36bfc7d72bc6d4..c8729119d8b190 100644 --- a/examples/jsm/libs/opentype.module.js +++ b/examples/jsm/libs/opentype.module.js @@ -1342,34 +1342,34 @@ sizeOf.UTF16 = function(v) { var eightBitMacEncodings = { 'x-mac-croatian': // Python: 'mac_croatian' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + - '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', 'x-mac-cyrillic': // Python: 'mac_cyrillic' 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + - 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + - 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', 'x-mac-greek': // Python: 'mac_greek' 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + - 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', 'x-mac-icelandic': // Python: 'mac_iceland' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + - 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', 'x-mac-ce': // Python: 'mac_latin2' 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + - 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', macintosh: // Python: 'mac_roman' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', 'x-mac-romanian': // Python: 'mac_romanian' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', 'x-mac-turkish': // Python: 'mac_turkish' 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' }; /** diff --git a/examples/jsm/offscreen/offscreen.js b/examples/jsm/offscreen/offscreen.js index 410c99bd9366f5..ba69f0a4d5d645 100644 --- a/examples/jsm/offscreen/offscreen.js +++ b/examples/jsm/offscreen/offscreen.js @@ -5,4 +5,4 @@ self.onmessage = function ( message ) { const data = message.data; init( data.drawingSurface, data.width, data.height, data.pixelRatio, data.path ); -}; \ No newline at end of file +}; From e0e172aa7d75272df1333619c260b4ea9c1c7863 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:13:04 -0500 Subject: [PATCH 44/45] lottie: typo --- examples/jsm/libs/lottie_canvas.module.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/libs/lottie_canvas.module.js b/examples/jsm/libs/lottie_canvas.module.js index 77726c1e1f0c4e..2dfcafa3102e80 100644 --- a/examples/jsm/libs/lottie_canvas.module.js +++ b/examples/jsm/libs/lottie_canvas.module.js @@ -1,4 +1,4 @@ -const lottie = /* @__PURE */ ( () => { +const lottie = /* @__PURE__ */ ( () => { const svgNS = 'http://www.w3.org/2000/svg'; From 17e52d11c01c74a1b6b1b20d8a2e504f675e5f46 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:15:25 -0500 Subject: [PATCH 45/45] OBB: typo --- examples/jsm/math/OBB.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/math/OBB.js b/examples/jsm/math/OBB.js index eb9d979453cf7c..76adbf02328f5f 100644 --- a/examples/jsm/math/OBB.js +++ b/examples/jsm/math/OBB.js @@ -11,7 +11,7 @@ import { const a = { c: null, // center - u: [ /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3(), /* @__PURE__ */ /* @__PURE__ */ new Vector3() ], // basis vectors + u: [ /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3() ], // basis vectors e: [] // half width };