CMap.cc 14.6 KB
Newer Older
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1 2 3 4 5 6 7 8
//========================================================================
//
// CMap.cc
//
// Copyright 2001-2003 Glyph & Cog, LLC
//
//========================================================================

9 10 11 12
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
13 14 15
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
16
// Copyright (C) 2008 Koji Otani <sho@bbr.jp>
17
// Copyright (C) 2008, 2009, 2017, 2018 Albert Astals Cid <aacid@kde.org>
18
// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
19
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
Albert Astals Cid's avatar
Albert Astals Cid committed
20
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
21
// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
22 23 24 25 26 27
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

Kristian Høgsberg's avatar
Kristian Høgsberg committed
28 29 30 31 32 33 34 35 36 37 38 39 40
#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "goo/gmem.h"
#include "goo/gfile.h"
#include "goo/GooString.h"
#include "Error.h"
#include "GlobalParams.h"
#include "PSTokenizer.h"
#include "CMap.h"
41
#include "Object.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
42 43 44 45

//------------------------------------------------------------------------

struct CMapVectorEntry {
46
  bool isVector;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
47 48 49 50 51 52 53 54 55 56 57 58
  union {
    CMapVectorEntry *vector;
    CID cid;
  };
};

//------------------------------------------------------------------------

static int getCharFromFile(void *data) {
  return fgetc((FILE *)data);
}

59 60 61 62
static int getCharFromStream(void *data) {
  return ((Stream *)data)->getChar();
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
63 64
//------------------------------------------------------------------------

Albert Astals Cid's avatar
Albert Astals Cid committed
65
CMap *CMap::parse(CMapCache *cache, const GooString *collectionA, Object *obj) {
66 67 68 69 70 71 72 73 74 75 76 77
  CMap *cMap;
  GooString *cMapNameA;

  if (obj->isName()) {
    cMapNameA = new GooString(obj->getName());
    if (!(cMap = globalParams->getCMap(collectionA, cMapNameA))) {
      error(errSyntaxError, -1,
	    "Unknown CMap '{0:t}' for character collection '{1:t}'",
	    cMapNameA, collectionA);
    }
    delete cMapNameA;
  } else if (obj->isStream()) {
78
    if (!(cMap = CMap::parse(nullptr, collectionA, obj->getStream()))) {
79 80 81 82
      error(errSyntaxError, -1, "Invalid CMap in Type 0 font");
    }
  } else {
    error(errSyntaxError, -1, "Invalid Encoding in Type 0 font");
83
    return nullptr;
84 85 86 87
  }
  return cMap;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
88 89
CMap *CMap::parse(CMapCache *cache, const GooString *collectionA,
		  const GooString *cMapNameA) {
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
  FILE *f;
  CMap *cMap;

  if (!(f = globalParams->findCMapFile(collectionA, cMapNameA))) {

    // Check for an identity CMap.
    if (!cMapNameA->cmp("Identity") || !cMapNameA->cmp("Identity-H")) {
      return new CMap(collectionA->copy(), cMapNameA->copy(), 0);
    }
    if (!cMapNameA->cmp("Identity-V")) {
      return new CMap(collectionA->copy(), cMapNameA->copy(), 1);
    }

    error(errSyntaxError, -1,
	  "Couldn't find '{0:t}' CMap file for '{1:t}' collection",
	  cMapNameA, collectionA);
106
    return nullptr;
107 108 109 110 111 112 113 114 115 116
  }

  cMap = new CMap(collectionA->copy(), cMapNameA->copy());
  cMap->parse2(cache, &getCharFromFile, f);

  fclose(f);

  return cMap;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
117
CMap *CMap::parse(CMapCache *cache, const GooString *collectionA, Stream *str) {
118
  CMap *cMap = new CMap(collectionA->copy(), nullptr);
Albert Astals Cid's avatar
Albert Astals Cid committed
119 120
  Object obj1 = str->getDict()->lookup("UseCMap");
  if (!obj1.isNull()) {
121 122 123 124 125 126 127 128 129
    cMap->useCMap(cache, &obj1);
  }

  str->reset();
  cMap->parse2(cache, &getCharFromStream, str);
  str->close();
  return cMap;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
130 131
CMap *CMap::parse(CMapCache *cache, const GooString *collectionA,
		  const GooString *cMapNameA, Stream *stream) {
132
  FILE *f = nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
133 134 135 136
  CMap *cmap;
  PSTokenizer *pst;
  char tok1[256], tok2[256], tok3[256];
  int n1, n2, n3;
137
  unsigned int start, end, code;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
138

139 140 141 142 143
  if (stream) {
    stream->reset();
    pst = new PSTokenizer(&getCharFromStream, stream);
  } else {
    if (!(f = globalParams->findCMapFile(collectionA, cMapNameA))) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
144

145 146 147 148 149 150 151
      // Check for an identity CMap.
      if (!cMapNameA->cmp("Identity") || !cMapNameA->cmp("Identity-H")) {
        return new CMap(collectionA->copy(), cMapNameA->copy(), 0);
      }
      if (!cMapNameA->cmp("Identity-V")) {
        return new CMap(collectionA->copy(), cMapNameA->copy(), 1);
      }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
152

153 154
      error(errSyntaxError, -1, "Couldn't find '{0:t}' CMap file for '{1:t}' collection",
	    cMapNameA, collectionA);
155
      return nullptr;
156 157
    }
    pst = new PSTokenizer(&getCharFromFile, f);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
158 159 160 161 162 163 164 165 166 167 168 169 170 171
  }

  cmap = new CMap(collectionA->copy(), cMapNameA->copy());

  pst->getToken(tok1, sizeof(tok1), &n1);
  while (pst->getToken(tok2, sizeof(tok2), &n2)) {
    if (!strcmp(tok2, "usecmap")) {
      if (tok1[0] == '/') {
	cmap->useCMap(cache, tok1 + 1);
      }
      pst->getToken(tok1, sizeof(tok1), &n1);
    } else if (!strcmp(tok1, "/WMode")) {
      cmap->wMode = atoi(tok2);
      pst->getToken(tok1, sizeof(tok1), &n1);
172 173 174 175 176 177 178
    } else if (!strcmp(tok2, "begincidchar")) {
      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
	if (!strcmp(tok1, "endcidchar")) {
	  break;
	}
	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
	    !strcmp(tok2, "endcidchar")) {
179
	  error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
180 181 182 183
	  break;
	}
	if (!(tok1[0] == '<' && tok1[n1 - 1] == '>' &&
	      n1 >= 4 && (n1 & 1) == 0)) {
184
	  error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
185 186 187 188
	  continue;
	}
	tok1[n1 - 1] = '\0';
	if (sscanf(tok1 + 1, "%x", &code) != 1) {
189
	  error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
190 191 192 193 194 195
	  continue;
	}
	n1 = (n1 - 2) / 2;
	cmap->addCIDs(code, code, n1, (CID)atoi(tok2));
      }
      pst->getToken(tok1, sizeof(tok1), &n1);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
196 197 198 199 200 201 202 203 204
    } else if (!strcmp(tok2, "begincidrange")) {
      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
	if (!strcmp(tok1, "endcidrange")) {
	  break;
	}
	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
	    !strcmp(tok2, "endcidrange") ||
	    !pst->getToken(tok3, sizeof(tok3), &n3) ||
	    !strcmp(tok3, "endcidrange")) {
205
	  error(errSyntaxError, -1, "Illegal entry in cidrange block in CMap");
Kristian Høgsberg's avatar
Kristian Høgsberg committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
	  break;
	}
	if (tok1[0] == '<' && tok2[0] == '<' &&
	    n1 == n2 && n1 >= 4 && (n1 & 1) == 0) {
	  tok1[n1 - 1] = tok2[n1 - 1] = '\0';
	  sscanf(tok1 + 1, "%x", &start);
	  sscanf(tok2 + 1, "%x", &end);
	  n1 = (n1 - 2) / 2;
	  cmap->addCIDs(start, end, n1, (CID)atoi(tok3));
	}
      }
      pst->getToken(tok1, sizeof(tok1), &n1);
    } else {
      strcpy(tok1, tok2);
    }
  }
  delete pst;

224 225 226
  if (f) {
    fclose(f);
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
227 228 229 230

  return cmap;
}

231 232 233 234
void CMap::parse2(CMapCache *cache, int (*getCharFunc)(void *), void *data) {
  PSTokenizer *pst;
  char tok1[256], tok2[256], tok3[256];
  int n1, n2, n3;
235
  unsigned int start = 0, end = 0, code;
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

  pst = new PSTokenizer(getCharFunc, data);
  pst->getToken(tok1, sizeof(tok1), &n1);
  while (pst->getToken(tok2, sizeof(tok2), &n2)) {
    if (!strcmp(tok2, "usecmap")) {
      if (tok1[0] == '/') {
	useCMap(cache, tok1 + 1);
      }
      pst->getToken(tok1, sizeof(tok1), &n1);
    } else if (!strcmp(tok1, "/WMode")) {
      wMode = atoi(tok2);
      pst->getToken(tok1, sizeof(tok1), &n1);
    } else if (!strcmp(tok2, "begincidchar")) {
      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
	if (!strcmp(tok1, "endcidchar")) {
	  break;
	}
	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
	    !strcmp(tok2, "endcidchar")) {
	  error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
	  break;
	}
	if (!(tok1[0] == '<' && tok1[n1 - 1] == '>' &&
	      n1 >= 4 && (n1 & 1) == 0)) {
	  error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
	  continue;
	}
	tok1[n1 - 1] = '\0';
	if (sscanf(tok1 + 1, "%x", &code) != 1) {
	  error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
	  continue;
	}
	n1 = (n1 - 2) / 2;
	addCIDs(code, code, n1, (CID)atoi(tok2));
      }
      pst->getToken(tok1, sizeof(tok1), &n1);
    } else if (!strcmp(tok2, "begincidrange")) {
      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
	if (!strcmp(tok1, "endcidrange")) {
	  break;
	}
	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
	    !strcmp(tok2, "endcidrange") ||
	    !pst->getToken(tok3, sizeof(tok3), &n3) ||
	    !strcmp(tok3, "endcidrange")) {
	  error(errSyntaxError, -1, "Illegal entry in cidrange block in CMap");
	  break;
	}
	if (tok1[0] == '<' && tok2[0] == '<' &&
	    n1 == n2 && n1 >= 4 && (n1 & 1) == 0) {
	  tok1[n1 - 1] = tok2[n1 - 1] = '\0';
	  sscanf(tok1 + 1, "%x", &start);
	  sscanf(tok2 + 1, "%x", &end);
	  n1 = (n1 - 2) / 2;
	  addCIDs(start, end, n1, (CID)atoi(tok3));
	}
      }
      pst->getToken(tok1, sizeof(tok1), &n1);
    } else {
      strcpy(tok1, tok2);
    }
  }
  delete pst;
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
301 302 303 304 305
CMap::CMap(GooString *collectionA, GooString *cMapNameA) {
  int i;

  collection = collectionA;
  cMapName = cMapNameA;
306
  isIdent = false;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
307
  wMode = 0;
308
  vector = (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
309
  for (i = 0; i < 256; ++i) {
310
    vector[i].isVector = false;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
311 312 313 314 315 316 317 318
    vector[i].cid = 0;
  }
  refCnt = 1;
}

CMap::CMap(GooString *collectionA, GooString *cMapNameA, int wModeA) {
  collection = collectionA;
  cMapName = cMapNameA;
319
  isIdent = true;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
320
  wMode = wModeA;
321
  vector = nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
322 323 324 325 326 327 328 329
  refCnt = 1;
}

void CMap::useCMap(CMapCache *cache, char *useName) {
  GooString *useNameStr;
  CMap *subCMap;

  useNameStr = new GooString(useName);
330 331 332 333 334
  // if cache is non-NULL, we already have a lock, and we can use
  // CMapCache::getCMap() directly; otherwise, we need to use
  // GlobalParams::getCMap() in order to acqure the lock need to use
  // GlobalParams::getCMap
  if (cache) {
335
    subCMap = cache->getCMap(collection, useNameStr, nullptr);
336 337 338
  } else {
    subCMap = globalParams->getCMap(collection, useNameStr);
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
339 340 341 342
  delete useNameStr;
  if (!subCMap) {
    return;
  }
343 344 345 346
  isIdent = subCMap->isIdent;
  if (subCMap->vector) {
    copyVector(vector, subCMap->vector);
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
347 348 349
  subCMap->decRefCnt();
}

350 351 352 353 354 355 356 357 358 359 360 361 362 363
void CMap::useCMap(CMapCache *cache, Object *obj) {
  CMap *subCMap;

  subCMap = CMap::parse(cache, collection, obj);
  if (!subCMap) {
    return;
  }
  isIdent = subCMap->isIdent;
  if (subCMap->vector) {
    copyVector(vector, subCMap->vector);
  }
  subCMap->decRefCnt();
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
364 365 366 367 368 369
void CMap::copyVector(CMapVectorEntry *dest, CMapVectorEntry *src) {
  int i, j;

  for (i = 0; i < 256; ++i) {
    if (src[i].isVector) {
      if (!dest[i].isVector) {
370
	dest[i].isVector = true;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
371
	dest[i].vector =
372
	  (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
373
	for (j = 0; j < 256; ++j) {
374
	  dest[i].vector[j].isVector = false;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
375 376 377 378 379 380
	  dest[i].vector[j].cid = 0;
	}
      }
      copyVector(dest[i].vector, src[i].vector);
    } else {
      if (dest[i].isVector) {
381
	error(errSyntaxError, -1, "Collision in usecmap");
Kristian Høgsberg's avatar
Kristian Høgsberg committed
382 383 384 385 386 387 388
      } else {
	dest[i].cid = src[i].cid;
      }
    }
  }
}

389
void CMap::addCIDs(unsigned int start, unsigned int end, unsigned int nBytes, CID firstCID) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
390 391 392
  CMapVectorEntry *vec;
  CID cid;
  int byte;
393
  unsigned int i, j;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
394

395 396 397 398
  if (nBytes > 4) {
    error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap");
    return;
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
399 400 401 402
  vec = vector;
  for (i = nBytes - 1; i >= 1; --i) {
    byte = (start >> (8 * i)) & 0xff;
    if (!vec[byte].isVector) {
403
      vec[byte].isVector = true;
404 405 406
      vec[byte].vector =
	  (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry));
      for (j = 0; j < 256; ++j) {
407
	vec[byte].vector[j].isVector = false;
408 409
	vec[byte].vector[j].cid = 0;
      }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
410 411 412 413 414 415
    }
    vec = vec[byte].vector;
  }
  cid = firstCID;
  for (byte = (int)(start & 0xff); byte <= (int)(end & 0xff); ++byte) {
    if (vec[byte].isVector) {
416
      error(errSyntaxError, -1,
417
	    "Invalid CID ({0:ux} - {1:ux} [{2:ud} bytes]) in CMap",
418
	    start, end, nBytes);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    } else {
      vec[byte].cid = cid;
    }
    ++cid;
  }
}

CMap::~CMap() {
  delete collection;
  delete cMapName;
  if (vector) {
    freeCMapVector(vector);
  }
}

void CMap::freeCMapVector(CMapVectorEntry *vec) {
  int i;

  for (i = 0; i < 256; ++i) {
    if (vec[i].isVector) {
      freeCMapVector(vec[i].vector);
    }
  }
  gfree(vec);
}

void CMap::incRefCnt() {
  ++refCnt;
}

void CMap::decRefCnt() {
450
  if (--refCnt == 0) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
451 452 453 454
    delete this;
  }
}

455
bool CMap::match(const GooString *collectionA, const GooString *cMapNameA) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
456 457 458
  return !collection->cmp(collectionA) && !cMapName->cmp(cMapNameA);
}

459
CID CMap::getCID(const char *s, int len, CharCode *c, int *nUsed) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
460
  CMapVectorEntry *vec;
461
  CharCode cc;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
462 463
  int n, i;

464 465
  vec = vector;
  cc = 0;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
466
  n = 0;
467
  while (vec && n < len) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
468
    i = s[n++] & 0xff;
469
    cc = (cc << 8) | i;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
470
    if (!vec[i].isVector) {
471
      *c = cc;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
472 473 474 475 476
      *nUsed = n;
      return vec[i].cid;
    }
    vec = vec[i].vector;
  }
477 478 479 480 481 482 483 484 485
  if (isIdent && len >= 2) {
    // identity CMap
    *nUsed = 2;
    *c = cc = ((s[0] & 0xff) << 8) + (s[1] & 0xff);
    return cc;
  }
  *nUsed = 1;
  *c = s[0] & 0xff;
  return 0;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
486 487
}

488 489
void CMap::setReverseMapVector(unsigned int startCode, CMapVectorEntry *vec,
 unsigned int *rmap, unsigned int rmapSize, unsigned int ncand) {
490 491
  int i;

492
  if (vec == nullptr) return;
493 494 495 496 497
  for (i = 0;i < 256;i++) {
    if (vec[i].isVector) {
      setReverseMapVector((startCode+i) << 8,
	  vec[i].vector,rmap,rmapSize,ncand);
    } else {
498
      unsigned int cid = vec[i].cid;
499 500

      if (cid < rmapSize) {
501
	unsigned int cand;
502 503

	for (cand = 0;cand < ncand;cand++) {
504 505
	  unsigned int code = startCode+i;
	  unsigned int idx = cid*ncand+cand;
506 507 508 509 510 511 512 513 514 515 516 517
	  if (rmap[idx] == 0) {
	    rmap[idx] = code;
	    break;
	  } else if (rmap[idx] == code) {
	    break;
	  }
	}
      }
    }
  }
}

518
void CMap::setReverseMap(unsigned int *rmap, unsigned int rmapSize, unsigned int ncand) {
519 520 521
  setReverseMapVector(0,vector,rmap,rmapSize,ncand);
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
522 523 524 525 526 527
//------------------------------------------------------------------------

CMapCache::CMapCache() {
  int i;

  for (i = 0; i < cMapCacheSize; ++i) {
528
    cache[i] = nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
529 530 531 532 533 534 535 536 537 538 539 540 541
  }
}

CMapCache::~CMapCache() {
  int i;

  for (i = 0; i < cMapCacheSize; ++i) {
    if (cache[i]) {
      cache[i]->decRefCnt();
    }
  }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
542
CMap *CMapCache::getCMap(const GooString *collection, const GooString *cMapName, Stream *stream) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
  CMap *cmap;
  int i, j;

  if (cache[0] && cache[0]->match(collection, cMapName)) {
    cache[0]->incRefCnt();
    return cache[0];
  }
  for (i = 1; i < cMapCacheSize; ++i) {
    if (cache[i] && cache[i]->match(collection, cMapName)) {
      cmap = cache[i];
      for (j = i; j >= 1; --j) {
	cache[j] = cache[j - 1];
      }
      cache[0] = cmap;
      cmap->incRefCnt();
      return cmap;
    }
  }
561
  if ((cmap = CMap::parse(this, collection, cMapName, stream))) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
562 563 564 565 566 567 568 569 570 571
    if (cache[cMapCacheSize - 1]) {
      cache[cMapCacheSize - 1]->decRefCnt();
    }
    for (j = cMapCacheSize - 1; j >= 1; --j) {
      cache[j] = cache[j - 1];
    }
    cache[0] = cmap;
    cmap->incRefCnt();
    return cmap;
  }
572
  return nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
573
}