예제 #1
0
 /// <summary>The add to analysis log.</summary>
 /// <param name="imageAssemblyAnalysisLog">The image assembly analysis log.</param>
 /// <param name="parentAstNode">The parent ast node.</param>
 /// <param name="image">The image.</param>
 /// <param name="failureReason">The failure reason.</param>
 internal static void SafeAdd(this ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, AstNode parentAstNode, string image = null, FailureReason?failureReason = null)
 {
     if (imageAssemblyAnalysisLog != null)
     {
         imageAssemblyAnalysisLog.Add(new ImageAssemblyAnalysis {
             AstNode = parentAstNode, Image = image, FailureReason = failureReason
         });
     }
 }
        /// <summary>Assembles the background images</summary>
        /// <param name="stylesheetNode">the style sheet node</param>
        /// <param name="dpi">The dpi.</param>
        /// <param name="threadContext">The thread Context.</param>
        /// <param name="spritedImageContentItems">The sprited Image Content Items.</param>
        /// <returns>The stylesheet node with the sprited images aplied.</returns>
        private AstNode SpriteBackgroundImages(AstNode stylesheetNode, float dpi, IWebGreaseContext threadContext, BlockingCollection <ContentItem> spritedImageContentItems)
        {
            // The image assembly is a 3 step process:
            // 1. Scan the Css followed by Pretty Print
            // 2. Run the image assembly tool
            // 3. Update the Css with generated images followed by Pretty Print
            return(threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Spriting).Execute(() =>
            {
                // Execute the pipeline for image assembly scan
                var scanLog = this.ExecuteImageAssemblyScan(stylesheetNode, threadContext);

                // Execute the pipeline for image assembly tool
                var imageMaps = new List <ImageLog>();
                var count = 0;
                foreach (var scanOutput in scanLog.ImageAssemblyScanOutputs)
                {
                    var spriteResult = this.SpriteImageFromLog(scanOutput, this.ImageAssembleScanDestinationFile + (count == 0 ? string.Empty : "." + count) + ".xml", scanLog.ImageAssemblyAnalysisLog, threadContext, spritedImageContentItems);
                    if (spriteResult != null)
                    {
                        imageMaps.Add(spriteResult);
                        count++;
                    }
                }

                // Step # 8 - Execute the pipeline for image assembly update
                stylesheetNode = this.ExecuteImageAssemblyUpdate(stylesheetNode, imageMaps, dpi);

                if (!string.IsNullOrWhiteSpace(this.ImageSpritingLogPath))
                {
                    scanLog.ImageAssemblyAnalysisLog.Save(this.ImageSpritingLogPath);
                }

                var imageAssemblyAnalysisLog = scanLog.ImageAssemblyAnalysisLog;
                if (this.ErrorOnInvalidSprite && imageAssemblyAnalysisLog.FailedSprites.Any())
                {
                    foreach (var failedSprite in imageAssemblyAnalysisLog.FailedSprites)
                    {
                        var failureMessage = ImageAssemblyAnalysisLog.GetFailureMessage(failedSprite);
                        if (!string.IsNullOrWhiteSpace(failedSprite.Image))
                        {
                            threadContext.Log.Error(
                                "Failed to sprite image {0}\r\nReason:{1}\r\nCss:{2}".InvariantFormat(
                                    failedSprite.Image, failureMessage, failedSprite.AstNode.PrettyPrint()));
                        }
                        else
                        {
                            threadContext.Log.Error(
                                "Failed to sprite:{0}\r\nReason:{1}".InvariantFormat(
                                    failedSprite.Image, failureMessage));
                        }
                    }
                }

                return stylesheetNode;
            }));
        }
        /// <summary>The create sprited images.</summary>
        /// <param name="mapXmlFile">The map xml file.</param>
        /// <param name="imageAssemblyAnalysisLog">The image assembly analysis log.</param>
        /// <param name="imageReferencesToAssemble">The image references to assemble.</param>
        /// <param name="cacheSection">The cache section.</param>
        /// <param name="results">The results.</param>
        /// <param name="threadContext">The thread Context.</param>
        /// <returns>The <see cref="bool"/>.</returns>
        private ImageLog CreateSpritedImages(
            string mapXmlFile,
            ImageAssemblyAnalysisLog imageAssemblyAnalysisLog,
            IEnumerable <InputImage> imageReferencesToAssemble,
            ICacheSection cacheSection,
            BlockingCollection <ContentItem> results,
            IWebGreaseContext threadContext)
        {
            if (!Directory.Exists(this.ImagesOutputDirectory))
            {
                Directory.CreateDirectory(this.ImagesOutputDirectory);
            }

            var imageMap = ImageAssembleGenerator.AssembleImages(
                imageReferencesToAssemble.ToSafeReadOnlyCollection(),
                SpritePackingType.Vertical,
                this.ImagesOutputDirectory,
                string.Empty,
                true,
                threadContext,
                this.ImageAssemblyPadding,
                imageAssemblyAnalysisLog,
                this.ForcedSpritingImageType);

            if (imageMap == null || imageMap.Document == null)
            {
                return(null);
            }

            var destinationDirectory = threadContext.Configuration.DestinationDirectory;

            if (!string.IsNullOrWhiteSpace(this.ImageAssembleScanDestinationFile))
            {
                var scanXml = imageMap.Document.ToString();
                FileHelper.WriteFile(mapXmlFile, scanXml);
                cacheSection.AddResult(ContentItem.FromFile(mapXmlFile), CacheFileCategories.SpriteLogFileXml);
            }

            var imageLog = new ImageLog(imageMap.Document);

            cacheSection.AddResult(ContentItem.FromContent(imageLog.ToJson(true)), CacheFileCategories.SpriteLogFile);

            foreach (var spritedFile in imageLog.InputImages.Select(il => il.OutputFilePath).Distinct())
            {
                var spritedImageContentItem = ContentItem.FromFile(spritedFile, spritedFile.MakeRelativeToDirectory(destinationDirectory));
                results.Add(spritedImageContentItem);
                cacheSection.AddResult(spritedImageContentItem, CacheFileCategories.HashedSpriteImage);
            }

            return(imageLog);
        }
        /// <summary>The sprite image.</summary>
        /// <param name="scanOutput">The scan Output.</param>
        /// <param name="mapXmlFile">The map xml file</param>
        /// <param name="imageAssemblyAnalysisLog">The image Assembly Analysis Log.</param>
        /// <param name="threadContext">The thread Context.</param>
        /// <param name="spritedImageContentItems">The sprited Image Content Items.</param>
        /// <returns>The <see cref="ImageLog"/>.</returns>
        private ImageLog SpriteImageFromLog(ImageAssemblyScanOutput scanOutput, string mapXmlFile, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, IWebGreaseContext threadContext, BlockingCollection <ContentItem> spritedImageContentItems)
        {
            if (scanOutput == null || !scanOutput.ImageReferencesToAssemble.Any())
            {
                return(null);
            }

            ImageLog imageLog = null;
            var      imageReferencesToAssemble = scanOutput.ImageReferencesToAssemble;

            if (imageReferencesToAssemble == null || imageReferencesToAssemble.Count == 0)
            {
                return(null);
            }

            var varBySettings = new { imageMap = GetRelativeSpriteCacheKey(imageReferencesToAssemble, threadContext), this.ImageAssemblyPadding };
            var success       = threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Spriting, SectionIdParts.Assembly)
                                .MakeCachable(varBySettings, false, true)
                                .RestoreFromCacheAction(cacheSection =>
            {
                imageLog = RestoreSpritedImagesFromCache(mapXmlFile, cacheSection, spritedImageContentItems, threadContext.Configuration.DestinationDirectory, this.ImageAssembleScanDestinationFile);
                return(imageLog != null);
            })
                                .Execute(cacheSection =>
            {
                imageLog = this.CreateSpritedImages(mapXmlFile, imageAssemblyAnalysisLog, imageReferencesToAssemble, cacheSection, spritedImageContentItems, threadContext);
                return(imageLog != null);
            });

            return
                (success
                ? imageLog
                : null);
        }
예제 #5
0
        /// <summary>Verify that url has some value</summary>
        /// <param name="parent">The parent AST node</param>
        /// <param name="imageReferencesToIgnore">The image reference to ignore</param>
        /// <param name="imageAssemblyAnalysisLog">The logging object</param>
        /// <param name="shouldIgnore">The result of scan if we should ignore the image reference</param>
        /// <returns>True if px units are used</returns>
        internal bool VerifyBackgroundUrl(AstNode parent, HashSet <string> imageReferencesToIgnore, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, out bool shouldIgnore)
        {
            shouldIgnore = false;
            if (string.IsNullOrWhiteSpace(this.Url))
            {
                imageAssemblyAnalysisLog.SafeAdd(parent, this.Url, FailureReason.NoUrl);
                return(false);
            }

            if (imageReferencesToIgnore != null)
            {
                var url          = this.Url;
                var fullImageUrl = url.NormalizeUrl();

                if (imageReferencesToIgnore.Contains(fullImageUrl))
                {
                    // Log diagnostics
                    imageAssemblyAnalysisLog.SafeAdd(parent, this.Url, FailureReason.IgnoreUrl);
                    shouldIgnore = true;
                    return(false);
                }
            }

            return(true);
        }
예제 #6
0
        internal static bool TryGetBackgroundDeclaration(this IEnumerable <DeclarationNode> declarationAstNodes, AstNode parentAstNode, out Background backgroundNode, out BackgroundImage backgroundImageNode, out BackgroundPosition backgroundPositionNode, out DeclarationNode backgroundSize, List <string> imageReferencesInInvalidDeclarations, HashSet <string> imageReferencesToIgnore, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, string outputUnit, double outputUnitFactor, bool ignoreImagesWithNonDefaultBackgroundSize = false)
        {
            // Initialize the nodes to null
            backgroundNode         = null;
            backgroundImageNode    = null;
            backgroundPositionNode = null;
            backgroundSize         = null;

            // With CSS3 multiple urls can be present in a single rule, this is not yet supported
            // background: url(flower.png), url(ball.png), url(grass.png) no-repeat;
            if (BackgroundImage.HasMultipleUrls(parentAstNode.MinifyPrint()))
            {
                imageAssemblyAnalysisLog.SafeAdd(parentAstNode, null, FailureReason.MultipleUrls);
                return(false);
            }

            // This will check if the selector hash -wg-spriting: ignore. If it does we will ignore the sprite.
            var webGreaseSpritingProperty = declarationAstNodes.FirstOrDefault(d => d.Property == "-wg-spriting");

            if (webGreaseSpritingProperty != null && webGreaseSpritingProperty.ExprNode.TermNode.StringBasedValue == "ignore")
            {
                imageAssemblyAnalysisLog.SafeAdd(parentAstNode, null, FailureReason.SpritingIgnore);
                return(false);
            }

            // The list of declarations should not have the duplicate declaration
            // properties. Validate and get the dictionary of declarations.
            var declarationProperties = declarationAstNodes.LoadDeclarationPropertiesDictionary();

            // The selector design is inefficient if the shorthand notation and long name is defined
            // in a scope of ruleset declaration, media ruleset declaration or page declaration. This
            // would be a great feature to add to Optimization visitor but would require a full table
            // scanning which is not yet implemented.
            // Per MSN CSS standards, the practice of defining the shorthand notation and long name
            // in scope of same selector is not allowed.
            DeclarationNode declarationAstNode;

            if (declarationProperties.TryGetValue(ImageAssembleConstants.Background, out declarationAstNode))
            {
                // There should not be any short and long notation simultaneosuly used in these set of declarations.
                // For example: Such a list of declarations end up in inefficient CSS.
                // #selector
                // {
                // background:url(foo.gif);
                // background-image:url(../../i/D5/DF5D9B4EFD5CFF9122942A67A1EEC5.gif);
                // background-position:500px 500px;
                // background-repeat:no-repeat
                // }
                // By design, we are not computing cascade here.
                // TODO: RTUIT: Add support to override some of these with extra values depending on the order, or use optimization to do this when merging styles.
                if (declarationProperties.ContainsKey(ImageAssembleConstants.BackgroundRepeat) ||
                    declarationProperties.ContainsKey(ImageAssembleConstants.BackgroundImage) ||
                    declarationProperties.ContainsKey(ImageAssembleConstants.BackgroundPosition))
                {
                    throw new ImageAssembleException(CssStrings.DuplicateBackgroundFormatError);
                }

                // Load the model for the "background" declaration
                var parsedBackground = new Background(declarationAstNode, outputUnit, outputUnitFactor);

                ////
                //// The url should be present
                ////
                bool shouldIgnore;
                if (!parsedBackground.BackgroundImage.VerifyBackgroundUrl(parentAstNode, imageReferencesToIgnore, imageAssemblyAnalysisLog, out shouldIgnore) ||
                    shouldIgnore)
                {
                    return(false);
                }

                ////
                //// The "no-repeat" term should be explicitly configured on the "background" declaration
                ////
                if (!parsedBackground.BackgroundRepeat.VerifyBackgroundNoRepeat())
                {
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url, FailureReason.BackgroundRepeatInvalid);
                    UpdateFailedUrlsList(parsedBackground.Url, imageReferencesInInvalidDeclarations);
                    return(false);
                }

                ////
                //// The background position should only be empty, x = any value and y = 0, top or px
                ////
                if (!parsedBackground.BackgroundPosition.IsVerticalSpriteCandidate())
                {
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url, FailureReason.IncorrectPosition);
                    UpdateFailedUrlsList(parsedBackground.Url, imageReferencesInInvalidDeclarations);
                    return(false);
                }

                //// Try to get the background size, returns false if we want to ignore images with background sizes and the background size is set to a non-default value.
                if (!TryGetBackgroundSize(ignoreImagesWithNonDefaultBackgroundSize, declarationProperties, out backgroundSize))
                {
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url, FailureReason.BackgroundSizeIsSetToNonDefaultValue);
                    UpdateFailedUrlsList(parsedBackground.Url, imageReferencesInInvalidDeclarations);
                    return(false);
                }

                backgroundNode = parsedBackground;
                imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url);

                //// SUCCESS - This is the candidate!
                return(true);
            }

            // Now there should be declaration for "background-image" or "background-repeat" or both
            if (declarationProperties.TryGetValue(ImageAssembleConstants.BackgroundImage, out declarationAstNode))
            {
                // Load the property model for the "background-image" declaration
                var parsedBackgroundImage = new BackgroundImage(declarationAstNode);

                ////
                //// The url should be present
                ////
                bool shouldIgnore;
                if (!parsedBackgroundImage.VerifyBackgroundUrl(parentAstNode, imageReferencesToIgnore, imageAssemblyAnalysisLog, out shouldIgnore) ||
                    shouldIgnore)
                {
                    return(false);
                }

                ////
                //// There is a "background-repeat" declaration found
                ////
                DeclarationNode backgroundRepeat;
                if (!declarationProperties.TryGetValue(ImageAssembleConstants.BackgroundRepeat, out backgroundRepeat))
                {
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.NoRepeat);
                    UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations);
                    return(false);
                }

                ////
                //// Now make sure that "background-repeat" is "no-repeat"
                ////
                if (!new BackgroundRepeat(backgroundRepeat).VerifyBackgroundNoRepeat())
                {
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.BackgroundRepeatInvalid);
                    UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations);
                    return(false);
                }

                //// Try to get the background size, returns false if we want to ignore images with background sizes and the background size is set to a non-default value.
                if (!TryGetBackgroundSize(ignoreImagesWithNonDefaultBackgroundSize, declarationProperties, out backgroundSize))
                {
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.BackgroundSizeIsSetToNonDefaultValue);
                    UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations);
                    return(false);
                }

                ////
                //// The background position should only be empty, x = any value and y = 0, top or px
                ////
                DeclarationNode backgroundPosition;
                if (declarationProperties.TryGetValue(ImageAssembleConstants.BackgroundPosition, out backgroundPosition))
                {
                    // Now if there is a "background-position" declaration (optional), lets make
                    // it should only be empty, px or left/top or right/px
                    var parsedBackgroundPosition = new BackgroundPosition(backgroundPosition, outputUnit, outputUnitFactor);

                    if (!parsedBackgroundPosition.IsVerticalSpriteCandidate())
                    {
                        imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.IncorrectPosition);
                        UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations);
                        return(false);
                    }

                    backgroundImageNode    = parsedBackgroundImage;
                    backgroundPositionNode = parsedBackgroundPosition;
                    imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url);

                    //// SUCCESS - This is the candidate!
                    return(true);
                }

                backgroundImageNode = parsedBackgroundImage;
                imageAssemblyAnalysisLog.SafeAdd(parentAstNode, backgroundImageNode.Url);

                //// SUCCESS - This is the candidate!
                return(true);
            }

            return(false);
        }
        internal static ImageMap AssembleImages(ReadOnlyCollection <InputImage> inputImages, SpritePackingType packingType, string assembleFileFolder, string mapFileName, string pngOptimizerToolCommand, bool dedup, IWebGreaseContext context, int?imagePadding = null, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog = null, ImageType?forcedImageType = null)
        {
            // deduping is optional.  CssPipelineTask already has deduping built into it, so it skips deduping in ImageAssembleTool.
            var inputImagesDeduped = dedup ? DedupImages(inputImages, context) : inputImages;

            var xmlMap = new ImageMap(mapFileName);

            Safe.LockFiles(
                inputImages.Select(ii => new FileInfo(ii.AbsoluteImagePath)).ToArray(),
                () =>
            {
                var separatedLists = SeparateByImageType(inputImagesDeduped, forcedImageType);
#if DEBUG
                foreach (ImageType imageType in Enum.GetValues(typeof(ImageType)))
                {
                    Console.WriteLine();
                    Console.WriteLine(Enum.GetName(typeof(ImageType), imageType));
                    foreach (var entry in separatedLists[imageType])
                    {
                        Console.WriteLine(entry.InputImage.OriginalImagePath);
                    }
                }
#endif
                var padding = imagePadding ?? DefaultPadding;
                var registeredAssemblers             = RegisterAvailableAssemblers(context);
                List <BitmapContainer> separatedList = null;
                foreach (var registeredAssembler in registeredAssemblers)
                {
                    var assembled = false;
                    try
                    {
                        // Set Image orientation as passed
                        registeredAssembler.PackingType = packingType;

                        // Set Xml Image Xml Map
                        registeredAssembler.ImageXmlMap = xmlMap;

                        xmlMap.AppendPadding(padding.ToString(CultureInfo.InvariantCulture));
                        registeredAssembler.PaddingBetweenImages = padding;

                        // Set PNG Optimizer tool path for PngAssemble
                        registeredAssembler.OptimizerToolCommand = pngOptimizerToolCommand;

                        // Assemble images of this type
                        separatedList = separatedLists[registeredAssembler.Type];

                        if (separatedList.Any())
                        {
                            // Set Assembled Image Name
                            registeredAssembler.AssembleFileName = GenerateAssembleFileName(separatedList.Select(s => s.InputImage), assembleFileFolder)
                                                                   + registeredAssembler.DefaultExtension;

                            assembled = registeredAssembler.Assemble(separatedList);
                        }
                    }
                    finally
                    {
                        if (assembled)
                        {
                            foreach (var entry in separatedList)
                            {
                                if (entry.Bitmap != null)
                                {
                                    if (imageAssemblyAnalysisLog != null)
                                    {
                                        imageAssemblyAnalysisLog.UpdateSpritedImage(registeredAssembler.Type, entry.InputImage.OriginalImagePath, registeredAssembler.AssembleFileName);
                                    }

                                    context.Cache.CurrentCacheSection.AddSourceDependency(entry.InputImage.AbsoluteImagePath);
                                    entry.Bitmap.Dispose();
                                }
                            }
                        }
                    }
                }

                var notSupportedList = separatedLists[ImageType.NotSupported];
                if (notSupportedList != null && notSupportedList.Count > 0)
                {
                    var message = new StringBuilder("The following files were not assembled because their formats are not supported:");
                    foreach (var entry in notSupportedList)
                    {
                        message.Append(" " + entry.InputImage.OriginalImagePath);
                    }

#if DEBUG
                    Console.WriteLine(message.ToString());
#endif
                    throw new ImageAssembleException(message.ToString());
                }
            });

            return(xmlMap);
        }
 /// <summary>Invokes Assemble method of appropriate Image Assembler depending upon
 /// the type of images to be Assembled.</summary>
 /// <param name="inputImages">List of InputImage</param>
 /// <param name="packingType">Image Packing Type(Horizontal/Vertical)</param>
 /// <param name="assembleFileFolder">folder path where the assembled file will be created.</param>
 /// <param name="pngOptimizerToolCommand">PNG Optimizer tool command</param>
 /// <param name="dedup">Remove duplicate images</param>
 /// <param name="context">The webgrease context</param>
 /// <param name="imagePadding">The image padding</param>
 /// <param name="imageAssemblyAnalysisLog">The image Assembly Analysis Log.</param>
 /// <param name="forcedImageType">The forced image type to override detection.</param>
 /// <returns>The <see cref="ImageMap"/>.</returns>
 internal static ImageMap AssembleImages(ReadOnlyCollection <InputImage> inputImages, SpritePackingType packingType, string assembleFileFolder, string pngOptimizerToolCommand, bool dedup, IWebGreaseContext context, int?imagePadding = null, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog = null, ImageType?forcedImageType = null)
 {
     return(AssembleImages(inputImages, packingType, assembleFileFolder, null, pngOptimizerToolCommand, dedup, context, imagePadding, imageAssemblyAnalysisLog, forcedImageType));
 }