/***************************************************************************
  qgstextrenderer.h
  -----------------
   begin                : September 2015
   copyright            : (C) Nyall Dawson
   email                : nyall dot dawson at gmail dot com
 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef QGSTEXTRENDERER_H
#define QGSTEXTRENDERER_H

#include "qgis.h"
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsmapunitscale.h"
#include "qgstextblock.h"

#include <QPainterPath>
#include <QPicture>

class QgsTextDocument;
class QgsTextDocumentMetrics;
class QgsTextFormat;
class QgsRenderContext;

class QFontMetricsF;

/**
 * \class QgsTextRenderer
  * \ingroup core
  * \brief Handles rendering text using rich formatting options, including drop shadows, buffers
  * and background shapes.
 */
class CORE_EXPORT QgsTextRenderer
{
  public:
    /**
     * Converts a Qt horizontal \a alignment flag to a Qgis::TextHorizontalAlignment value.
     *
     * \see convertQtVAlignment()
     * \since QGIS 3.16
     */
    static Qgis::TextHorizontalAlignment convertQtHAlignment( Qt::Alignment alignment );

    /**
     * Converts a Qt vertical \a alignment flag to a Qgis::TextVerticalAlignment value.
     *
     * \see convertQtHAlignment()
     * \since QGIS 3.16
     */
    static Qgis::TextVerticalAlignment convertQtVAlignment( Qt::Alignment alignment );

    /**
     * Calculates pixel size (considering output size should be in pixel or map units, scale factors and optionally oversampling)
     * \param size size to convert
     * \param c rendercontext
     * \param unit size units
     * \param mapUnitScale a mapUnitScale clamper
     * \returns font pixel size
     */
    static int sizeToPixel( double size, const QgsRenderContext &c, Qgis::RenderUnit unit, const QgsMapUnitScale &mapUnitScale = QgsMapUnitScale() );

    // TODO QGIS 5.0 -- remove drawAsOutlines from below methods!

    /**
     * Draws text within a rectangle using the specified settings.
     * \param rect destination rectangle for text, in painter units
     * \param rotation text rotation
     * \param alignment horizontal alignment
     * \param textLines list of lines of text to draw
     * \param context render context
     * \param format text format
     * \param drawAsOutlines set to FALSE to render text as text. This allows outputs to
     * formats like SVG to maintain text as text objects, but at the cost of degraded
     * rendering and may result in side effects like misaligned text buffers. This setting is deprecated and has no effect
     * as of QGIS 3.4.3 and the text format should be set using QgsRenderContext::setTextRenderFormat() instead.
     * \param vAlignment vertical alignment (since QGIS 3.16)
     * \param flags text rendering flags (since QGIS 3.24)
     * \param mode text layout mode. Only Qgis::TextLayoutMode::Rectangle, Qgis::TextLayoutMode::RectangleCapHeightBased and Qgis::TextLayoutMode::RectangleAscentBased are accepted (since QGIS 3.30)
     *
     * \see drawDocument(), which is more efficient if the text document and metrics have already been calculated.
     */
    static void drawText(
      const QRectF &rect,
      double rotation,
      Qgis::TextHorizontalAlignment alignment,
      const QStringList &textLines,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      bool drawAsOutlines = true,
      Qgis::TextVerticalAlignment vAlignment = Qgis::TextVerticalAlignment::Top,
      Qgis::TextRendererFlags flags = Qgis::TextRendererFlags(),
      Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Rectangle
    );

    /**
     * Draws a text document within a rectangle using the specified settings.
     *
     * Calling this method is more efficient than calling drawText() if the text document and metrics have already
     * been calculated.
     *
     * \warning Unlike drawText(), this method does not automatically update data defined properties in the text \a format. This
     * is the caller's responsibility to do, and must be done prior to generating the text \a document and \a metrics.
     *
     * \param rect destination rectangle for text, in painter units
     * \param format base text format
     * \param document text document to draw
     * \param metrics precalculated text metrics
     * \param context destination render context
     * \param horizontalAlignment horizontal alignment
     * \param verticalAlignment vertical alignment
     * \param rotation text rotation
     * \param mode text layout mode. Only Qgis::TextLayoutMode::Rectangle, Qgis::TextLayoutMode::RectangleCapHeightBased and Qgis::TextLayoutMode::RectangleAscentBased are accepted.
     * \param flags text rendering flags
     *
     * \since QGIS 3.30
     */
    static void drawDocument(
      const QRectF &rect,
      const QgsTextFormat &format,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      QgsRenderContext &context,
      Qgis::TextHorizontalAlignment horizontalAlignment = Qgis::TextHorizontalAlignment::Left,
      Qgis::TextVerticalAlignment verticalAlignment = Qgis::TextVerticalAlignment::Top,
      double rotation = 0,
      Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Rectangle,
      Qgis::TextRendererFlags flags = Qgis::TextRendererFlags()
    );

    /**
     * Draws text at a point origin using the specified settings.
     * \param point origin of text, in painter units
     * \param rotation text rotation
     * \param alignment horizontal alignment
     * \param textLines list of lines of text to draw
     * \param context render context
     * \param format text format
     * \param drawAsOutlines set to FALSE to render text as text. This allows outputs to
     * formats like SVG to maintain text as text objects, but at the cost of degraded
     * rendering and may result in side effects like misaligned text buffers. This setting is deprecated and has no effect
     * as of QGIS 3.4.3 and the text format should be set using QgsRenderContext::setTextRenderFormat() instead.
     */
    static void drawText(
      QPointF point, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines = true
    );

    /**
     * Draws a text document at a point origin using the specified settings.
     *
     * Calling this method is more efficient than calling drawText() if the text document and metrics have already
     * been calculated.
     *
     * \warning Unlike drawText(), this method does not automatically update data defined properties in the text \a format. This
     * is the caller's responsibility to do, and must be done prior to generating the text \a document and \a metrics.
     *
     * \param point origin of text, in painter units
     * \param format base text format
     * \param document text document to draw
     * \param metrics precalculated text metrics
     * \param context destination render context
     * \param alignment horizontal alignment
     * \param rotation text rotation
     * \param mode optional layout mode (since QGIS 3.42)
     *
     * \since QGIS 3.40
     */
    static void drawDocument(
      QPointF point,
      const QgsTextFormat &format,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      QgsRenderContext &context,
      Qgis::TextHorizontalAlignment alignment,
      double rotation,
      Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Point
    );

    /**
     * Draws text along a line using the specified settings.
     *
     * \param line line to render text along, in painter units
     * \param text text to draw
     * \param context render context
     * \param format text format
     * \param offsetAlongLine offset along the line (in painter units) to start text at
     * \param offsetFromLine offset from the line (in painter units). Negative values will shift the text to the left of the line, positive values will shift the text to the right.
     * \param flags curved text behavior flags (since QGIS 4.0)
     *
     * \since QGIS 3.32
     */
    static void drawTextOnLine(
      const QPolygonF &line,
      const QString &text,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      double offsetAlongLine = 0,
      double offsetFromLine = 0,
      Qgis::CurvedTextFlags flags = Qgis::CurvedTextFlag::UseBaselinePlacement | Qgis::CurvedTextFlag::TruncateStringWhenLineIsTooShort
    );

    /**
     * Draws a text document along a line using the specified settings.
     *
     * \param line line to render text along, in painter units
     * \param format text format
     * \param document text document to draw
     * \param context render context
     * \param offsetAlongLine offset along the line (in painter units) to start text at
     * \param offsetFromLine offset from the line (in painter units). Negative values will shift the text to the left of the line, positive values will shift the text to the right.
     * \param flags curved text behavior flags (since QGIS 4.0)
     *
     * \since QGIS 3.32
     */
    static void drawDocumentOnLine(
      const QPolygonF &line,
      const QgsTextFormat &format,
      const QgsTextDocument &document,
      QgsRenderContext &context,
      double offsetAlongLine = 0,
      double offsetFromLine = 0,
      Qgis::CurvedTextFlags flags = Qgis::CurvedTextFlag::UseBaselinePlacement | Qgis::CurvedTextFlag::TruncateStringWhenLineIsTooShort
    );

    /**
     * Draws a single component of rendered text using the specified settings.
     * \param rect destination rectangle for text, in painter units
     * \param rotation text rotation
     * \param alignment horizontal alignment
     * \param textLines list of lines of text to draw
     * \param context render context
     * \param format text format
     * \param part component of text to draw. Note that Shadow parts cannot be drawn
     * individually and instead are drawn with their associated part (e.g., drawn together
     * with the text or background parts)
     * \param drawAsOutlines set to FALSE to render text as text. This allows outputs to
     * formats like SVG to maintain text as text objects, but at the cost of degraded
     * rendering and may result in side effects like misaligned text buffers. This setting is deprecated and has no effect
     * as of QGIS 3.4.3 and the text format should be set using QgsRenderContext::setTextRenderFormat() instead.
     *
     * \deprecated QGIS 3.40. Private API only, will be removed in 4.0.
     */
    Q_DECL_DEPRECATED static void drawPart(
      const QRectF &rect,
      double rotation,
      Qgis::TextHorizontalAlignment alignment,
      const QStringList &textLines,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponent part,
      bool drawAsOutlines = true
    ) SIP_DEPRECATED;

    /**
     * Draws a single component of rendered text using the specified settings.
     * \param origin origin for start of text, in painter units. Y coordinate will be used as baseline.
     * \param rotation text rotation
     * \param alignment horizontal alignment
     * \param textLines list of lines of text to draw
     * \param context render context
     * \param format text format
     * \param part component of text to draw. Note that Shadow parts cannot be drawn
     * individually and instead are drawn with their associated part (e.g., drawn together
     * with the text or background parts)
     * \param drawAsOutlines set to FALSE to render text as text. This allows outputs to
     * formats like SVG to maintain text as text objects, but at the cost of degraded
     * rendering and may result in side effects like misaligned text buffers. This setting is deprecated and has no effect
     * as of QGIS 3.4.3 and the text format should be set using QgsRenderContext::setTextRenderFormat() instead.
     *
     * \deprecated QGIS 3.40. Private API only, will be removed in 4.0.
     */
    Q_DECL_DEPRECATED static void drawPart(
      QPointF origin,
      double rotation,
      Qgis::TextHorizontalAlignment alignment,
      const QStringList &textLines,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponent part,
      bool drawAsOutlines = true
    ) SIP_DEPRECATED;

    /**
     * Returns the font metrics for the given text \a format, when rendered
     * in the specified render \a context. The font metrics will take into account
     * all scaling required by the render context.
     *
     * The optional \a scaleFactor argument can specify a font size scaling factor. It is recommended to set this to
     * QgsTextRenderer::calculateScaleFactorForFormat() and then manually scale painter devices or calculations
     * based on the resultant font metrics. Failure to do so will result in poor quality text rendering
     * at small font sizes.
     *
     * \since QGIS 3.2
     */
    static QFontMetricsF fontMetrics( QgsRenderContext &context, const QgsTextFormat &format, double scaleFactor = 1.0 );

    /**
     * Returns the width of a text based on a given format.
     * \param context render context
     * \param format text format
     * \param textLines list of lines of text to calculate width from
     * \param fontMetrics font metrics
     */
    static double textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics = nullptr );

    /**
     * Returns the height of a text based on a given format.
     * \param context render context
     * \param format text format
     * \param textLines list of lines of text to calculate width from
     * \param mode draw mode
     * \param fontMetrics font metrics
     * \param flags text renderer flags (since QGIS 3.24)
     * \param maxLineWidth maximum line width, in painter units. Used when the Qgis::TextRendererFlag::WrapLines flag is used (since QGIS 3.24)
     */
    static double textHeight(
      const QgsRenderContext &context,
      const QgsTextFormat &format,
      const QStringList &textLines,
      Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Point,
      QFontMetricsF *fontMetrics = nullptr,
      Qgis::TextRendererFlags flags = Qgis::TextRendererFlags(),
      double maxLineWidth = 0
    );

    /**
     * Returns the height of a character when rendered with the specified text \a format.
     *
     * \param context render context
     * \param format text format
     * \param character character to determine height of. If \a character is invalid, then the maximum character height will be returned.
     * \param includeEffects if TRUE, then the size of formatting effects such as buffers and shadows will be considered in the
     * returned height. If FALSE, then the returned size considers the character only.
     *
     * \since QGIS 3.16
     */
    static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects = false );

    /**
     * Returns TRUE if the specified \a text requires line wrapping in order to fit within the specified \a width (in painter units).
     *
     * \see wrappedText()
     * \since QGIS 3.24
     */
    static bool textRequiresWrapping( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format );

    /**
     * Wraps a \a text string to multiple lines, such that each individual line will fit within the specified \a width (in painter units).
     *
     * \see textRequiresWrapping()
     * \since QGIS 3.24
     */
    static QStringList wrappedText( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format );

    /**
     * Scale factor for upscaling font sizes and downscaling destination painter devices.
     *
     * Using this scale factor and manually adjusting any font metric based calculations results in more stable
     * font metrics and sizes for small font sizes.
     *
     * \warning Deprecated, use calculateScaleFactorForFormat() instead.
     *
     * \since QGIS 3.16
     */
    static constexpr double FONT_WORKAROUND_SCALE = 10;

    /**
     * Returns the scale factor used for upscaling font sizes and downscaling destination painter devices.
     *
     * Using this scale factor and manually adjusting any font metric based calculations results in more stable
     * font metrics and sizes for small font sizes.
     *
     * \since QGIS 3.40
     */
    static double calculateScaleFactorForFormat( const QgsRenderContext &context, const QgsTextFormat &format );

    // to match QTextEngine handling of superscript/subscript font sizes

    /**
     * Scale factor to use for super or subscript text which doesn't have an explicit font size set.
     *
     * \since QGIS 3.32
     */
    static constexpr double SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR = 2.0 / 3.0;

  private:
    struct Component
    {
        //! Block to render
        QgsTextBlock block;

        //! Index of block
        int blockIndex = 0;

        //! Index of first fragment in block
        int firstFragmentIndex = 0;

        //! Current origin point for painting (generally current painter rotation point)
        QPointF origin;
        //! Whether to translate the painter to supplied origin
        bool useOrigin = false;
        //! Any rotation to be applied to painter (in radians)
        double rotation = 0.0;
        //! Any rotation to be applied to painter (in radians) after initial rotation
        double rotationOffset = 0.0;
        //! Current center point of label component, after rotation
        QPointF center;
        //! Width and height of label component, transformed and ready for painting
        QSizeF size;
        //! Any translation offsets to be applied before painting, transformed and ready for painting
        QPointF offset;
        //! A stored QPicture of painting for the component
        QPicture picture;

        /**
       * Buffer for component to accommodate graphic items ignored by QPicture,
       * e.g. half-width of an applied QPen, which would extend beyond boundingRect() of QPicture
       */
        double pictureBuffer = 0.0;
        //! A ratio of native painter dpi and that of rendering context's painter
        double dpiRatio = 1.0;
        //! Horizontal alignment
        Qgis::TextHorizontalAlignment hAlign = Qgis::TextHorizontalAlignment::Left;

        //! Any additional word spacing to apply while rendering component
        double extraWordSpacing = 0;
        //! Any additional letter spacing to apply while rendering component
        double extraLetterSpacing = 0;
    };

    static double textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document );
    static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document, Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Point );

    /**
     * Draws components of rendered text using the specified settings.
     * \param rect destination rectangle for text
     * \param rotation text rotation
     * \param alignment horizontal alignment
     * \param vAlignment vertical alignment
     * \param document text document to draw
     * \param metrics document metrics
     * \param context render context
     * \param format text format
     * \param parts components of text to draw. Note that Shadow parts cannot be drawn
     * individually and instead are drawn with their associated part (e.g., drawn together
     * with the text or background parts)
     * \param mode layout mode
     * \note Not available in Python bindings
     * \since QGIS 3.14
     */
    static void drawParts(
      const QRectF &rect,
      double rotation,
      Qgis::TextHorizontalAlignment alignment,
      Qgis::TextVerticalAlignment vAlignment,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponents parts,
      Qgis::TextLayoutMode mode
    );

    /**
     * Draws components of rendered text using the specified settings.
     * \param origin origin for start of text. Y coordinate will be used as baseline.
     * \param rotation text rotation
     * \param alignment horizontal alignment
     * \param document document to draw
     * \param metrics precalculated document metrics
     * \param context render context
     * \param format text format
     * \param parts components of text to draw. Note that Shadow parts cannot be drawn
     * individually and instead are drawn with their associated part (e.g., drawn together
     * with the text or background parts)
     * \param mode layout mode
     * \note Not available in Python bindings
     * \since QGIS 3.14
     */
    static void drawParts(
      QPointF origin,
      double rotation,
      Qgis::TextHorizontalAlignment alignment,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponents parts,
      Qgis::TextLayoutMode mode
    );

    static double drawBuffer( QgsRenderContext &context, const Component &component, const QgsTextFormat &format, const QgsTextDocumentMetrics &metrics, Qgis::TextLayoutMode mode );

    static void drawBackground(
      QgsRenderContext &context, const Component &component, const QgsTextFormat &format, const QgsTextDocumentMetrics &metrics, Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Rectangle
    );

    static void drawShadow( QgsRenderContext &context, const Component &component, const QgsTextFormat &format );

    static void drawMask( QgsRenderContext &context, const Component &component, const QgsTextFormat &format, const QgsTextDocumentMetrics &metrics, Qgis::TextLayoutMode mode );

    static void drawText( QgsRenderContext &context, const Component &component, const QgsTextFormat &format );

    static void drawTextInternal(
      Qgis::TextComponents components,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      const Component &component,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      Qgis::TextHorizontalAlignment alignment,
      Qgis::TextVerticalAlignment vAlignment,
      Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Rectangle
    );

    static Qgis::TextOrientation calculateRotationAndOrientationForComponent( const QgsTextFormat &format, const Component &component, double &rotation );

    static void calculateExtraSpacingForLineJustification( double spaceToDistribute, const QgsTextBlock &block, double &extraWordSpace, double &extraLetterSpace );
    static void applyExtraSpacingForLineJustification( QFont &font, double extraWordSpace, double extraLetterSpace );

    static void drawTextInternalHorizontal(
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponents components,
      Qgis::TextLayoutMode mode,
      const Component &component,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      double fontScale,
      Qgis::TextHorizontalAlignment hAlignment,
      Qgis::TextVerticalAlignment vAlignment,
      double rotation
    );

    static void drawTextInternalVertical(
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponents components,
      Qgis::TextLayoutMode mode,
      const Component &component,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      double fontScale,
      Qgis::TextHorizontalAlignment hAlignment,
      Qgis::TextVerticalAlignment vAlignment,
      double rotation
    );

    struct DeferredRenderFragment
    {
        // mandatory
        QColor color;
        QPointF point;
        // optional
        QPainterPath path;
        // optional
        QFont font;
        QString text;
    };

    struct BlockMetrics
    {
        double xOffset = 0;
        double backgroundXOffset = 0;
        double width = 0;
        double backgroundWidth = 0;
        double extraWordSpace = 0;
        double extraLetterSpace = 0;
    };

    static QVector< QgsTextRenderer::BlockMetrics > calculateBlockMetrics(
      const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, Qgis::TextLayoutMode mode, double targetWidth, const Qgis::TextHorizontalAlignment hAlignment
    );

    struct DeferredRenderBlock
    {
        QPointF origin;
        Component component;
        QVector< DeferredRenderFragment > fragments;
    };

    static QBrush createBrushForPath( QgsRenderContext &context, const QString &path );

    static void renderBlockHorizontal(
      const QgsTextBlock &block,
      int blockIndex,
      const QgsTextDocumentMetrics &metrics,
      QgsRenderContext &context,
      const QgsTextFormat &format,
      QPainter *painter,
      bool forceRenderAsPaths,
      double fontScale,
      double extraWordSpace,
      double extraLetterSpace,
      Qgis::TextLayoutMode mode,
      DeferredRenderBlock *deferredRenderBlock
    );
    static void renderDocumentBackgrounds(
      QgsRenderContext &context,
      const QgsTextDocument &document,
      const QgsTextDocumentMetrics &metrics,
      const Component &component,
      const QVector< QgsTextRenderer::BlockMetrics > &blockMetrics,
      Qgis::TextLayoutMode mode,
      double verticalAlignOffset,
      double rotation
    );
    static void renderDeferredBlocks(
      QgsRenderContext &context,
      const QgsTextFormat &format,
      Qgis::TextComponents components,
      const std::vector<DeferredRenderBlock> &deferredBlocks,
      bool usePathsForText,
      double fontScale,
      const Component &component,
      double rotation
    );
    static void renderDeferredBuffer(
      QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, const std::vector<DeferredRenderBlock> &deferredBlocks, double fontScale, const Component &component, double rotation
    );
    static void renderDeferredShadowForText(
      QgsRenderContext &context, const QgsTextFormat &format, const std::vector<DeferredRenderBlock> &deferredBlocks, double fontScale, const Component &component, double rotation
    );
    static void renderDeferredText( QgsRenderContext &context, const std::vector<DeferredRenderBlock> &deferredBlocks, bool usePathsForText, double fontScale, const Component &component, double rotation );

    friend class QgsVectorLayerLabelProvider;
    friend class QgsLabelPreview;

    static QgsTextFormat updateShadowPosition( const QgsTextFormat &format );

    /**
     * Returns TRUE if paths should be used to render a document
     */
    static bool usePathsToRender( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document );

    /**
     * Returns TRUE if a picture should be used to render a document
     */
    static bool usePictureToRender( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document );
};

#endif // QGSTEXTRENDERER_H
