text: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   * 
   *   http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  #ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESPONSIVE_IMAGE_FILTER_H_
  #define NET_INSTAWEB_REWRITER_PUBLIC_RESPONSIVE_IMAGE_FILTER_H_
  
  #include <map>
  #include <vector>
  
  #include "net/instaweb/rewriter/public/common_filter.h"
  #include "net/instaweb/rewriter/public/rewrite_driver.h"
  #include "pagespeed/kernel/base/basictypes.h"
  #include "pagespeed/kernel/base/string.h"
  #include "pagespeed/kernel/base/string_util.h"
  #include "pagespeed/kernel/html/html_element.h"
  #include "pagespeed/kernel/html/html_filter.h"
  
  namespace net_instaweb {
  
  struct ResponsiveImageCandidate {
    ResponsiveImageCandidate(HtmlElement* element_arg, double resolution_arg)
        : element(element_arg), resolution(resolution_arg) {}
    ResponsiveImageCandidate() : element(NULL), resolution(0) {}
  
    HtmlElement* element;
    double resolution;
  };
  typedef std::vector<ResponsiveImageCandidate> ResponsiveImageCandidateVector;
  
  // We insert several virtual <img> tags. This structure keeps track of all
  // the virtual <img> inserted for a single original <img>.
  struct ResponsiveVirtualImages {
    int width;
    int height;
    // One non-inlinable virtual <img> for every resolution supported (2x, 4x).
    // These will be used in the srcset (if inlinable_candidate is not actually
    // inlined).
    ResponsiveImageCandidateVector non_inlinable_candidates;
    // One inlinable virtual <img> for the highest resolution. If this is
    // inlined, we use that as the only version. Otherwise we discard it.
    ResponsiveImageCandidate inlinable_candidate;
    // One fullsized image. This will be used at the top end of the srcset in
    // case users zoom in far enough.
    ResponsiveImageCandidate fullsized_candidate;
  };
  typedef std::map<HtmlElement*, ResponsiveVirtualImages>
      ResponsiveImageCandidateMap;
  
  // Filter which converts <img> tags into responsive srcset= counterparts by
  // rewriting the images at multiple resolutions.
  // The filter actually runs twice. Once before rewrite_images to split an <img>
  // element into multiple elements (one for each resolution). The second time,
  // it runs after rewrite_images have been rendered and it combines the elements
  // back together into a single <img> element with srcset=.
  // It also adds JavaScript to polyfill and add zoom responsiveness to srcset.
  class ResponsiveImageFirstFilter : public CommonFilter {
   public:
    // Labels for different images used by Responsive image filters.
    static const char kOriginalImage[];
    static const char kNonInlinableVirtualImage[];
    static const char kInlinableVirtualImage[];
    static const char kFullsizedVirtualImage[];
  
    explicit ResponsiveImageFirstFilter(RewriteDriver* driver);
    ~ResponsiveImageFirstFilter() override;
  
    void StartElementImpl(HtmlElement* element) override {}
    void StartDocumentImpl() override;
    void EndElementImpl(HtmlElement* element) override;
  
    const char* Name() const override { return "ResponsiveImageFirst"; }
  
   private:
    void AddHiResImages(HtmlElement* element);
    ResponsiveImageCandidate AddHiResVersion(
        HtmlElement* img, const HtmlElement::Attribute& src_attr, int orig_width,
        int orig_height, StringPiece responsive_attribute_value,
        double resolution);
  
    friend class ResponsiveImageSecondFilter;
  
    std::vector<double> densities_;
    ResponsiveImageCandidateMap candidate_map_;
  
    DISALLOW_COPY_AND_ASSIGN(ResponsiveImageFirstFilter);
  };
  
  class ResponsiveImageSecondFilter : public CommonFilter {
   public:
    ResponsiveImageSecondFilter(RewriteDriver* driver,
                                const ResponsiveImageFirstFilter* first_filter);
    ~ResponsiveImageSecondFilter() override;
  
    void StartElementImpl(HtmlElement* element) override {}
    void StartDocumentImpl() override;
    void EndElementImpl(HtmlElement* element) override;
    void EndDocument() override;
  
    const char* Name() const override { return "ResponsiveImageSecond"; }
  
    // Injects scripts only when option responsive_images_zoom is enabled, and
    // the current document is not AMP.
    ScriptUsage GetScriptUsage() const override { return kMayInjectScripts; }
  
   private:
    void CombineHiResImages(HtmlElement* orig_element,
                            const ResponsiveVirtualImages& candidates);
    void Cleanup(HtmlElement* orig_element,
                 const ResponsiveVirtualImages& candidates);
    void InsertPlaceholderDebugComment(const ResponsiveImageCandidate& candidate,
                                       const char* qualifier);
  
    const GoogleString responsive_js_url_;
    const ResponsiveImageFirstFilter* first_filter_;
    // Is the ResponsiveImagesZoom filter enabled?
    bool zoom_filter_enabled_;
    // Was at least one srcset added? If not we don't insert zoom script.
    bool srcsets_added_;
  
    DISALLOW_COPY_AND_ASSIGN(ResponsiveImageSecondFilter);
  };
  
  }  // namespace net_instaweb
  
  #endif  // NET_INSTAWEB_REWRITER_PUBLIC_RESPONSIVE_IMAGE_FILTER_H_
  