/*
 * Copyright 2019 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkottieTextShaper_DEFINED
#define SkottieTextShaper_DEFINED

#include "include/core/SkFont.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/private/base/SkTypeTraits.h"
#include "include/utils/SkTextUtils.h"

#include <vector>

class SkCanvas;
class SkFontMgr;
class SkTypeface;
class SkString;

struct SkRect;

namespace skottie {

// Helper implementing After Effects text shaping semantics on top of SkShaper.

class Shaper final {
public:
    struct RunRec {
        SkFont fFont;
        size_t fSize;

        static_assert(::sk_is_trivially_relocatable<decltype(fFont)>::value);

        using sk_is_trivially_relocatable = std::true_type;
    };

    struct ShapedGlyphs {
        std::vector<RunRec>    fRuns;

        // Consolidated storage for all runs.
        std::vector<SkGlyphID> fGlyphIDs;
        std::vector<SkPoint>   fGlyphPos;

        // fClusters[i] is an input string index, pointing to the start of the UTF sequence
        // associated with fGlyphs[i].  The number of entries matches the number of glyphs.
        // Only available with Flags::kClusters.
        std::vector<size_t>    fClusters;

        enum class BoundsType { kConservative, kTight };
        SkRect computeBounds(BoundsType) const;

        void draw(SkCanvas*, const SkPoint& origin, const SkPaint&) const;
    };

    struct Fragment {
        ShapedGlyphs fGlyphs;
        SkPoint      fOrigin;

        // Only valid for kFragmentGlyphs
        float        fAdvance,
                     fAscent;
        uint32_t     fLineIndex;    // 0-based index for the line this fragment belongs to.
        bool         fIsWhitespace; // True if the first code point in the corresponding
                                    // cluster is whitespace.
    };

    struct Result {
        std::vector<Fragment> fFragments;
        size_t                fMissingGlyphCount = 0;
        // Relative text size scale, when using an auto-scaling ResizePolicy
        // (otherwise 1.0).  This is informative of the final text size, and is
        // not required to render the Result.
        float                 fScale = 1.0f;

        SkRect computeVisualBounds() const;
    };

    enum class VAlign : uint8_t {
        // Align the first line typographical top with the text box top (AE box text).
        kTop,
        // Align the first line typographical baseline with the text box top (AE point text).
        kTopBaseline,

        // Skottie vertical alignment extensions

        // These are based on a hybrid extent box defined (in Y) as
        //
        //   ------------------------------------------------------
        //   MIN(visual_top_extent   , typographical_top_extent   )
        //
        //                         ...
        //
        //   MAX(visual_bottom_extent, typographical_bottom_extent)
        //   ------------------------------------------------------
        kHybridTop,     // extent box top    -> text box top
        kHybridCenter,  // extent box center -> text box center
        kHybridBottom,  // extent box bottom -> text box bottom

        // Visual alignement modes -- these are using tight visual bounds for the paragraph.
        kVisualTop,     // visual top    -> text box top
        kVisualCenter,  // visual center -> text box center
        kVisualBottom,  // visual bottom -> text box bottom
    };

    enum class ResizePolicy : uint8_t {
        // Use the specified text size.
        kNone,
        // Resize the text such that the extent box fits (snuggly) in the text box,
        // both horizontally and vertically.
        kScaleToFit,
        // Same kScaleToFit if the text doesn't fit at the specified font size.
        // Otherwise, same as kNone.
        kDownscaleToFit,
    };

    enum class LinebreakPolicy : uint8_t {
        // Break lines such that they fit in a non-empty paragraph box, horizontally.
        kParagraph,
        // Only break lines when requested explicitly (\r), regardless of paragraph box dimensions.
        kExplicit,
    };

    // Initial text direction.
    enum class Direction : uint8_t { kLTR, kRTL };

    enum class Capitalization {
        kNone,
        kUpperCase,
    };

    enum Flags : uint32_t {
        kNone                       = 0x00,

        // Split out individual glyphs into separate Fragments
        // (useful when the caller intends to manipulate glyphs independently).
        kFragmentGlyphs             = 0x01,

        // Compute the advance and ascent for each fragment.
        kTrackFragmentAdvanceAscent = 0x02,

        // Return cluster information.
        kClusters                   = 0x04,
    };

    struct TextDesc {
        const sk_sp<SkTypeface>&  fTypeface;
        SkScalar                  fTextSize       = 0,
                                  fMinTextSize    = 0,  // when auto-sizing
                                  fMaxTextSize    = 0,  // when auto-sizing
                                  fLineHeight     = 0,
                                  fLineShift      = 0,
                                  fAscent         = 0;
        SkTextUtils::Align        fHAlign         = SkTextUtils::kLeft_Align;
        VAlign                    fVAlign         = Shaper::VAlign::kTop;
        ResizePolicy              fResize         = Shaper::ResizePolicy::kNone;
        LinebreakPolicy           fLinebreak      = Shaper::LinebreakPolicy::kExplicit;
        Direction                 fDirection      = Shaper::Direction::kLTR ;
        Capitalization            fCapitalization = Shaper::Capitalization::kNone;
        size_t                    fMaxLines       = 0;  // when auto-sizing, 0 -> no max
        uint32_t                  fFlags          = 0;
        const char*               fLocale         = nullptr;
        const char*               fFontFamily     = nullptr;
    };

    // Performs text layout along an infinite horizontal line, starting at |point|.
    // Only explicit line breaks (\r) are observed.
    static Result Shape(const SkString& text, const TextDesc& desc, const SkPoint& point,
                        const sk_sp<SkFontMgr>&);

    // Performs text layout within |box|, injecting line breaks as needed to ensure
    // horizontal fitting.  The result is *not* guaranteed to fit vertically (it may extend
    // below the box bottom).
    static Result Shape(const SkString& text, const TextDesc& desc, const SkRect& box,
                        const sk_sp<SkFontMgr>&);

private:
    Shaper() = delete;
};

} // namespace skottie

#endif // SkottieTextShaper_DEFINED
