/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  Labeling Implementation.
 * Author:   Steve Lime and the MapServer team.
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

/*
** maplabel.c: Routines to enable text drawing, BITMAP or TRUETYPE.
*/

#include <assert.h>
#include <float.h>

#include "mapserver.h"
#include "fontcache.h"

#include "cpl_vsi.h"
#include "cpl_string.h"

void initTextPath(textPathObj *ts) { memset(ts, 0, sizeof(*ts)); }

int WARN_UNUSED msLayoutTextSymbol(mapObj *map, textSymbolObj *ts,
                                   textPathObj *tgret);

#if defined(USE_EXTENDED_DEBUG) && 0
static void msDebugTextPath(textSymbolObj *ts) {
  int i;
  msDebug("text: %s\n", ts->annotext);
  if (ts->textpath) {
    for (i = 0; i < ts->textpath->numglyphs; i++) {
      glyphObj *g = &ts->textpath->glyphs[i];
      msDebug("glyph %d: pos: %f %f\n", g->glyph->key.codepoint, g->pnt.x,
              g->pnt.y);
    }
  } else {
    msDebug("no glyphs\n");
  }
  msDebug("\n=========================================\n");
}
#endif

int msComputeTextPath(mapObj *map, textSymbolObj *ts) {
  textPathObj *tgret = msSmallMalloc(sizeof(textPathObj));
  assert(ts->annotext && *ts->annotext);
  initTextPath(tgret);
  ts->textpath = tgret;
  tgret->absolute = 0;
  tgret->glyph_size = ts->label->size * ts->scalefactor;
  tgret->glyph_size =
      MS_MAX(tgret->glyph_size, ts->label->minsize * ts->resolutionfactor);
  tgret->glyph_size = MS_NINT(
      MS_MIN(tgret->glyph_size, ts->label->maxsize * ts->resolutionfactor));
  tgret->line_height = ceil(tgret->glyph_size * 1.33);
  return msLayoutTextSymbol(map, ts, tgret);
}

void initTextSymbol(textSymbolObj *ts) { memset(ts, 0, sizeof(*ts)); }

void freeTextPath(textPathObj *tp) {
  free(tp->glyphs);
  if (tp->bounds.poly) {
    free(tp->bounds.poly->point);
    free(tp->bounds.poly);
  }
}

void freeTextSymbol(textSymbolObj *ts) { freeTextSymbolEx(ts, MS_TRUE); }

void freeTextSymbolEx(textSymbolObj *ts, int doFreeLabel) {
  if (ts->textpath) {
    freeTextPath(ts->textpath);
    free(ts->textpath);
  }
  if (ts->label->numstyles) {
    if (ts->style_bounds) {
      int i;
      for (i = 0; i < ts->label->numstyles; i++) {
        if (ts->style_bounds[i]) {
          if (ts->style_bounds[i]->poly) {
            free(ts->style_bounds[i]->poly->point);
            free(ts->style_bounds[i]->poly);
          }
          free(ts->style_bounds[i]);
        }
      }
      free(ts->style_bounds);
    }
  }
  free(ts->annotext);
  if (doFreeLabel && freeLabel(ts->label) == MS_SUCCESS) {
    free(ts->label);
  }
}

void msCopyTextPath(textPathObj *dst, textPathObj *src) {
  int i;
  *dst = *src;
  if (src->bounds.poly) {
    dst->bounds.poly = msSmallMalloc(sizeof(lineObj));
    dst->bounds.poly->numpoints = src->bounds.poly->numpoints;
    dst->bounds.poly->point =
        msSmallMalloc(src->bounds.poly->numpoints * sizeof(pointObj));
    for (i = 0; i < src->bounds.poly->numpoints; i++) {
      dst->bounds.poly->point[i] = src->bounds.poly->point[i];
    }
  } else {
    dst->bounds.poly = NULL;
  }
  if (dst->numglyphs > 0) {
    dst->glyphs = msSmallMalloc(dst->numglyphs * sizeof(glyphObj));
    for (i = 0; i < dst->numglyphs; i++)
      dst->glyphs[i] = src->glyphs[i];
  }
}

void msCopyTextSymbol(textSymbolObj *dst, textSymbolObj *src) {
  *dst = *src;
  MS_REFCNT_INCR(src->label);
  dst->annotext = msStrdup(src->annotext);
  if (src->textpath) {
    dst->textpath = msSmallMalloc(sizeof(textPathObj));
    msCopyTextPath(dst->textpath, src->textpath);
  }
  if (src->style_bounds) {
    int i;
    dst->style_bounds =
        msSmallCalloc(src->label->numstyles, sizeof(label_bounds *));
    for (i = 0; i < src->label->numstyles; i++) {
      if (src->style_bounds[i]) {
        dst->style_bounds[i] = msSmallMalloc(sizeof(label_bounds));
        copyLabelBounds(dst->style_bounds[i], src->style_bounds[i]);
      }
    }
  }
}

static int labelNeedsDeepCopy(labelObj *label) {
  int i;
  if (label->numbindings > 0)
    return MS_TRUE;
  for (i = 0; i < label->numstyles; i++) {
    if (label->styles[i]->numbindings > 0) {
      return MS_TRUE;
    }
  }
  return MS_FALSE;
}

void msPopulateTextSymbolForLabelAndString(textSymbolObj *ts, labelObj *l,
                                           char *string, double scalefactor,
                                           double resolutionfactor,
                                           label_cache_mode cache) {
  if (cache == duplicate_always) {
    ts->label = msSmallMalloc(sizeof(labelObj));
    initLabel(ts->label);
    msCopyLabel(ts->label, l);
  } else if (cache == duplicate_never) {
    ts->label = l;
    MS_REFCNT_INCR(l);
  } else if (cache == duplicate_if_needed && labelNeedsDeepCopy(l)) {
    ts->label = msSmallMalloc(sizeof(labelObj));
    initLabel(ts->label);
    msCopyLabel(ts->label, l);
  } else {
    ts->label = l;
    MS_REFCNT_INCR(l);
  }
  ts->resolutionfactor = resolutionfactor;
  ts->scalefactor = scalefactor;
  ts->annotext = string; /* we take the ownership of the annotation text */
  ts->rotation = l->angle * MS_DEG_TO_RAD;
}

int msAddLabelGroup(mapObj *map, imageObj *image, layerObj *layer,
                    int classindex, shapeObj *shape, pointObj *point,
                    double featuresize) {
  int l, s, priority;
  labelCacheSlotObj *cacheslot;

  labelCacheMemberObj *cachePtr = NULL;
  layerObj *layerPtr = NULL;
  classObj *classPtr = NULL;
  int numtextsymbols = 0;
  textSymbolObj **textsymbols, *ts;
  int layerindex = layer->index;

  // We cannot use GET_LAYER here because in drawQuery the drawing may happen
  // on a temp layer only.
  layerPtr = layer;
  classPtr = layer->class[classindex];

  if (classPtr->numlabels == 0)
    return MS_SUCCESS; /* not an error just nothing to do */

  /* check that the label intersects the layer mask */
  if (layerPtr->mask) {
    int maskLayerIdx = msGetLayerIndex(map, layerPtr->mask);
    layerObj *maskLayer = GET_LAYER(map, maskLayerIdx);
    unsigned char *alphapixptr;
    if (maskLayer->maskimage &&
        MS_IMAGE_RENDERER(maskLayer->maskimage)->supports_pixel_buffer) {
      rasterBufferObj rb;
      int x, y;
      memset(&rb, 0, sizeof(rasterBufferObj));
      if (MS_UNLIKELY(MS_FAILURE ==
                      MS_IMAGE_RENDERER(maskLayer->maskimage)
                          ->getRasterBufferHandle(maskLayer->maskimage, &rb))) {
        return MS_FAILURE;
      }
      x = MS_NINT(point->x);
      y = MS_NINT(point->y);
      /* Using label repeatdistance, we might have a point with x/y below 0. See
       * #4764 */
      if (x >= 0 && x < (int)rb.width && y >= 0 && y < (int)rb.height) {
        assert(rb.type == MS_BUFFER_BYTE_RGBA);
        alphapixptr = rb.data.rgba.a + rb.data.rgba.row_step * y +
                      rb.data.rgba.pixel_step * x;
        if (!*alphapixptr) {
          /* label point does not intersect mask */
          return MS_SUCCESS;
        }
      } else {
        return MS_SUCCESS; /* label point does not intersect image extent, we
                             cannot know if it intersects mask, so we discard it
                             (#5237)*/
      }
    } else {
      msSetError(MS_MISCERR,
                 "Layer (%s) references references a mask layer, but the "
                 "selected renderer does not support them",
                 "msAddLabelGroup()", layerPtr->name);
      return (MS_FAILURE);
    }
  }

  textsymbols = msSmallMalloc(classPtr->numlabels * sizeof(textSymbolObj *));

  for (l = 0; l < classPtr->numlabels; l++) {
    labelObj *lbl = classPtr->labels[l];
    char *annotext;
    if (msGetLabelStatus(map, layerPtr, shape, lbl) == MS_OFF) {
      continue;
    }
    annotext = msShapeGetLabelAnnotation(layerPtr, shape, lbl);
    if (!annotext) {
      for (s = 0; s < lbl->numstyles; s++) {
        if (lbl->styles[s]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT)
          break; /* we have a "symbol only label, so we shouldn't skip this
                    label */
      }
      if (s == lbl->numstyles) {
        continue; /* no anno text, and no label symbols */
      }
    }
    ts = msSmallMalloc(sizeof(textSymbolObj));
    initTextSymbol(ts);
    msPopulateTextSymbolForLabelAndString(
        ts, lbl, annotext, layerPtr->scalefactor, image->resolutionfactor, 1);

    if (annotext && *annotext && lbl->autominfeaturesize && featuresize > 0) {
      if (MS_UNLIKELY(MS_FAILURE == msComputeTextPath(map, ts))) {
        freeTextSymbol(ts);
        free(ts);
        return MS_FAILURE;
      }
      if (featuresize <
          (ts->textpath->bounds.bbox.maxx - ts->textpath->bounds.bbox.minx)) {
        /* feature is too big to be drawn, skip it */
        freeTextSymbol(ts);
        free(ts);
        continue;
      }
    }
    textsymbols[numtextsymbols] = ts;
    numtextsymbols++;
  }

  if (numtextsymbols == 0) {
    free(textsymbols);
    return MS_SUCCESS;
  }

  /* Validate label priority value and get ref on label cache for it */
  priority =
      classPtr->labels[0]->priority; /* take priority from the first label */
  if (priority < 1)
    priority = 1;
  else if (priority > MS_MAX_LABEL_PRIORITY)
    priority = MS_MAX_LABEL_PRIORITY;

  cacheslot = &(map->labelcache.slots[priority - 1]);

  if (cacheslot->numlabels ==
      cacheslot->cachesize) { /* just add it to the end */
    cacheslot->labels = (labelCacheMemberObj *)realloc(
        cacheslot->labels, sizeof(labelCacheMemberObj) *
                               (cacheslot->cachesize + MS_LABELCACHEINCREMENT));
    MS_CHECK_ALLOC(cacheslot->labels,
                   sizeof(labelCacheMemberObj) *
                       (cacheslot->cachesize + MS_LABELCACHEINCREMENT),
                   MS_FAILURE);
    cacheslot->cachesize += MS_LABELCACHEINCREMENT;
  }

  cachePtr = &(cacheslot->labels[cacheslot->numlabels]);

  cachePtr->layerindex =
      layerindex; /* so we can get back to this *raw* data if necessary */

  cachePtr->classindex = classindex;

  cachePtr->point = *point; /* the actual label point */

  cachePtr->leaderline = NULL;
  cachePtr->leaderbbox = NULL;

  cachePtr->markerid = -1;

  cachePtr->status = MS_FALSE;

  if (layerPtr->type == MS_LAYER_POINT && classPtr->numstyles > 0) {
    /* cache the marker placement, it's already on the map */
    /* TO DO: at the moment only checks the bottom style, perhaps should check
     * all of them */
    /* #2347: after RFC-24 classPtr->styles could be NULL so we check it */
    double w, h;
    if (msGetMarkerSize(map, classPtr->styles[0], &w, &h,
                        layerPtr->scalefactor) != MS_SUCCESS)
      return (MS_FAILURE);

    if (cacheslot->nummarkers ==
        cacheslot->markercachesize) { /* just add it to the end */
      cacheslot->markers = (markerCacheMemberObj *)realloc(
          cacheslot->markers,
          sizeof(markerCacheMemberObj) *
              (cacheslot->cachesize + MS_LABELCACHEINCREMENT));
      MS_CHECK_ALLOC(cacheslot->markers,
                     sizeof(markerCacheMemberObj) *
                         (cacheslot->cachesize + MS_LABELCACHEINCREMENT),
                     MS_FAILURE);
      cacheslot->markercachesize += MS_LABELCACHEINCREMENT;
    }

    cacheslot->markers[cacheslot->nummarkers].bounds.minx = (point->x - .5 * w);
    cacheslot->markers[cacheslot->nummarkers].bounds.miny = (point->y - .5 * h);
    cacheslot->markers[cacheslot->nummarkers].bounds.maxx =
        cacheslot->markers[cacheslot->nummarkers].bounds.minx + (w - 1);
    cacheslot->markers[cacheslot->nummarkers].bounds.maxy =
        cacheslot->markers[cacheslot->nummarkers].bounds.miny + (h - 1);
    cacheslot->markers[cacheslot->nummarkers].id = cacheslot->numlabels;

    cachePtr->markerid = cacheslot->nummarkers;
    cacheslot->nummarkers++;
  }
  cachePtr->textsymbols = textsymbols;
  cachePtr->numtextsymbols = numtextsymbols;

  cacheslot->numlabels++;

  return (MS_SUCCESS);
}

int msAddLabel(mapObj *map, imageObj *image, labelObj *label, int layerindex,
               int classindex, shapeObj *shape, pointObj *point,
               double featuresize, textSymbolObj *ts) {
  int i;
  labelCacheSlotObj *cacheslot;
  labelCacheMemberObj *cachePtr = NULL;
  const char *annotext = NULL;
  char *annotextToFree = NULL;
  layerObj *layerPtr;
  classObj *classPtr;

  layerPtr = GET_LAYER(map, layerindex);
  assert(layerPtr);

  assert(classindex < layerPtr->numclasses);
  classPtr = layerPtr->class[classindex];

  assert(label);

  if (ts)
    annotext = ts->annotext;
  else if (shape) {
    annotextToFree = msShapeGetLabelAnnotation(layerPtr, shape, label);
    annotext = annotextToFree;
  }

  if (!annotext) {
    /* check if we have a labelpnt style */
    for (i = 0; i < label->numstyles; i++) {
      if (label->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT)
        break;
    }
    if (i == label->numstyles) {
      /* label has no text or marker symbols */
      if (ts) {
        freeTextSymbol(ts);
        free(ts);
      }
      return MS_SUCCESS;
    }
  }

  if (classPtr->leader) {
    if (ts && ts->textpath && ts->textpath->absolute) {
      msSetError(MS_MISCERR, "LEADERs are not supported on ANGLE FOLLOW labels",
                 "msAddLabel()");
      return MS_FAILURE;
    }
  }
  /* check that the label intersects the layer mask */

  if (layerPtr->mask) {
    int maskLayerIdx = msGetLayerIndex(map, layerPtr->mask);
    layerObj *maskLayer = GET_LAYER(map, maskLayerIdx);
    unsigned char *alphapixptr;
    if (maskLayer->maskimage &&
        MS_IMAGE_RENDERER(maskLayer->maskimage)->supports_pixel_buffer) {
      rasterBufferObj rb;
      memset(&rb, 0, sizeof(rasterBufferObj));
      if (MS_UNLIKELY(MS_FAILURE ==
                      MS_IMAGE_RENDERER(maskLayer->maskimage)
                          ->getRasterBufferHandle(maskLayer->maskimage, &rb))) {
        msFree(annotextToFree);
        return MS_FAILURE;
      }
      assert(rb.type == MS_BUFFER_BYTE_RGBA);
      if (point) {
        int x = MS_NINT(point->x);
        int y = MS_NINT(point->y);
        /* Using label repeatdistance, we might have a point with x/y below 0.
         * See #4764 */
        if (x >= 0 && x < (int)rb.width && y >= 0 && y < (int)rb.height) {
          alphapixptr = rb.data.rgba.a + rb.data.rgba.row_step * y +
                        rb.data.rgba.pixel_step * x;
          if (!*alphapixptr) {
            /* label point does not intersect mask */
            if (ts) {
              freeTextSymbol(ts);
              free(ts);
            }
            msFree(annotextToFree);
            return MS_SUCCESS;
          }
        } else {
          msFree(annotextToFree);
          return MS_SUCCESS; /* label point does not intersect image extent, we
                                cannot know if it intersects mask, so we discard
                                it (#5237)*/
        }
      } else if (ts && ts->textpath) {
        int i = 0;
        for (i = 0; i < ts->textpath->numglyphs; i++) {
          int x = MS_NINT(ts->textpath->glyphs[i].pnt.x);
          int y = MS_NINT(ts->textpath->glyphs[i].pnt.y);
          if (x >= 0 && x < (int)rb.width && y >= 0 && y < (int)rb.height) {
            alphapixptr = rb.data.rgba.a + rb.data.rgba.row_step * y +
                          rb.data.rgba.pixel_step * x;
            if (!*alphapixptr) {
              freeTextSymbol(ts);
              free(ts);
              msFree(annotextToFree);
              return MS_SUCCESS;
            }
          } else {
            freeTextSymbol(ts);
            free(ts);
            msFree(annotextToFree);
            return MS_SUCCESS; /* label point does not intersect image extent,
                                  we cannot know if it intersects mask, so we
                                  discard it (#5237)*/
          }
        }
      }
    } else {
      msSetError(MS_MISCERR,
                 "Layer (%s) references references a mask layer, but the "
                 "selected renderer does not support them",
                 "msAddLabel()", layerPtr->name);
      msFree(annotextToFree);
      return (MS_FAILURE);
    }
  }

  if (!ts) {
    ts = msSmallMalloc(sizeof(textSymbolObj));
    initTextSymbol(ts);
    msPopulateTextSymbolForLabelAndString(ts, label, annotextToFree,
                                          layerPtr->scalefactor,
                                          image->resolutionfactor, 1);
    // annotextToFree = NULL;
  }

  if (annotext && label->autominfeaturesize && featuresize > 0) {
    if (!ts->textpath) {
      if (MS_UNLIKELY(MS_FAILURE == msComputeTextPath(map, ts)))
        return MS_FAILURE;
    }
    if (!ts->textpath) {
      return MS_FAILURE;
    }
    if (featuresize >
        (ts->textpath->bounds.bbox.maxx - ts->textpath->bounds.bbox.minx)) {
      /* feature is too big to be drawn, skip it */
      freeTextSymbol(ts);
      free(ts);
      return MS_SUCCESS;
    }
  }

  /* Validate label priority value and get ref on label cache for it */
  if (label->priority < 1)
    label->priority = 1;
  else if (label->priority > MS_MAX_LABEL_PRIORITY)
    label->priority = MS_MAX_LABEL_PRIORITY;

  cacheslot = &(map->labelcache.slots[label->priority - 1]);

  if (cacheslot->numlabels ==
      cacheslot->cachesize) { /* just add it to the end */
    cacheslot->labels = (labelCacheMemberObj *)realloc(
        cacheslot->labels, sizeof(labelCacheMemberObj) *
                               (cacheslot->cachesize + MS_LABELCACHEINCREMENT));
    MS_CHECK_ALLOC(cacheslot->labels,
                   sizeof(labelCacheMemberObj) *
                       (cacheslot->cachesize + MS_LABELCACHEINCREMENT),
                   MS_FAILURE);
    cacheslot->cachesize += MS_LABELCACHEINCREMENT;
  }

  cachePtr = &(cacheslot->labels[cacheslot->numlabels]);

  cachePtr->layerindex =
      layerindex; /* so we can get back to this *raw* data if necessary */
  cachePtr->classindex = classindex;
  cachePtr->leaderline = NULL;
  cachePtr->leaderbbox = NULL;

  /* Store the label point or the label path (Bug #1620) */
  if (point) {
    cachePtr->point = *point; /* the actual label point */
  } else {
    assert(ts && ts->textpath && ts->textpath->absolute &&
           ts->textpath->numglyphs > 0);
    /* Use the middle point of the labelpath for mindistance calculations */
    cachePtr->point = ts->textpath->glyphs[ts->textpath->numglyphs / 2].pnt;
  }

  /* copy the label */
  cachePtr->numtextsymbols = 1;
  cachePtr->textsymbols =
      (textSymbolObj **)msSmallMalloc(sizeof(textSymbolObj *));
  cachePtr->textsymbols[0] = ts;
  cachePtr->markerid = -1;

  cachePtr->status = MS_FALSE;

  if (layerPtr->type == MS_LAYER_POINT &&
      classPtr->numstyles >
          0) { /* cache the marker placement, it's already on the map */
    double w, h;

    if (cacheslot->nummarkers ==
        cacheslot->markercachesize) { /* just add it to the end */
      cacheslot->markers = (markerCacheMemberObj *)realloc(
          cacheslot->markers,
          sizeof(markerCacheMemberObj) *
              (cacheslot->cachesize + MS_LABELCACHEINCREMENT));
      MS_CHECK_ALLOC(cacheslot->markers,
                     sizeof(markerCacheMemberObj) *
                         (cacheslot->cachesize + MS_LABELCACHEINCREMENT),
                     MS_FAILURE);
      cacheslot->markercachesize += MS_LABELCACHEINCREMENT;
    }

    i = cacheslot->nummarkers;

    /* TO DO: at the moment only checks the bottom style, perhaps should check
     * all of them */
    /* #2347: after RFC-24 classPtr->styles could be NULL so we check it */
    if (classPtr->styles != NULL) {
      if (msGetMarkerSize(map, classPtr->styles[0], &w, &h,
                          layerPtr->scalefactor) != MS_SUCCESS)
        return (MS_FAILURE);
      assert(point);
      cacheslot->markers[cacheslot->nummarkers].bounds.minx =
          (point->x - .5 * w);
      cacheslot->markers[cacheslot->nummarkers].bounds.miny =
          (point->y - .5 * h);
      cacheslot->markers[cacheslot->nummarkers].bounds.maxx =
          cacheslot->markers[cacheslot->nummarkers].bounds.minx + (w - 1);
      cacheslot->markers[cacheslot->nummarkers].bounds.maxy =
          cacheslot->markers[cacheslot->nummarkers].bounds.miny + (h - 1);
      cacheslot->markers[i].id = cacheslot->numlabels;

      cachePtr->markerid = i;

      cacheslot->nummarkers++;
    }
  }

  cacheslot->numlabels++;

  return (MS_SUCCESS);
}

/*
** Is a label completely in the image, reserving a gutter (in pixels) inside
** image for no labels (effectively making image larger. The gutter can be
** negative in cases where a label has a buffer around it.
*/
static int labelInImage(int width, int height, lineObj *lpoly, rectObj *bounds,
                        int gutter) {
  int j;

  /* do a bbox test first */
  if (bounds->minx >= gutter && bounds->miny >= gutter &&
      bounds->maxx < width - gutter && bounds->maxy < height - gutter) {
    return MS_TRUE;
  }

  if (lpoly) {
    for (j = 1; j < lpoly->numpoints; j++) {
      if (lpoly->point[j].x < gutter)
        return (MS_FALSE);
      if (lpoly->point[j].x >= width - gutter)
        return (MS_FALSE);
      if (lpoly->point[j].y < gutter)
        return (MS_FALSE);
      if (lpoly->point[j].y >= height - gutter)
        return (MS_FALSE);
    }
  } else {
    /* if no poly, then return false as the boundong box intersected */
    return MS_FALSE;
  }

  return (MS_TRUE);
}

void insertRenderedLabelMember(mapObj *map, labelCacheMemberObj *cachePtr) {
  if (map->labelcache.num_rendered_members ==
      map->labelcache.num_allocated_rendered_members) {
    if (map->labelcache.num_rendered_members == 0) {
      map->labelcache.num_allocated_rendered_members = 50;
    } else {
      map->labelcache.num_allocated_rendered_members *= 2;
    }
    map->labelcache.rendered_text_symbols =
        msSmallRealloc(map->labelcache.rendered_text_symbols,
                       map->labelcache.num_allocated_rendered_members *
                           sizeof(labelCacheMemberObj *));
  }
  map->labelcache
      .rendered_text_symbols[map->labelcache.num_rendered_members++] = cachePtr;
}

static inline int testSegmentLabelBBoxIntersection(const rectObj *leaderbbox,
                                                   const pointObj *lp1,
                                                   const pointObj *lp2,
                                                   const label_bounds *test) {
  if (msRectOverlap(leaderbbox, &test->bbox)) {
    if (test->poly) {
      int pp;
      for (pp = 1; pp < test->poly->numpoints; pp++) {
        if (msIntersectSegments(&(test->poly->point[pp - 1]),
                                &(test->poly->point[pp]), lp1,
                                lp2) == MS_TRUE) {
          return (MS_FALSE);
        }
      }
    } else {
      pointObj tp1 = {0}, tp2 = {0};
      tp1.x = test->bbox.minx;
      tp1.y = test->bbox.miny;
      tp2.x = test->bbox.minx;
      tp2.y = test->bbox.maxy;
      if (msIntersectSegments(lp1, lp2, &tp1, &tp2))
        return MS_FALSE;
      tp2.x = test->bbox.maxx;
      tp2.y = test->bbox.miny;
      if (msIntersectSegments(lp1, lp2, &tp1, &tp2))
        return MS_FALSE;
      tp1.x = test->bbox.maxx;
      tp1.y = test->bbox.maxy;
      tp2.x = test->bbox.minx;
      tp2.y = test->bbox.maxy;
      if (msIntersectSegments(lp1, lp2, &tp1, &tp2))
        return MS_FALSE;
      tp2.x = test->bbox.maxx;
      tp2.y = test->bbox.miny;
      if (msIntersectSegments(lp1, lp2, &tp1, &tp2))
        return MS_FALSE;
    }
  }
  return MS_TRUE;
}

int msTestLabelCacheLeaderCollision(mapObj *map, pointObj *lp1, pointObj *lp2) {
  int p;
  rectObj leaderbbox;
  leaderbbox.minx = MS_MIN(lp1->x, lp2->x);
  leaderbbox.maxx = MS_MAX(lp1->x, lp2->x);
  leaderbbox.miny = MS_MIN(lp1->y, lp2->y);
  leaderbbox.maxy = MS_MAX(lp1->y, lp2->y);
  for (p = 0; p < map->labelcache.num_rendered_members; p++) {
    labelCacheMemberObj *curCachePtr = map->labelcache.rendered_text_symbols[p];
    if (msRectOverlap(&leaderbbox, &(curCachePtr->bbox))) {
      /* leaderbbox interesects with the curCachePtr's global bbox */
      int t;
      for (t = 0; t < curCachePtr->numtextsymbols; t++) {
        int s;
        textSymbolObj *ts = curCachePtr->textsymbols[t];
        /* check for intersect with textpath */
        if (ts->textpath &&
            testSegmentLabelBBoxIntersection(
                &leaderbbox, lp1, lp2, &ts->textpath->bounds) == MS_FALSE) {
          return MS_FALSE;
        }
        /* check for intersect with label's labelpnt styles */
        if (ts->style_bounds) {
          for (s = 0; s < ts->label->numstyles; s++) {
            if (ts->label->styles[s]->_geomtransform.type ==
                MS_GEOMTRANSFORM_LABELPOINT) {
              if (testSegmentLabelBBoxIntersection(
                      &leaderbbox, lp1, lp2, ts->style_bounds[s]) == MS_FALSE) {
                return MS_FALSE;
              }
            }
          }
        }
      }
      if (curCachePtr->leaderbbox) {
        if (msIntersectSegments(lp1, lp2, &(curCachePtr->leaderline->point[0]),
                                &(curCachePtr->leaderline->point[1])) ==
            MS_TRUE) {
          return MS_FALSE;
        }
      }
    }
  }
  return MS_TRUE;
}

/* msTestLabelCacheCollisions()
**
** Compares label bounds (in *bounds) against labels already drawn and markers
from cache and
** returns MS_FALSE if it collides with another label, or collides with a
marker.
**
** This function is used by the various msDrawLabelCacheXX() implementations.

int msTestLabelCacheCollisions(mapObj *map, labelCacheMemberObj *cachePtr,
label_bounds *bounds, int current_priority, int current_label);
*/
int msTestLabelCacheCollisions(mapObj *map, labelCacheMemberObj *cachePtr,
                               label_bounds *lb, int current_priority,
                               int current_label) {
  labelCacheObj *labelcache = &(map->labelcache);
  int i, p, ll;

  /*
   * Check against image bounds first
   */
  if (!cachePtr->textsymbols[0]->label->partials) {
    if (labelInImage(map->width, map->height, lb->poly, &lb->bbox,
                     labelcache->gutter) == MS_FALSE) {
      return MS_FALSE;
    }
  }

  /* Compare against all rendered markers from this priority level and higher.
  ** Labels can overlap their own marker and markers from lower priority levels
  */
  for (p = current_priority; p < MS_MAX_LABEL_PRIORITY; p++) {
    labelCacheSlotObj *markerslot;
    markerslot = &(labelcache->slots[p]);

    for (ll = 0; ll < markerslot->nummarkers; ll++) {
      if (!(p == current_priority &&
            current_label ==
                markerslot->markers[ll]
                    .id)) { /* labels can overlap their own marker */
        if (intersectLabelPolygons(NULL, &markerslot->markers[ll].bounds,
                                   lb->poly, &lb->bbox) == MS_TRUE) {
          return MS_FALSE;
        }
      }
    }
  }

  for (p = 0; p < labelcache->num_rendered_members; p++) {
    labelCacheMemberObj *curCachePtr = labelcache->rendered_text_symbols[p];
    if (msRectOverlap(&curCachePtr->bbox, &lb->bbox)) {
      for (i = 0; i < curCachePtr->numtextsymbols; i++) {
        int j;
        textSymbolObj *ts = curCachePtr->textsymbols[i];
        if (ts->textpath &&
            intersectLabelPolygons(ts->textpath->bounds.poly,
                                   &ts->textpath->bounds.bbox, lb->poly,
                                   &lb->bbox) == MS_TRUE) {
          return MS_FALSE;
        }
        if (ts->style_bounds) {
          for (j = 0; j < ts->label->numstyles; j++) {
            if (ts->style_bounds[j] &&
                ts->label->styles[j]->_geomtransform.type ==
                    MS_GEOMTRANSFORM_LABELPOINT) {
              if (intersectLabelPolygons(ts->style_bounds[j]->poly,
                                         &ts->style_bounds[j]->bbox, lb->poly,
                                         &lb->bbox)) {
                return MS_FALSE;
              }
            }
          }
        }
      }
    }
    if (curCachePtr->leaderline) {
      if (testSegmentLabelBBoxIntersection(
              curCachePtr->leaderbbox, &curCachePtr->leaderline->point[0],
              &curCachePtr->leaderline->point[1], lb) == MS_FALSE) {
        return MS_FALSE;
      }
    }
  }
  return MS_TRUE;
}

/* utility function to get the rect of a string outside of a rendering loop,
 i.e. without going through textpath layouts */
int msGetStringSize(mapObj *map, labelObj *label, int size, char *string,
                    rectObj *r) {
  textSymbolObj ts;
  double lsize = label->size;
  initTextSymbol(&ts);
  label->size = size;
  msPopulateTextSymbolForLabelAndString(&ts, label, msStrdup(string), 1, 1, 0);
  if (MS_UNLIKELY(MS_FAILURE == msGetTextSymbolSize(map, &ts, r)))
    return MS_FAILURE;
  label->size = lsize;
  freeTextSymbol(&ts);
  return MS_SUCCESS;
}

int msInitFontSet(fontSetObj *fontset) {
  fontset->filename = NULL;

  /* fontset->fonts = NULL; */
  initHashTable(&(fontset->fonts));

  fontset->numfonts = 0;
  fontset->map = NULL;
  return (0);
}

int msFreeFontSet(fontSetObj *fontset) {
  if (fontset->filename)
    free(fontset->filename);
  fontset->filename = NULL;
  msFreeHashItems(&(fontset->fonts));
  /* fontset->fonts = NULL; */
  fontset->numfonts = 0;

  return (0);
}

int msLoadFontSet(fontSetObj *fontset, mapObj *map) {
  VSILFILE *stream;
  const char *line;
  char *path;
  char szPath[MS_MAXPATHLEN];
  int i;
  int bFullPath = 0;
  const char *realpath;

  if (fontset->numfonts != 0) /* already initialized */
    return (0);

  if (!fontset->filename)
    return (0);

  fontset->map = (mapObj *)map;

  path = msGetPath(fontset->filename);

  /* fontset->fonts = msCreateHashTable(); // create font hash */
  /* if(!fontset->fonts) { */
  /* msSetError(MS_HASHERR, "Error initializing font hash.", "msLoadFontSet()");
   */
  /* return(-1); */
  /* } */

  realpath = msBuildPath(szPath, fontset->map->mappath, fontset->filename);
  if (!realpath) {
    free(path);
    return -1;
  }
  stream = VSIFOpenL(realpath, "rb");
  if (!stream) {
    msSetError(MS_IOERR, "Error opening fontset %s.", "msLoadFontset()",
               fontset->filename);
    free(path);
    return (-1);
  }

  i = 0;
  while ((line = CPLReadLineL(stream)) !=
         NULL) { /* while there's something to load */

    if (line[0] == '#' || line[0] == '\n' || line[0] == '\r' || line[0] == ' ')
      continue; /* skip comments and blank lines */

    char alias[64];
    snprintf(alias, sizeof(alias), "%s", line);
    char *ptr = strpbrk(alias, " \t");
    if (!ptr)
      continue;
    *ptr = '\0';

    const char *file1StartPtr = line + (ptr - alias);
    file1StartPtr++;
    /* Skip leading spaces */
    while (isspace((int)*file1StartPtr))
      file1StartPtr++;

    if (!(*file1StartPtr) || !(*alias))
      continue;

    char file1[MS_PATH_LENGTH];
    snprintf(file1, sizeof(file1), "%s", file1StartPtr);
    /* Remove trailing spaces */
    ptr = file1 + strlen(file1) - 1;
    while (ptr >= file1 && isspace((int)*ptr)) {
      *ptr = '\0';
      --ptr;
    }

    bFullPath = 0;
#if defined(_WIN32) && !defined(__CYGWIN__)
    if (file1[0] == '\\' || (strlen(file1) > 1 && (file1[1] == ':')))
      bFullPath = 1;
#else
    if (file1[0] == '/')
      bFullPath = 1;
#endif

    if (bFullPath) { /* already full path */
      msInsertHashTable(&(fontset->fonts), alias, file1);
    } else {
      char file2[MS_PATH_LENGTH];
      snprintf(file2, sizeof(file2), "%s%s", path, file1);
      /* msInsertHashTable(fontset->fonts, alias, file2); */

      /*
      ** msBuildPath is use here, but if we have to save the fontset file
      ** the msBuildPath must be done everywhere the fonts are used and
      ** removed here.
      */
      msInsertHashTable(&(fontset->fonts), alias,
                        msBuildPath(szPath, fontset->map->mappath, file2));
    }

    i++;
  }

  fontset->numfonts = i;
  VSIFCloseL(stream); /* close the file */
  free(path);

  return (0);
}

int msGetTextSymbolSize(mapObj *map, textSymbolObj *ts, rectObj *r) {
  if (!ts->textpath) {
    if (MS_UNLIKELY(MS_FAILURE == msComputeTextPath(map, ts)))
      return MS_FAILURE;
  }
  if (!ts->textpath)
    return MS_FAILURE;
  *r = ts->textpath->bounds.bbox;
  return MS_SUCCESS;
}
/*
** Note: All these routines assume a reference point at the LL corner of the
*text. GD's
** bitmapped fonts use UL and this is compensated for. Note the rect is relative
*to the
** LL corner of the text to be rendered, this is first line for TrueType fonts.
*/

#define MARKER_SLOP 2

pointObj get_metrics(pointObj *p, int position, textPathObj *tp, int ox, int oy,
                     double rotation, int buffer, label_bounds *bounds) {
  pointObj q = {0}; // initialize
  double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
  double sin_a, cos_a;
  double w, h, x, y;

  w = tp->bounds.bbox.maxx - tp->bounds.bbox.minx;
  h = tp->bounds.bbox.maxy - tp->bounds.bbox.miny;

  switch (position) {
  case MS_UL:
    x1 = -w - ox;
    y1 = -oy;
    break;
  case MS_UC:
    x1 = -(w / 2.0);
    y1 = -oy - MARKER_SLOP;
    break;
  case MS_UR:
    x1 = ox;
    y1 = -oy;
    break;
  case MS_CL:
    x1 = -w - ox - MARKER_SLOP;
    if (oy > 0 && tp->numlines == 1)
      y1 = oy;
    else
      y1 = (h / 2.0);
    break;
  case MS_CC:
    x1 = -(w / 2.0) + ox;
    y1 = (h / 2.0) + oy;
    break;
  case MS_CR:
    x1 = ox + MARKER_SLOP;
    if (oy > 0 && tp->numlines == 1)
      y1 = oy;
    else
      y1 = (h / 2.0);
    break;
  case MS_LL:
    x1 = -w - ox;
    y1 = h + oy;
    break;
  case MS_LC:
    x1 = -(w / 2.0);
    y1 = h + oy + MARKER_SLOP;
    break;
  case MS_LR:
    x1 = ox;
    y1 = h + oy;
    break;
  }

  if (rotation) {
    sin_a = sin(rotation);
    cos_a = cos(rotation);

    x = x1 - tp->bounds.bbox.minx;
    y = tp->bounds.bbox.maxy - y1;
    q.x = p->x + (x * cos_a - (y)*sin_a);
    q.y = p->y - (x * sin_a + (y)*cos_a);

    if (bounds) {
      x2 = x1 - buffer; /* ll */
      y2 = y1 + buffer;
      bounds->poly->point[0].x = p->x + (x2 * cos_a - (-y2) * sin_a);
      bounds->poly->point[0].y = p->y - (x2 * sin_a + (-y2) * cos_a);

      x2 = x1 - buffer; /* ul */
      y2 = y1 - h - buffer;
      bounds->poly->point[1].x = p->x + (x2 * cos_a - (-y2) * sin_a);
      bounds->poly->point[1].y = p->y - (x2 * sin_a + (-y2) * cos_a);

      x2 = x1 + w + buffer; /* ur */
      y2 = y1 - h - buffer;
      bounds->poly->point[2].x = p->x + (x2 * cos_a - (-y2) * sin_a);
      bounds->poly->point[2].y = p->y - (x2 * sin_a + (-y2) * cos_a);

      x2 = x1 + w + buffer; /* lr */
      y2 = y1 + buffer;
      bounds->poly->point[3].x = p->x + (x2 * cos_a - (-y2) * sin_a);
      bounds->poly->point[3].y = p->y - (x2 * sin_a + (-y2) * cos_a);

      bounds->poly->point[4].x = bounds->poly->point[0].x;
      bounds->poly->point[4].y = bounds->poly->point[0].y;
      fastComputeBounds(bounds->poly, &bounds->bbox);
    }
  } else {
    q.x = p->x + x1 - tp->bounds.bbox.minx;
    q.y = p->y + y1 - tp->bounds.bbox.maxy;

    if (bounds) {
      /* no rotation, we only need to return a bbox */
      bounds->poly = NULL;

      bounds->bbox.minx = q.x - buffer;
      bounds->bbox.maxy = q.y + buffer + tp->bounds.bbox.maxy;
      bounds->bbox.maxx = q.x + w + buffer;
      bounds->bbox.miny = bounds->bbox.maxy - h - buffer * 2;
    }
  }
  return (q);
}

int intersectTextSymbol(textSymbolObj *ts, label_bounds *lb) {
  if (ts->textpath && ts->textpath->absolute) {
    if (intersectLabelPolygons(lb->poly, &lb->bbox, ts->textpath->bounds.poly,
                               &ts->textpath->bounds.bbox))
      return MS_TRUE;
  }
  if (ts->style_bounds) {
    int s;
    for (s = 0; s < ts->label->numstyles; s++) {
      if (ts->style_bounds[s] &&
          ts->label->styles[s]->_geomtransform.type ==
              MS_GEOMTRANSFORM_LABELPOINT &&
          intersectLabelPolygons(lb->poly, &lb->bbox, ts->style_bounds[s]->poly,
                                 &ts->style_bounds[s]->bbox))
        return MS_TRUE;
    }
  }
  return MS_FALSE;
}

/*
** Variation on msIntersectPolygons. Label polygons aren't like shapefile
*polygons. They
** have no holes, and often do have overlapping parts (i.e. road symbols).
*/
int intersectLabelPolygons(lineObj *l1, rectObj *r1, lineObj *l2, rectObj *r2) {
  int v1, v2;
  pointObj *point;
  lineObj *p1, *p2, sp1, sp2;
  pointObj pnts1[5], pnts2[5];

  /* STEP 0: check bounding boxes */
  if (!msRectOverlap(r1, r2)) { /* from alans@wunderground.com */
    return (MS_FALSE);
  }
  if (!l1 && !l2)
    return MS_TRUE;

  if (!l1) {
    p1 = &sp1;
    p1->numpoints = 5;
    p1->point = pnts1;
    pnts1[0].x = pnts1[1].x = pnts1[4].x = r1->minx;
    pnts1[2].x = pnts1[3].x = r1->maxx;
    pnts1[0].y = pnts1[3].y = pnts1[4].y = r1->miny;
    pnts1[1].y = pnts1[2].y = r1->maxy;
  } else {
    p1 = l1;
  }
  if (!l2) {
    p2 = &sp2;
    p2->numpoints = 5;
    p2->point = pnts2;
    pnts2[0].x = pnts2[1].x = pnts2[4].x = r2->minx;
    pnts2[2].x = pnts2[3].x = r2->maxx;
    pnts2[0].y = pnts2[3].y = pnts2[4].y = r2->miny;
    pnts2[1].y = pnts2[2].y = r2->maxy;
  } else {
    p2 = l2;
  }
  (void)pnts1[0].x;
  (void)pnts1[0].y;
  (void)pnts1[1].x;
  (void)pnts1[1].y;
  (void)pnts1[2].x;
  (void)pnts1[2].y;
  (void)pnts1[3].x;
  (void)pnts1[3].y;
  (void)pnts1[4].x;
  (void)pnts1[4].y;
  (void)pnts2[0].x;
  (void)pnts2[0].y;
  (void)pnts2[1].x;
  (void)pnts2[1].y;
  (void)pnts2[2].x;
  (void)pnts2[2].y;
  (void)pnts2[3].x;
  (void)pnts2[3].y;
  (void)pnts2[4].x;
  (void)pnts2[4].y;

  /* STEP 1: look for intersecting line segments */
  for (v1 = 1; v1 < p1->numpoints; v1++)
    for (v2 = 1; v2 < p2->numpoints; v2++)
      if (msIntersectSegments(&(p1->point[v1 - 1]), &(p1->point[v1]),
                              &(p2->point[v2 - 1]),
                              &(p2->point[v2])) == MS_TRUE) {
        return (MS_TRUE);
      }

  /* STEP 2: polygon one completely contains two (only need to check one point
   * from each part) */
  point = &(p2->point[0]);
  if (msPointInPolygon(point, p1) ==
      MS_TRUE) /* ok, the point is in a polygon */
    return (MS_TRUE);

  /* STEP 3: polygon two completely contains one (only need to check one point
   * from each part) */
  point = &(p1->point[0]);
  if (msPointInPolygon(point, p2) ==
      MS_TRUE) /* ok, the point is in a polygon */
    return (MS_TRUE);

  return (MS_FALSE);
}

/* For MapScript, exactly the same the msInsertStyle */
int msInsertLabelStyle(labelObj *label, styleObj *style, int nStyleIndex) {
  if (!style) {
    msSetError(MS_CHILDERR, "Can't insert a NULL Style",
               "msInsertLabelStyle()");
    return -1;
  }

  /* Ensure there is room for a new style */
  if (msGrowLabelStyles(label) == NULL) {
    return -1;
  }
  /* Catch attempt to insert past end of styles array */
  else if (nStyleIndex >= label->numstyles) {
    msSetError(MS_CHILDERR, "Cannot insert style beyond index %d",
               "insertLabelStyle()", label->numstyles - 1);
    return -1;
  } else if (nStyleIndex < 0) { /* Insert at the end by default */
    label->styles[label->numstyles] = style;
    MS_REFCNT_INCR(style);
    label->numstyles++;
    return label->numstyles - 1;
  } else {
    /* Move styles existing at the specified nStyleIndex or greater */
    /* to a higher nStyleIndex */
    for (int i = label->numstyles - 1; i >= nStyleIndex; i--) {
      label->styles[i + 1] = label->styles[i];
    }
    label->styles[nStyleIndex] = style;
    MS_REFCNT_INCR(style);
    label->numstyles++;
    return nStyleIndex;
  }
}

/**
 * Move the style up inside the array of styles.
 */
int msMoveLabelStyleUp(labelObj *label, int nStyleIndex) {
  if (label && nStyleIndex < label->numstyles && nStyleIndex > 0) {
    styleObj *psTmpStyle = (styleObj *)malloc(sizeof(styleObj));
    initStyle(psTmpStyle);

    msCopyStyle(psTmpStyle, label->styles[nStyleIndex]);

    msCopyStyle(label->styles[nStyleIndex], label->styles[nStyleIndex - 1]);

    msCopyStyle(label->styles[nStyleIndex - 1], psTmpStyle);

    return (MS_SUCCESS);
  }
  msSetError(MS_CHILDERR, "Invalid index: %d", "msMoveLabelStyleUp()",
             nStyleIndex);
  return (MS_FAILURE);
}

/**
 * Move the style down inside the array of styles.
 */
int msMoveLabelStyleDown(labelObj *label, int nStyleIndex) {
  styleObj *psTmpStyle = NULL;

  if (label && nStyleIndex < label->numstyles - 1 && nStyleIndex >= 0) {
    psTmpStyle = (styleObj *)malloc(sizeof(styleObj));
    initStyle(psTmpStyle);

    msCopyStyle(psTmpStyle, label->styles[nStyleIndex]);

    msCopyStyle(label->styles[nStyleIndex], label->styles[nStyleIndex + 1]);

    msCopyStyle(label->styles[nStyleIndex + 1], psTmpStyle);

    return (MS_SUCCESS);
  }
  msSetError(MS_CHILDERR, "Invalid index: %d", "msMoveLabelStyleDown()",
             nStyleIndex);
  return (MS_FAILURE);
}

/**
 * Delete the style identified by the index and shift
 * styles that follows the deleted style.
 */
int msDeleteLabelStyle(labelObj *label, int nStyleIndex) {
  if (label && nStyleIndex < label->numstyles && nStyleIndex >= 0) {
    if (freeStyle(label->styles[nStyleIndex]) == MS_SUCCESS)
      msFree(label->styles[nStyleIndex]);
    for (int i = nStyleIndex; i < label->numstyles - 1; i++) {
      label->styles[i] = label->styles[i + 1];
    }
    label->styles[label->numstyles - 1] = NULL;
    label->numstyles--;
    return (MS_SUCCESS);
  }
  msSetError(MS_CHILDERR, "Invalid index: %d", "msDeleteLabelStyle()",
             nStyleIndex);
  return (MS_FAILURE);
}

styleObj *msRemoveLabelStyle(labelObj *label, int nStyleIndex) {
  int i;
  styleObj *style;
  if (nStyleIndex < 0 || nStyleIndex >= label->numstyles) {
    msSetError(MS_CHILDERR, "Cannot remove style, invalid nStyleIndex %d",
               "removeLabelStyle()", nStyleIndex);
    return NULL;
  } else {
    style = label->styles[nStyleIndex];
    for (i = nStyleIndex; i < label->numstyles - 1; i++) {
      label->styles[i] = label->styles[i + 1];
    }
    label->styles[label->numstyles - 1] = NULL;
    label->numstyles--;
    MS_REFCNT_DECR(style);
    return style;
  }
}
