define(function (require, exports, module) { "use strict"; var Immutable = require("immutable"); var unit = require("js/util/unit"), objUtil = require("js/util/object"), contentLib = require("adapter").lib.contentLayer; /** * Model for a bounds rectangle, we extract it from the layer descriptor * for the bounds without effects * * @constructor * @param {object} model */ var Radii = Immutable.Record({ /** * Radius of the top-left border * @type {number} */ topLeft: 0, /** * Radius of the top-right border * @type {number} */ topRight: 0, /** * Radius of the bottom-right border * @type {number} */ bottomRight: 0, /** * Radius of the bottom-left border * @type {number} */ bottomLeft: 0 }); Object.defineProperties(Radii.prototype, objUtil.cachedGetSpecs({ /** * Convert the set of border radii to a single scalar, or null of the radii * are disequal. * * @return {?number} */ "scalar": function () { if (this.topLeft === this.topRight && this.topRight === this.bottomRight && this.bottomRight === this.bottomLeft) { return this.topLeft; } else { return null; } } })); /** * Updates the radii object with new properties * * @param {object} descriptor Photoshop layer descriptor * @return {Radii} */ Radii.prototype.resetFromDescriptor = function (descriptor) { var newRadiiObject = Radii.fromLayerDescriptor(descriptor); return this.merge(newRadiiObject); }; /** * Construct a Radii object from the given Photoshop layer descriptor. * * @param {object} descriptor * @return {?Radii} */ Radii.fromLayerDescriptor = function (descriptor) { if (!descriptor.keyOriginType || descriptor.keyOriginType.length === 0) { return null; } // There is a shape, but it has been deformed and the radii are invalid var value = descriptor.keyOriginType[0]; if (value.keyShapeInvalidated) { return null; } var model = {}, type = value.keyOriginType; switch (type) { case contentLib.originTypes.ORIGIN_RECT: model.topLeft = 0; model.topRight = 0; model.bottomRight = 0; model.bottomLeft = 0; break; case contentLib.originTypes.ORIGIN_ROUNDED_RECT: var radii = value.keyOriginRRectRadii, resolution = objUtil.getPath(descriptor, "AGMStrokeStyleInfo.strokeStyleResolution"); if (resolution === undefined) { resolution = 300; } model.topLeft = unit.toPixels(radii.topLeft, resolution); model.topRight = unit.toPixels(radii.topRight, resolution); model.bottomRight = unit.toPixels(radii.bottomRight, resolution); model.bottomLeft = unit.toPixels(radii.bottomLeft, resolution); break; default: return null; } return new Radii(model); }; module.exports = Radii; });
define(function (require, exports, module) { "use strict"; var Immutable = require("immutable"); var objUtil = require("js/util/object"), Layer = require("./layer"); /** * Model document bounds or layer bounds without effects. * * @constructor * @param {object} model */ var Bounds = Immutable.Record({ /** * @type {number} */ top: null, /** * @type {number} */ bottom: null, /** * @type {number} */ left: null, /** * @type {number} */ right: null }); Object.defineProperties(Bounds.prototype, objUtil.cachedGetSpecs({ /** * Width of the bounding box. * @type {number} */ "width": function () { return this.right - this.left; }, /** * Height of the bounding box. * @type {number} */ "height": function () { return this.bottom - this.top; }, /** * Horizontal center of the bounding box. * @type {number} */ "xCenter": function () { return this.left + (this.width / 2); }, /** * Vertical center of the bounding box. * @type {number} */ "yCenter": function () { return this.top + (this.height / 2); }, /** * Area of the bounding box * @type {number} */ "area": function () { return this.width * this.height; }, /** * Whether the bounds are empty. * @type {boolean} */ "empty": function () { return this.area === 0; } })); /** * Create a new Bounds object from the given document descriptor. * * @param {object} descriptor Photoshop document descriptor * @return {Bounds} */ Bounds.fromDocumentDescriptor = function (descriptor) { var resolution = descriptor.resolution._value, multiplier = resolution / 72, model = {}; var height = descriptor.height._value * multiplier, width = descriptor.width._value * multiplier; model.top = 0; model.left = 0; model.bottom = height; model.right = width; return new Bounds(model); }; /** * Create a new Bounds object from the given layer descriptor. * * @param {object} descriptor Photoshop layer descriptor * @return {Bounds} */ Bounds.fromLayerDescriptor = function (descriptor) { var boundsObject = this.parseLayerDescriptor(descriptor); return new Bounds(boundsObject); }; /** * Parses given layer descriptor to a constructor usable object * * @param {object} descriptor * @param {number} descriptor.layerKind * @param {LayerKind} descriptor.layerKindName * @param {?boolean} descriptor.artboardEnabled If set, will parse artboard value * @param {?object} descriptor.artboard Contains the artboard bounds descriptor * @param {?object} descriptor.pathBounds If available, will be parsed as shape layer * @param {?object} descriptor.boundsNoEffects Bounds object available for all layers * @param {?object} descriptor.boundsNoMask Bounds object available for all layers * * @return {?{top: number, left: number, bottom: number, right: number}} */ Bounds.parseLayerDescriptor = function (descriptor) { var boundsObject, layerKind = descriptor.layerKindName || Layer.KIND_TO_NAME[descriptor.layerKind]; // artboards are also groups. so we handle them separately if (descriptor.artboardEnabled) { boundsObject = objUtil.getPath(descriptor, "artboard.artboardRect"); } else if (layerKind === Layer.KINDS.VECTOR && descriptor.hasOwnProperty("pathBounds")) { boundsObject = objUtil.getPath(descriptor, "pathBounds.pathBounds"); } else { switch (layerKind) { // Photoshop's group / adjustment bounds are not useful, so ignore them. case Layer.KINDS.GROUP: if (descriptor.vectorMaskEnabled) { boundsObject = descriptor.bounds; } else { return null; } break; case Layer.KINDS.GROUPEND: case Layer.KINDS.ADJUSTMENT: return null; case Layer.KINDS.TEXT: boundsObject = descriptor.boundingBox; break; case Layer.KINDS.VECTOR: boundsObject = descriptor.boundsNoEffects; break; default: boundsObject = descriptor.boundsNoMask; break; } var model = {}; model.top = boundsObject.top._value; model.left = boundsObject.left._value; model.bottom = boundsObject.bottom._value; model.right = boundsObject.right._value; boundsObject = model; } delete boundsObject._obj; return boundsObject; }; /** * Updates the bound object with new properties * * @param {object} descriptor Photoshop layer descriptor * @return {Bounds} [description] */ Bounds.prototype.resetFromDescriptor = function (descriptor) { var newBoundObject = Bounds.parseLayerDescriptor(descriptor); return this.merge(newBoundObject); }; /** * Create a new bounds object from the union of the given bounds objects. * Returns null if no bounds objects are supplied. * * @param {Array.<Bounds>} childBounds * @return {?Bounds} */ Bounds.union = function (childBounds) { if (childBounds.isEmpty()) { return null; } var startBounds = childBounds.first(), nextBounds = startBounds.withMutations(function (model) { childBounds.rest().forEach(function (child) { model.top = Math.min(model.top, child.top); model.left = Math.min(model.left, child.left); model.bottom = Math.max(model.bottom, child.bottom); model.right = Math.max(model.right, child.right); }); }); return nextBounds; }; /** * Creates a new bounds object from the intersection of the given bounds objects. * Returns null if they don't intersect * * @param {Bounds} boundsOne * @param {Bounds} boundsTwo * @return {?Bounds} Intersection of boundsOne and boundsTwo */ Bounds.intersection = function (boundsOne, boundsTwo) { if (!boundsOne || !boundsTwo) { return null; } var model = { top: boundsTwo.top < boundsOne.top ? boundsOne.top : boundsTwo.top, left: boundsTwo.left < boundsOne.left ? boundsOne.left : boundsTwo.left, bottom: boundsTwo.bottom < boundsOne.bottom ? boundsTwo.bottom : boundsOne.bottom, right: boundsTwo.right < boundsOne.right ? boundsTwo.right : boundsOne.right }; if (model.bottom < model.top || model.right < model.left) { return null; } return new Bounds(model); }; /** * Indicates whether the given point is contained in the bounding box. * * @param {number} x * @param {number} y * @return {boolean} */ Bounds.prototype.contains = function (x, y) { return this.top <= y && y <= this.bottom && this.left <= x && x <= this.right; }; /** * If the width or height are negative, will swap the adjacent edges * * @return {Bounds} Updated bounds object */ Bounds.prototype.normalize = function () { var newBounds = {}; if (this.right < this.left) { newBounds.left = this.right; newBounds.right = this.left; } if (this.bottom < this.top) { newBounds.top = this.bottom; newBounds.bottom = this.top; } return this.merge(newBounds); }; /** * Clones this bounds object with an updated position. * * @protected * @param {number=} x New X position * @param {number=} y New Y position * @return {Bounds} The updated bounds object */ Bounds.prototype.updatePosition = function (x, y) { var width = this.width, height = this.height, newBounds = {}; if (typeof x === "number") { newBounds.left = x; newBounds.right = x + width; } if (typeof y === "number") { newBounds.top = y; newBounds.bottom = y + height; } return this.merge(newBounds); }; /** * Clones this bounds object with an updated size. * * @protected * @param {number=} w New width * @param {number=} h New height * @param {boolean=} proportional size change * @return {Bounds} The updated bounds object */ Bounds.prototype.updateSize = function (w, h, proportional) { var newBounds = {}; if (typeof w === "number") { var oldWidth = this.width; newBounds.right = this.left + w; if (proportional) { var newHeight = this.height / oldWidth * w; newBounds.bottom = this.top + newHeight; } } if (typeof h === "number") { var oldHeight = this.height; newBounds.bottom = this.top + h; if (proportional) { var newWidth = this.width / oldHeight * h; newBounds.right = this.left + newWidth; } } return this.merge(newBounds); }; /** * Clones this bounds object with an updated position and size. * * @protected * @param {number=} x New X position * @param {number=} y New Y position * @param {number=} width New width * @param {number=} height New height * @return {Bounds} The updated bounds object */ Bounds.prototype.updateSizeAndPosition = function (x, y, width, height) { var model = {}; if (typeof x === "number") { model.left = x; if (typeof width === "number") { model.right = x + width; } } if (typeof y === "number") { model.top = y; if (typeof height === "number") { model.bottom = y + height; } } return this.merge(model); }; /** * Checks to see if these bounds intersects with other bounds * * @param {Bounds} otherBounds * @return {boolean} */ Bounds.prototype.intersects = function (otherBounds) { return this.left < otherBounds.right && this.right > otherBounds.left && this.top < otherBounds.bottom && this.bottom > otherBounds.top; }; /** * Clones this bounds object returning it relative to the position * of the given bounds' top left value * * @param {Bounds} otherBounds Comparison bounds * @return {Bounds} Updated bounds where location is in relation to otherBounds */ Bounds.prototype.relativeTo = function (otherBounds) { var x = otherBounds.left, y = otherBounds.top, model = { left: this.left - x, top: this.top - y, right: this.right - x, bottom: this.bottom - y }; return this.merge(model); }; module.exports = Bounds; });
define(function (require, exports, module) { "use strict"; var Immutable = require("immutable"), mathjs = require("mathjs"), tinycolor = require("tinycolor2"); var objUtil = require("js/util/object"), mathUtil = require("js/util/math"); /** * @constructor * @param {object} model */ var Color = Immutable.Record({ /** * [0, 255] * @type {number} */ r: 0, /** * [0, 255] * @type {number} */ g: 0, /** * [0, 255] * @type {number} */ b: 0, /** * [0, 1] * @type {number} */ a: 1 }); /** * @type {Color} */ Color.DEFAULT = new Color(); /** * Test that a color object supplied by Ps is a valid RGB color object * * @param {object} obj * @return {boolean} true if the supplied object is of type RGBColor */ Color.isValidPhotoshopColorObj = function (obj) { return obj && (typeof obj === "object") && (obj._obj === "RGBColor"); }; /** * Construct a new Color object from a Photoshop color descriptor and * opacity percentage. * * @param {object} rgb Photoshop color descriptor * @param {number} opacityPercentage In [0, 100] * @return {Color} */ Color.fromPhotoshopColorObj = function (rgb, opacityPercentage) { var green; if (rgb.hasOwnProperty("grain")) { green = rgb.grain; } else if (rgb.hasOwnProperty("green")) { green = rgb.green; } else { throw new Error("Unable to parse Photoshop color object"); } return new Color({ "r": mathjs.round(rgb.red), "g": mathjs.round(green), "b": mathjs.round(rgb.blue), "a": mathjs.round(opacityPercentage / 100, 4) }); }; /** * Construct a new Color object from a TinyColor instance. * * @param {TinyColor} tiny * @return {Color} */ Color.fromTinycolor = function (tiny) { return new Color(tiny.toRgb()); }; Object.defineProperties(Color.prototype, objUtil.cachedGetSpecs({ /** * The opacity percentage of this color object * In [0, 100] * @type {number} */ "opacity": function () { return mathjs.round(this.a * 100, 0); } })); /** * Set the alpha value of this color, but not its RGB value. * * @param {number|Color} alpha Number in [0, 1], or a Color object * @return {Color} */ Color.prototype.setAlpha = function (alpha) { if (alpha instanceof Color) { alpha = alpha.a; } return this.set("a", alpha); }; /** * Set the opaque value of this color, but not its alpha value. * * @param {Color} color * @return {Color} */ Color.prototype.setOpaque = function (color) { return this.merge({ r: color.r, g: color.g, b: color.b }); }; /** * Normalize the alpha value of this color w.r.t. 255. * * @return {Color} */ Color.prototype.normalizeAlpha = function () { return this.set("a", mathUtil.normalize(this.a, 255)); }; /** * Set the opacity value of this color. * * @param {number} opacityPercentage In [0, 100] * @return {Color} */ Color.prototype.setOpacity = function (opacityPercentage) { return this.setAlpha(mathjs.round(opacityPercentage / 100, 4)); }; /** * Get an opaque version of this color. * * @return {Color} */ Color.prototype.opaque = function () { return this.delete("a"); }; /** * Return instance of TinyColor. Provide convenicen for accessing TinyColor functions * @return {TinyColor} */ Color.prototype.toTinyColor = function () { return tinycolor(this.toJS()); }; module.exports = Color; });