var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
/**
 * @module ol/render/canvas/TextBuilder
 */
import CanvasBuilder from './Builder.js';
import CanvasInstruction from './Instruction.js';
import TextPlacement from '../../style/TextPlacement.js';
import { asColorLike } from '../../colorlike.js';
import { defaultFillStyle, defaultFont, defaultLineCap, defaultLineDash, defaultLineDashOffset, defaultLineJoin, defaultLineWidth, defaultMiterLimit, defaultPadding, defaultStrokeStyle, defaultTextAlign, defaultTextBaseline, registerFont, } from '../canvas.js';
import { getUid } from '../../util.js';
import { intersects } from '../../extent.js';
import { matchingChunk } from '../../geom/flat/straightchunk.js';
/**
 * @const
 * @enum {number}
 */
export var TEXT_ALIGN = {
    'left': 0,
    'end': 0,
    'center': 0.5,
    'right': 1,
    'start': 1,
    'top': 0,
    'middle': 0.5,
    'hanging': 0.2,
    'alphabetic': 0.8,
    'ideographic': 0.8,
    'bottom': 1,
};
var CanvasTextBuilder = /** @class */ (function (_super) {
    __extends(CanvasTextBuilder, _super);
    /**
     * @param {number} tolerance Tolerance.
     * @param {import("../../extent.js").Extent} maxExtent Maximum extent.
     * @param {number} resolution Resolution.
     * @param {number} pixelRatio Pixel ratio.
     */
    function CanvasTextBuilder(tolerance, maxExtent, resolution, pixelRatio) {
        var _this = _super.call(this, tolerance, maxExtent, resolution, pixelRatio) || this;
        /**
         * @private
         * @type {Array<HTMLCanvasElement>}
         */
        _this.labels_ = null;
        /**
         * @private
         * @type {string|Array<string>}
         */
        _this.text_ = '';
        /**
         * @private
         * @type {number}
         */
        _this.textOffsetX_ = 0;
        /**
         * @private
         * @type {number}
         */
        _this.textOffsetY_ = 0;
        /**
         * @private
         * @type {boolean|undefined}
         */
        _this.textRotateWithView_ = undefined;
        /**
         * @private
         * @type {number}
         */
        _this.textRotation_ = 0;
        /**
         * @private
         * @type {?import("../canvas.js").FillState}
         */
        _this.textFillState_ = null;
        /**
         * @type {!Object<string, import("../canvas.js").FillState>}
         */
        _this.fillStates = {};
        /**
         * @private
         * @type {?import("../canvas.js").StrokeState}
         */
        _this.textStrokeState_ = null;
        /**
         * @type {!Object<string, import("../canvas.js").StrokeState>}
         */
        _this.strokeStates = {};
        /**
         * @private
         * @type {import("../canvas.js").TextState}
         */
        _this.textState_ = /** @type {import("../canvas.js").TextState} */ ({});
        /**
         * @type {!Object<string, import("../canvas.js").TextState>}
         */
        _this.textStates = {};
        /**
         * @private
         * @type {string}
         */
        _this.textKey_ = '';
        /**
         * @private
         * @type {string}
         */
        _this.fillKey_ = '';
        /**
         * @private
         * @type {string}
         */
        _this.strokeKey_ = '';
        /**
         * Data shared with an image builder for combined decluttering.
         * @private
         * @type {import("../canvas.js").DeclutterImageWithText}
         */
        _this.declutterImageWithText_ = undefined;
        return _this;
    }
    /**
     * @return {import("../canvas.js").SerializableInstructions} the serializable instructions.
     */
    CanvasTextBuilder.prototype.finish = function () {
        var instructions = _super.prototype.finish.call(this);
        instructions.textStates = this.textStates;
        instructions.fillStates = this.fillStates;
        instructions.strokeStates = this.strokeStates;
        return instructions;
    };
    /**
     * @param {import("../../geom/SimpleGeometry.js").default|import("../Feature.js").default} geometry Geometry.
     * @param {import("../../Feature.js").FeatureLike} feature Feature.
     */
    CanvasTextBuilder.prototype.drawText = function (geometry, feature) {
        var fillState = this.textFillState_;
        var strokeState = this.textStrokeState_;
        var textState = this.textState_;
        if (this.text_ === '' || !textState || (!fillState && !strokeState)) {
            return;
        }
        var coordinates = this.coordinates;
        var begin = coordinates.length;
        var geometryType = geometry.getType();
        var flatCoordinates = null;
        var stride = geometry.getStride();
        if (textState.placement === TextPlacement.LINE &&
            (geometryType == 'LineString' ||
                geometryType == 'MultiLineString' ||
                geometryType == 'Polygon' ||
                geometryType == 'MultiPolygon')) {
            if (!intersects(this.getBufferedMaxExtent(), geometry.getExtent())) {
                return;
            }
            var ends = void 0;
            flatCoordinates = geometry.getFlatCoordinates();
            if (geometryType == 'LineString') {
                ends = [flatCoordinates.length];
            }
            else if (geometryType == 'MultiLineString') {
                ends = /** @type {import("../../geom/MultiLineString.js").default} */ (geometry).getEnds();
            }
            else if (geometryType == 'Polygon') {
                ends = /** @type {import("../../geom/Polygon.js").default} */ (geometry)
                    .getEnds()
                    .slice(0, 1);
            }
            else if (geometryType == 'MultiPolygon') {
                var endss = 
                /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getEndss();
                ends = [];
                for (var i = 0, ii = endss.length; i < ii; ++i) {
                    ends.push(endss[i][0]);
                }
            }
            this.beginGeometry(geometry, feature);
            var textAlign = textState.textAlign;
            // No `justify` support for line placement.
            var flatOffset = 0;
            var flatEnd = void 0;
            for (var o = 0, oo = ends.length; o < oo; ++o) {
                if (textAlign == undefined) {
                    var range = matchingChunk(textState.maxAngle, flatCoordinates, flatOffset, ends[o], stride);
                    flatOffset = range[0];
                    flatEnd = range[1];
                }
                else {
                    flatEnd = ends[o];
                }
                for (var i = flatOffset; i < flatEnd; i += stride) {
                    coordinates.push(flatCoordinates[i], flatCoordinates[i + 1]);
                }
                var end = coordinates.length;
                flatOffset = ends[o];
                this.drawChars_(begin, end);
                begin = end;
            }
            this.endGeometry(feature);
        }
        else {
            var geometryWidths = textState.overflow ? null : [];
            switch (geometryType) {
                case 'Point':
                case 'MultiPoint':
                    flatCoordinates =
                        /** @type {import("../../geom/MultiPoint.js").default} */ (geometry).getFlatCoordinates();
                    break;
                case 'LineString':
                    flatCoordinates =
                        /** @type {import("../../geom/LineString.js").default} */ (geometry).getFlatMidpoint();
                    break;
                case 'Circle':
                    flatCoordinates =
                        /** @type {import("../../geom/Circle.js").default} */ (geometry).getCenter();
                    break;
                case 'MultiLineString':
                    flatCoordinates =
                        /** @type {import("../../geom/MultiLineString.js").default} */ (geometry).getFlatMidpoints();
                    stride = 2;
                    break;
                case 'Polygon':
                    flatCoordinates =
                        /** @type {import("../../geom/Polygon.js").default} */ (geometry).getFlatInteriorPoint();
                    if (!textState.overflow) {
                        geometryWidths.push(flatCoordinates[2] / this.resolution);
                    }
                    stride = 3;
                    break;
                case 'MultiPolygon':
                    var interiorPoints = 
                    /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getFlatInteriorPoints();
                    flatCoordinates = [];
                    for (var i = 0, ii = interiorPoints.length; i < ii; i += 3) {
                        if (!textState.overflow) {
                            geometryWidths.push(interiorPoints[i + 2] / this.resolution);
                        }
                        flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]);
                    }
                    if (flatCoordinates.length === 0) {
                        return;
                    }
                    stride = 2;
                    break;
                default:
            }
            var end = this.appendFlatPointCoordinates(flatCoordinates, stride);
            if (end === begin) {
                return;
            }
            if (geometryWidths &&
                (end - begin) / 2 !== flatCoordinates.length / stride) {
                var beg_1 = begin / 2;
                geometryWidths = geometryWidths.filter(function (w, i) {
                    var keep = coordinates[(beg_1 + i) * 2] === flatCoordinates[i * stride] &&
                        coordinates[(beg_1 + i) * 2 + 1] === flatCoordinates[i * stride + 1];
                    if (!keep) {
                        --beg_1;
                    }
                    return keep;
                });
            }
            this.saveTextStates_();
            if (textState.backgroundFill || textState.backgroundStroke) {
                this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke);
                if (textState.backgroundFill) {
                    this.updateFillStyle(this.state, this.createFill);
                    this.hitDetectionInstructions.push(this.createFill(this.state));
                }
                if (textState.backgroundStroke) {
                    this.updateStrokeStyle(this.state, this.applyStroke);
                    this.hitDetectionInstructions.push(this.createStroke(this.state));
                }
            }
            this.beginGeometry(geometry, feature);
            // adjust padding for negative scale
            var padding = textState.padding;
            if (padding != defaultPadding &&
                (textState.scale[0] < 0 || textState.scale[1] < 0)) {
                var p0 = textState.padding[0];
                var p1 = textState.padding[1];
                var p2 = textState.padding[2];
                var p3 = textState.padding[3];
                if (textState.scale[0] < 0) {
                    p1 = -p1;
                    p3 = -p3;
                }
                if (textState.scale[1] < 0) {
                    p0 = -p0;
                    p2 = -p2;
                }
                padding = [p0, p1, p2, p3];
            }
            // The image is unknown at this stage so we pass null; it will be computed at render time.
            // For clarity, we pass NaN for offsetX, offsetY, width and height, which will be computed at
            // render time.
            var pixelRatio_1 = this.pixelRatio;
            this.instructions.push([
                CanvasInstruction.DRAW_IMAGE,
                begin,
                end,
                null,
                NaN,
                NaN,
                NaN,
                1,
                0,
                0,
                this.textRotateWithView_,
                this.textRotation_,
                [1, 1],
                NaN,
                undefined,
                this.declutterImageWithText_,
                padding == defaultPadding
                    ? defaultPadding
                    : padding.map(function (p) {
                        return p * pixelRatio_1;
                    }),
                !!textState.backgroundFill,
                !!textState.backgroundStroke,
                this.text_,
                this.textKey_,
                this.strokeKey_,
                this.fillKey_,
                this.textOffsetX_,
                this.textOffsetY_,
                geometryWidths,
            ]);
            var scale = 1 / pixelRatio_1;
            this.hitDetectionInstructions.push([
                CanvasInstruction.DRAW_IMAGE,
                begin,
                end,
                null,
                NaN,
                NaN,
                NaN,
                1,
                0,
                0,
                this.textRotateWithView_,
                this.textRotation_,
                [scale, scale],
                NaN,
                undefined,
                this.declutterImageWithText_,
                padding,
                !!textState.backgroundFill,
                !!textState.backgroundStroke,
                this.text_,
                this.textKey_,
                this.strokeKey_,
                this.fillKey_,
                this.textOffsetX_,
                this.textOffsetY_,
                geometryWidths,
            ]);
            this.endGeometry(feature);
        }
    };
    /**
     * @private
     */
    CanvasTextBuilder.prototype.saveTextStates_ = function () {
        var strokeState = this.textStrokeState_;
        var textState = this.textState_;
        var fillState = this.textFillState_;
        var strokeKey = this.strokeKey_;
        if (strokeState) {
            if (!(strokeKey in this.strokeStates)) {
                this.strokeStates[strokeKey] = {
                    strokeStyle: strokeState.strokeStyle,
                    lineCap: strokeState.lineCap,
                    lineDashOffset: strokeState.lineDashOffset,
                    lineWidth: strokeState.lineWidth,
                    lineJoin: strokeState.lineJoin,
                    miterLimit: strokeState.miterLimit,
                    lineDash: strokeState.lineDash,
                };
            }
        }
        var textKey = this.textKey_;
        if (!(textKey in this.textStates)) {
            this.textStates[textKey] = {
                font: textState.font,
                textAlign: textState.textAlign || defaultTextAlign,
                justify: textState.justify,
                textBaseline: textState.textBaseline || defaultTextBaseline,
                scale: textState.scale,
            };
        }
        var fillKey = this.fillKey_;
        if (fillState) {
            if (!(fillKey in this.fillStates)) {
                this.fillStates[fillKey] = {
                    fillStyle: fillState.fillStyle,
                };
            }
        }
    };
    /**
     * @private
     * @param {number} begin Begin.
     * @param {number} end End.
     */
    CanvasTextBuilder.prototype.drawChars_ = function (begin, end) {
        var strokeState = this.textStrokeState_;
        var textState = this.textState_;
        var strokeKey = this.strokeKey_;
        var textKey = this.textKey_;
        var fillKey = this.fillKey_;
        this.saveTextStates_();
        var pixelRatio = this.pixelRatio;
        var baseline = TEXT_ALIGN[textState.textBaseline];
        var offsetY = this.textOffsetY_ * pixelRatio;
        var text = this.text_;
        var strokeWidth = strokeState
            ? (strokeState.lineWidth * Math.abs(textState.scale[0])) / 2
            : 0;
        this.instructions.push([
            CanvasInstruction.DRAW_CHARS,
            begin,
            end,
            baseline,
            textState.overflow,
            fillKey,
            textState.maxAngle,
            pixelRatio,
            offsetY,
            strokeKey,
            strokeWidth * pixelRatio,
            text,
            textKey,
            1,
        ]);
        this.hitDetectionInstructions.push([
            CanvasInstruction.DRAW_CHARS,
            begin,
            end,
            baseline,
            textState.overflow,
            fillKey,
            textState.maxAngle,
            1,
            offsetY,
            strokeKey,
            strokeWidth,
            text,
            textKey,
            1 / pixelRatio,
        ]);
    };
    /**
     * @param {import("../../style/Text.js").default} textStyle Text style.
     * @param {Object} [opt_sharedData] Shared data.
     */
    CanvasTextBuilder.prototype.setTextStyle = function (textStyle, opt_sharedData) {
        var textState, fillState, strokeState;
        if (!textStyle) {
            this.text_ = '';
        }
        else {
            var textFillStyle = textStyle.getFill();
            if (!textFillStyle) {
                fillState = null;
                this.textFillState_ = fillState;
            }
            else {
                fillState = this.textFillState_;
                if (!fillState) {
                    fillState = /** @type {import("../canvas.js").FillState} */ ({});
                    this.textFillState_ = fillState;
                }
                fillState.fillStyle = asColorLike(textFillStyle.getColor() || defaultFillStyle);
            }
            var textStrokeStyle = textStyle.getStroke();
            if (!textStrokeStyle) {
                strokeState = null;
                this.textStrokeState_ = strokeState;
            }
            else {
                strokeState = this.textStrokeState_;
                if (!strokeState) {
                    strokeState = /** @type {import("../canvas.js").StrokeState} */ ({});
                    this.textStrokeState_ = strokeState;
                }
                var lineDash = textStrokeStyle.getLineDash();
                var lineDashOffset = textStrokeStyle.getLineDashOffset();
                var lineWidth = textStrokeStyle.getWidth();
                var miterLimit = textStrokeStyle.getMiterLimit();
                strokeState.lineCap = textStrokeStyle.getLineCap() || defaultLineCap;
                strokeState.lineDash = lineDash ? lineDash.slice() : defaultLineDash;
                strokeState.lineDashOffset =
                    lineDashOffset === undefined ? defaultLineDashOffset : lineDashOffset;
                strokeState.lineJoin = textStrokeStyle.getLineJoin() || defaultLineJoin;
                strokeState.lineWidth =
                    lineWidth === undefined ? defaultLineWidth : lineWidth;
                strokeState.miterLimit =
                    miterLimit === undefined ? defaultMiterLimit : miterLimit;
                strokeState.strokeStyle = asColorLike(textStrokeStyle.getColor() || defaultStrokeStyle);
            }
            textState = this.textState_;
            var font = textStyle.getFont() || defaultFont;
            registerFont(font);
            var textScale = textStyle.getScaleArray();
            textState.overflow = textStyle.getOverflow();
            textState.font = font;
            textState.maxAngle = textStyle.getMaxAngle();
            textState.placement = textStyle.getPlacement();
            textState.textAlign = textStyle.getTextAlign();
            textState.justify = textStyle.getJustify();
            textState.textBaseline =
                textStyle.getTextBaseline() || defaultTextBaseline;
            textState.backgroundFill = textStyle.getBackgroundFill();
            textState.backgroundStroke = textStyle.getBackgroundStroke();
            textState.padding = textStyle.getPadding() || defaultPadding;
            textState.scale = textScale === undefined ? [1, 1] : textScale;
            var textOffsetX = textStyle.getOffsetX();
            var textOffsetY = textStyle.getOffsetY();
            var textRotateWithView = textStyle.getRotateWithView();
            var textRotation = textStyle.getRotation();
            this.text_ = textStyle.getText() || '';
            this.textOffsetX_ = textOffsetX === undefined ? 0 : textOffsetX;
            this.textOffsetY_ = textOffsetY === undefined ? 0 : textOffsetY;
            this.textRotateWithView_ =
                textRotateWithView === undefined ? false : textRotateWithView;
            this.textRotation_ = textRotation === undefined ? 0 : textRotation;
            this.strokeKey_ = strokeState
                ? (typeof strokeState.strokeStyle == 'string'
                    ? strokeState.strokeStyle
                    : getUid(strokeState.strokeStyle)) +
                    strokeState.lineCap +
                    strokeState.lineDashOffset +
                    '|' +
                    strokeState.lineWidth +
                    strokeState.lineJoin +
                    strokeState.miterLimit +
                    '[' +
                    strokeState.lineDash.join() +
                    ']'
                : '';
            this.textKey_ =
                textState.font +
                    textState.scale +
                    (textState.textAlign || '?') +
                    (textState.justify || '?') +
                    (textState.textBaseline || '?');
            this.fillKey_ = fillState
                ? typeof fillState.fillStyle == 'string'
                    ? fillState.fillStyle
                    : '|' + getUid(fillState.fillStyle)
                : '';
        }
        this.declutterImageWithText_ = opt_sharedData;
    };
    return CanvasTextBuilder;
}(CanvasBuilder));
export default CanvasTextBuilder;
//# sourceMappingURL=TextBuilder.js.map