public void ProcessResource(CombinatorResource resource, StringBuilder combinedContent, ICombinatorSettings settings) { if (resource.IsCdnResource && !settings.CombineCDNResources) { resource.IsOriginal = true; return; } var absoluteUrlString = resource.AbsoluteUrl.ToString(); _resourceFileService.LoadResourceContent(resource); _eventHandler.OnContentLoaded(resource); if (String.IsNullOrEmpty(resource.Content)) return; if (resource.Type == ResourceType.Style) { var stylesheet = new StylesheetParser().Parse(resource.Content); AdjustRelativePaths(resource, stylesheet); if (settings.EmbedCssImages && (settings.EmbedCssImagesStylesheetExcludeFilter == null || !settings.EmbedCssImagesStylesheetExcludeFilter.IsMatch(absoluteUrlString))) { EmbedImages(resource, stylesheet, settings.EmbeddedImagesMaxSizeKB); } resource.Content = stylesheet.ToString(); } if (settings.MinifyResources && (settings.MinificationExcludeFilter == null || !settings.MinificationExcludeFilter.IsMatch(absoluteUrlString))) { MinifyResourceContent(resource); if (String.IsNullOrEmpty(resource.Content)) return; } _eventHandler.OnContentProcessed(resource); combinedContent.Append(resource.Content); }
public void ReplaceCssImagesWithSprite(CombinatorResource resource) { Func<RuleSet, Term, bool> noSprite = (ruleSet, url) => { if (url.Value.Contains("no-sprite") || ruleSet.Selectors.Any(selector => selector.SimpleSelectors.Any(simpleSelector => simpleSelector.Class == "no-sprite"))) return true; // Images with a background position are not suitable for sprite generation if they aren't top-left if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-position" && (declaration.Expression.Terms.Count() != 2 || !declaration.Expression.Terms.Any(term => term.Value == "top") || !declaration.Expression.Terms.Any(term => term.Value == "left")))) return true; // Images with a background size are not suitable for sprite generation if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-size")) return true; // Images with a background repeat are not suitable for sprite generation if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-repeat" && (declaration.Expression.Terms.Count() != 1 || declaration.Expression.Terms.First().Value != "no-repeat"))) return true; var backgroundTerms = ruleSet.Declarations .Where(declaration => declaration.Name == "background") .SelectMany(declaration => declaration.Expression.Terms); // Sized backgrounds are not suitable either // LineHeightTerm is filled with a value when if in the shorthand background declaration there's the background size specified, e.g.: // background: url(url-to-img) 300px 400px / 500px 600px no-repeat; // Now there will be a term for 400px, having LineHeightTerm specified for 500px. if (backgroundTerms.Any(term => term.LineHeightTerm != null)) return true; var backgroundTermValues = backgroundTerms.Select(term => term.Value); // Positioned backgrounds are not suitable either, except top-left ones if (backgroundTerms .Any(term => term.Value == "center" || term.Value == "top" || term.Value == "right" || term.Value == "bottom" || term.Unit != null) && !(backgroundTermValues.Contains("top") && backgroundTermValues.Contains("left"))) return true; if (backgroundTermValues.Any(value => value == "repeat-x" || value == "repeat-y" || value == "repeat")) return true; return false; }; var images = new Dictionary<string, CssImage>(); var stylesheet = new StylesheetParser().Parse(resource.Content); ProcessImageUrls( resource, stylesheet, (ruleSet, urlTerm) => { var url = urlTerm.Value; if (noSprite(ruleSet, urlTerm)) return; var imageContent = _resourceFileService.GetImageContent(InlineUriFactory(resource, url), 5000); if (imageContent != null) { images[url] = new CssImage { Content = imageContent }; } }); if (images.Count == 0) return; _cacheFileService.WriteSpriteStream( resource.Content.GetHashCode() + ".jpg", (stream, publicUrl) => { using (var sprite = new Sprite(images.Values.Select(image => image.Content))) { var imageEnumerator = images.Values.GetEnumerator(); foreach (var backgroundImage in sprite.Generate(stream, ImageFormat.Jpeg)) { imageEnumerator.MoveNext(); imageEnumerator.Current.BackgroundImage = backgroundImage; imageEnumerator.Current.BackgroundImage.Url = InlineUriFactory(resource, publicUrl); } } }); ProcessImageUrls( resource, stylesheet, (ruleSet, urlTerm) => { var url = urlTerm.Value; if (images.ContainsKey(url)) { var backgroundImage = images[url].BackgroundImage; var imageDeclaration = new Declaration { Name = "background-image", Expression = new Expression { Terms = new List<Term> { new Term { Type = TermType.Url, Value = backgroundImage.Url.ToProtocolRelative() } } } }; ruleSet.Declarations.Add(imageDeclaration); var bgPosition = backgroundImage.Position; var positionDeclaration = new Declaration { Name = "background-position", Expression = new Expression { Terms = new List<Term> { new Term { Type = TermType.Number, Value = bgPosition.X.ToString(), Unit = Unit.Px }, new Term { Type = TermType.Number, Value = bgPosition.Y.ToString(), Unit = Unit.Px } } } }; ruleSet.Declarations.Add(positionDeclaration); } }); resource.Content = stylesheet.ToString(); }