drawText(geometry, feature, index) {
    const fillState = this.textFillState_;
    const strokeState = this.textStrokeState_;
    const textState = this.textState_;
    if (this.text_ === '' || !textState || (!fillState && !strokeState)) {
      return;
    }

    const coordinates = this.coordinates;
    let begin = coordinates.length;

    const geometryType = geometry.getType();
    let flatCoordinates = null;
    let stride = geometry.getStride();

    if (
      textState.placement === 'line' &&
      (geometryType == 'LineString' ||
        geometryType == 'MultiLineString' ||
        geometryType == 'Polygon' ||
        geometryType == 'MultiPolygon')
    ) {
      if (!intersects(this.maxExtent, geometry.getExtent())) {
        return;
      }
      let ends;
      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') {
        const endss =
          /** @type {import("../../geom/MultiPolygon.js").default} */ (
            geometry
          ).getEndss();
        ends = [];
        for (let i = 0, ii = endss.length; i < ii; ++i) {
          ends.push(endss[i][0]);
        }
      }
      this.beginGeometry(geometry, feature, index);
      const repeat = textState.repeat;
      const textAlign = repeat ? undefined : textState.textAlign;
      // No `justify` support for line placement.
      let flatOffset = 0;
      for (let o = 0, oo = ends.length; o < oo; ++o) {
        let chunks;
        if (repeat) {
          chunks = lineChunk(
            repeat * this.resolution,
            flatCoordinates,
            flatOffset,
            ends[o],
            stride,
          );
        } else {
          chunks = [flatCoordinates.slice(flatOffset, ends[o])];
        }
        for (let c = 0, cc = chunks.length; c < cc; ++c) {
          const chunk = chunks[c];
          let chunkBegin = 0;
          let chunkEnd = chunk.length;
          if (textAlign == undefined) {
            const range = matchingChunk(
              textState.maxAngle,
              chunk,
              0,
              chunk.length,
              2,
            );
            chunkBegin = range[0];
            chunkEnd = range[1];
          }
          for (let i = chunkBegin; i < chunkEnd; i += stride) {
            coordinates.push(chunk[i], chunk[i + 1]);
          }
          const end = coordinates.length;
          flatOffset = ends[o];
          this.drawChars_(begin, end);
          begin = end;
        }
      }
      this.endGeometry(feature);
    } else {
      let 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':
          const interiorPoints =
            /** @type {import("../../geom/MultiPolygon.js").default} */ (
              geometry
            ).getFlatInteriorPoints();
          flatCoordinates = [];
          for (let 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:
      }
      const end = this.appendFlatPointCoordinates(flatCoordinates, stride);
      if (end === begin) {
        return;
      }
      if (
        geometryWidths &&
        (end - begin) / 2 !== flatCoordinates.length / stride
      ) {
        let beg = begin / 2;
        geometryWidths = geometryWidths.filter((w, i) => {
          const keep =
            coordinates[(beg + i) * 2] === flatCoordinates[i * stride] &&
            coordinates[(beg + i) * 2 + 1] === flatCoordinates[i * stride + 1];
          if (!keep) {
            --beg;
          }
          return keep;
        });
      }

      this.saveTextStates_();

      const backgroundFill = textState.backgroundFill
        ? this.createFill(this.fillStyleToState(textState.backgroundFill))
        : null;
      const backgroundStroke = textState.backgroundStroke
        ? this.createStroke(this.strokeStyleToState(textState.backgroundStroke))
        : null;

      this.beginGeometry(geometry, feature, index);

      // adjust padding for negative scale
      let padding = textState.padding;
      if (
        padding != defaultPadding &&
        (textState.scale[0] < 0 || textState.scale[1] < 0)
      ) {
        let p0 = textState.padding[0];
        let p1 = textState.padding[1];
        let p2 = textState.padding[2];
        let 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.
      const pixelRatio = this.pixelRatio;
      this.instructions.push([
        CanvasInstruction.DRAW_IMAGE,
        begin,
        end,
        null,
        NaN,
        NaN,
        NaN,
        1,
        0,
        0,
        this.textRotateWithView_,
        this.textRotation_,
        [1, 1],
        NaN,
        this.declutterMode_,
        this.declutterImageWithText_,
        padding == defaultPadding
          ? defaultPadding
          : padding.map(function (p) {
              return p * pixelRatio;
            }),
        backgroundFill,
        backgroundStroke,
        this.text_,
        this.textKey_,
        this.strokeKey_,
        this.fillKey_,
        this.textOffsetX_,
        this.textOffsetY_,
        geometryWidths,
      ]);
      const scale = 1 / pixelRatio;
      // Set default fill for hit detection background
      const hitDetectionBackgroundFill = backgroundFill
        ? backgroundFill.slice(0)
        : null;
      if (hitDetectionBackgroundFill) {
        hitDetectionBackgroundFill[1] = defaultFillStyle;
      }
      this.hitDetectionInstructions.push([
        CanvasInstruction.DRAW_IMAGE,
        begin,
        end,
        null,
        NaN,
        NaN,
        NaN,
        1,
        0,
        0,
        this.textRotateWithView_,
        this.textRotation_,
        [scale, scale],
        NaN,
        this.declutterMode_,
        this.declutterImageWithText_,
        padding,
        hitDetectionBackgroundFill,
        backgroundStroke,
        this.text_,
        this.textKey_,
        this.strokeKey_,
        this.fillKey_ ? defaultFillStyle : this.fillKey_,
        this.textOffsetX_,
        this.textOffsetY_,
        geometryWidths,
      ]);

      this.endGeometry(feature);
    }
  }
@param {import("../../geom/SimpleGeometry.js").default|import("../Feature.js").default} geometry Geometry.
@param {import("../../Feature.js").FeatureLike} feature Feature.
@param {number} [index] Render order index.
@override