emf-converter 4.8.0 → 4.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -16402,8 +16402,8 @@ function midtoneGamma(t) {
16402
16402
  low = high;
16403
16403
  high = (high + 100) / 2;
16404
16404
  }
16405
- gamma = Math.abs(t) === 100 ? 10 : Math.min(10, gamma + (Math.abs(t) - low) / (high - low));
16406
- return Math.fround(t < 0 ? gamma : 1 / gamma);
16405
+ gamma = Math.abs(t) === 100 ? 10 : Math.min(10, truncF32(gamma + truncF32(truncF32(Math.abs(t) - low) / truncF32(high - low))));
16406
+ return t < 0 ? truncF32(gamma) : truncF32(1 / truncF32(gamma));
16407
16407
  }
16408
16408
  function brightnessContrastLut(brightness, contrast) {
16409
16409
  const gain = contrast >= 100 ? 1e6 : contrast >= 0 ? 100 / (100 - contrast) : (100 + contrast) / 100;
@@ -16420,6 +16420,23 @@ function curveAdjustmentLut(adjustment, intensity) {
16420
16420
  return nativeCurveLookup(adjustment, clamp(Math.trunc(intensity), min, max)).slice();
16421
16421
  }
16422
16422
  function levelsLut(highlight, midtone, shadow) {
16423
+ if (highlight !== shadow) {
16424
+ const forward = highlight > shadow;
16425
+ const lo = truncF32((forward ? shadow : highlight) * 255 / 100);
16426
+ const hi = truncF32((forward ? highlight : shadow) * 255 / 100);
16427
+ const scale = truncF32(1 / truncF32(hi - lo));
16428
+ const exponent2 = midtoneGamma(midtone);
16429
+ const lut = new Uint8Array(256);
16430
+ for (let v = 0; v < 256; v++) {
16431
+ let level = clamp(truncF32(truncF32(v - lo) * scale), 0, 1);
16432
+ if (midtone !== 0) {
16433
+ level = truncF32(level ** exponent2);
16434
+ }
16435
+ const out = forward ? truncF32(255 * level) : truncF32(255 - truncF32(255 * level));
16436
+ lut[v] = clamp(Math.floor(out + 0.5), 0, 255);
16437
+ }
16438
+ return lut;
16439
+ }
16423
16440
  const base = shadow * 255 / 100 - (highlight === shadow && shadow < 50 ? 1 : 0);
16424
16441
  const span = highlight === shadow ? 1 : (highlight - shadow) * 255 / 100;
16425
16442
  const exponent = midtoneGamma(midtone);
@@ -16452,14 +16469,6 @@ function applyColorMatrix(src, m) {
16452
16469
  }
16453
16470
  return out;
16454
16471
  }
16455
- function hslToRgb(h, s, l) {
16456
- const c = (1 - Math.abs(2 * l - 1)) * s;
16457
- const hp = (h % 360 + 360) % 360 / 60;
16458
- const x = c * (1 - Math.abs(hp % 2 - 1));
16459
- const m = l - c / 2;
16460
- const [r, g, b] = hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];
16461
- return [r + m, g + m, b + m];
16462
- }
16463
16472
  var HUE_SEXTANT = 43;
16464
16473
  var HUE_CIRCLE = 255;
16465
16474
  function gdipHue(r, g, b) {
@@ -16482,29 +16491,30 @@ function gdipHue(r, g, b) {
16482
16491
  function applyHueSaturationLightness(src, hue, saturation, lightness) {
16483
16492
  const out = new Uint8ClampedArray(src);
16484
16493
  const shift = Math.round(hue * HUE_CIRCLE / 360);
16485
- const sMul = 1 + saturation / 100;
16494
+ const satScale = 65536 + Math.floor(saturation * 65536 / 100);
16495
+ const lightOffset = Math.round(255 * Math.floor(lightness * 65536 / 100) / 65536);
16486
16496
  for (let i = 0; i < src.length; i += 4) {
16487
16497
  const r = src[i];
16488
16498
  const g = src[i + 1];
16489
16499
  const b = src[i + 2];
16490
16500
  const max = Math.max(r, g, b);
16491
16501
  const min = Math.min(r, g, b);
16492
- const l = clamp(Math.round(((max + min >> 1) * 100 + 255 * lightness) / 100), 0, 255);
16502
+ const l = clamp((max + min >> 1) + lightOffset, 0, 255);
16493
16503
  if (max === min) {
16494
16504
  out[i] = out[i + 1] = out[i + 2] = l;
16495
16505
  continue;
16496
16506
  }
16497
16507
  const sum = max + min;
16498
- const s = Math.fround(clamp((max - min) / (sum <= 255 ? sum : 510 - sum) * sMul, 0, 1));
16499
- const hi = l <= 127 ? l * (1 + s) : l + s * (255 - l);
16500
- const m1 = l <= 127 ? 2 * l - Math.trunc(hi) : Math.round(2 * l - hi);
16501
- const m2 = 2 * l - m1;
16508
+ const sat2 = clamp(Math.round(Math.floor((max - min) * 255 / (sum <= 255 ? sum : 510 - sum)) * satScale / 65536), 0, 255);
16509
+ const hi = l <= 127 ? Math.floor(l * (255 + sat2) / 255) : l + sat2 - Math.floor(l * sat2 / 255);
16510
+ const m1 = 2 * l - hi;
16511
+ const m2 = hi;
16502
16512
  const index = ((gdipHue(r, g, b) + shift) % HUE_CIRCLE + HUE_CIRCLE) % HUE_CIRCLE;
16503
16513
  const q = index + Math.floor((index + 41) / 85);
16504
16514
  const sextant = Math.min(5, Math.floor(q / HUE_SEXTANT));
16505
- const f = (q - sextant * HUE_SEXTANT) / HUE_SEXTANT;
16506
- const up = Math.trunc(m1 + (m2 - m1) * f);
16507
- const down = Math.trunc(m1 + (m2 - m1) * (1 - f));
16515
+ const position = q - sextant * HUE_SEXTANT;
16516
+ const up = m1 + Math.floor((m2 - m1) * position / HUE_SEXTANT);
16517
+ const down = m1 + Math.floor((m2 - m1) * (HUE_SEXTANT - position) / HUE_SEXTANT);
16508
16518
  const [nr, ng, nb] = [
16509
16519
  [m2, up, m1],
16510
16520
  [down, m2, m1],
@@ -16519,28 +16529,40 @@ function applyHueSaturationLightness(src, hue, saturation, lightness) {
16519
16529
  }
16520
16530
  return out;
16521
16531
  }
16522
- var LUMA_709 = [0.2126, 0.7152, 0.0722];
16523
- var TINT_CHROMA_SCALE = 0.985;
16524
16532
  function applyTint(src, hue, amount) {
16525
16533
  const index = (Math.round(hue * 255 / 360) + 256) % 256;
16526
16534
  const q = (index + Math.floor((index + 41) / 85)) % 258;
16527
- const tint = hslToRgb(q * 60 / 43, 1, 0.5);
16528
- const ty = tint[0] * LUMA_709[0] + tint[1] * LUMA_709[1] + tint[2] * LUMA_709[2];
16529
- const chroma = tint.map((c) => (c - ty) * TINT_CHROMA_SCALE);
16530
- const a = amount / 100;
16535
+ const sextant = Math.min(5, Math.floor(q / HUE_SEXTANT));
16536
+ const position = q - sextant * HUE_SEXTANT;
16537
+ const up = Math.floor(TINT_FULL * position / HUE_SEXTANT);
16538
+ const down = Math.floor(TINT_FULL * (HUE_SEXTANT - position) / HUE_SEXTANT);
16539
+ const [tr, tg, tb] = [
16540
+ [TINT_FULL, up, 0],
16541
+ [down, TINT_FULL, 0],
16542
+ [0, TINT_FULL, up],
16543
+ [0, down, TINT_FULL],
16544
+ [up, 0, TINT_FULL],
16545
+ [TINT_FULL, 0, down]
16546
+ ][sextant];
16547
+ const weight = Math.sign(amount) * Math.floor((255 * Math.abs(amount) + 49) / 100);
16548
+ const original = 255 - weight;
16531
16549
  const out = new Uint8ClampedArray(src);
16532
16550
  for (let i = 0; i < src.length; i += 4) {
16533
16551
  const r = src[i];
16534
16552
  const g = src[i + 1];
16535
16553
  const b = src[i + 2];
16536
- const y = r * LUMA_709[0] + g * LUMA_709[1] + b * LUMA_709[2];
16537
- const v = Math.max(r, g, b);
16538
- out[i] = Math.round((1 - a) * r + a * (y + v * chroma[0]));
16539
- out[i + 1] = Math.round((1 - a) * g + a * (y + v * chroma[1]));
16540
- out[i + 2] = Math.round((1 - a) * b + a * (y + v * chroma[2]));
16554
+ const u = weight * Math.max(r, g, b) >> 8;
16555
+ const x0 = original * r + tr * u;
16556
+ const x1 = original * g + tg * u;
16557
+ const x2 = original * b + tb * u;
16558
+ const base = 54 * r + 183 * g + 19 * b - (54 * (x0 >> 8) + 183 * (x1 >> 8) + 19 * (x2 >> 8));
16559
+ out[i] = base + x0 >> 8;
16560
+ out[i + 1] = base + x1 >> 8;
16561
+ out[i + 2] = base + x2 >> 8;
16541
16562
  }
16542
16563
  return out;
16543
16564
  }
16565
+ var TINT_FULL = 254;
16544
16566
  function applyRedEyeCorrection(src, width, height, areas) {
16545
16567
  const out = new Uint8ClampedArray(src);
16546
16568
  for (const a of areas) {
@@ -20540,8 +20562,7 @@ function resampleImage(rgba, width, height, spec, surface) {
20540
20562
  const axisAligned = m[1] === 0 && m[2] === 0;
20541
20563
  if (hq && axisAligned) {
20542
20564
  const ox = m[0] * spec.srcX + m[4];
20543
- const oy = m[3] * spec.srcY + m[5];
20544
- m = [m[0], 0, 0, m[3], m[4] + mirroredOrigin(ox) - ox, m[5] + mirroredOrigin(oy) - oy];
20565
+ m = [m[0], 0, 0, m[3], m[4] + mirroredOrigin(ox) - ox, m[5]];
20545
20566
  }
20546
20567
  const inv2 = invert2(m);
20547
20568
  if (!inv2) {
@@ -23354,6 +23375,21 @@ var nestedReplayer = null;
23354
23375
  function registerNestedMetafileReplayer(fn) {
23355
23376
  nestedReplayer = fn;
23356
23377
  }
23378
+ function inclusiveNestedMapping(toDevice, srcX, srcY, srcW, srcH) {
23379
+ const [a, b, c, d, e, f] = toDevice;
23380
+ const lengthU = Math.hypot(a, b) * srcW;
23381
+ const lengthV = Math.hypot(c, d) * srcH;
23382
+ if (!(srcW > 1 && srcH > 1 && lengthU > 1 && lengthV > 1)) {
23383
+ return toDevice;
23384
+ }
23385
+ const ku = (lengthU - 1) / lengthU * (srcW / (srcW - 1));
23386
+ const kv = (lengthV - 1) / lengthV * (srcH / (srcH - 1));
23387
+ const na = a * ku;
23388
+ const nb = b * ku;
23389
+ const nc = c * kv;
23390
+ const nd = d * kv;
23391
+ return [na, nb, nc, nd, e + (a - na) * srcX + (c - nc) * srcY, f + (b - nb) * srcX + (d - nd) * srcY];
23392
+ }
23357
23393
  function drawNestedMetafile(rCtx, img, cached, source) {
23358
23394
  const depth = rCtx.nestingDepth ?? 0;
23359
23395
  if (!nestedReplayer || !source || !img.data || source.unit !== UNIT_PIXEL || depth >= MAX_NESTED_METAFILE_DEPTH) {
@@ -23367,9 +23403,12 @@ function drawNestedMetafile(rCtx, img, cached, source) {
23367
23403
  if (!size) {
23368
23404
  return false;
23369
23405
  }
23370
- const toCanvas = mulMatrix(
23371
- [1, 0, 0, 1, -0.5, -0.5],
23372
- mulMatrix(mulMatrix(plusWorldMatrix(rCtx), source.toWorld(srcX, srcY, srcW, srcH)), [1, 0, 0, 1, 0.5, 0.5])
23406
+ const toCanvas = inclusiveNestedMapping(
23407
+ mulMatrix(plusWorldMatrix(rCtx), source.toWorld(srcX, srcY, srcW, srcH)),
23408
+ srcX,
23409
+ srcY,
23410
+ srcW,
23411
+ srcH
23373
23412
  );
23374
23413
  const bytes = img.data.slice(0);
23375
23414
  const caches = cached && cached.kind === "metafile" ? cached.caches : void 0;
@@ -29188,9 +29227,9 @@ function perpendicularVectors(width, dx0, dy0) {
29188
29227
  const vx = rounded[0], vy = rounded[1];
29189
29228
  return { v: flip ? [-vx, -vy] : [vx, vy], ray: flip ? [-x, -y] : [x, y] };
29190
29229
  }
29191
- function squareExtension(width, dx, dy) {
29230
+ function squareExtension(width, dx, dy, scale = 1) {
29192
29231
  const len = Math.hypot(dx, dy);
29193
- const r = width / 2;
29232
+ const r = width / 2 * scale;
29194
29233
  return [Math.floor(dx / len * r + 0.5), Math.floor(dy / len * r + 0.5)];
29195
29234
  }
29196
29235
  function turnSign(ax, ay, bx, by) {
@@ -29229,7 +29268,7 @@ var Outliner = class {
29229
29268
  const [L, R] = drawVertices(this.pen, d[0], d[1]);
29230
29269
  const perpendicular = curve && drawDir ? drawDir : [dx, dy];
29231
29270
  const vectors = perpendicularVectors(this.opts.width, perpendicular[0], perpendicular[1]);
29232
- return { dx, dy, L, R, v: vectors.v, vRaw: vectors.ray, e: squareExtension(this.opts.width, perpendicular[0], perpendicular[1]), curveEnd: !!drawDir, tangentLength: drawDir ? drawDir[0] * drawDir[0] + drawDir[1] * drawDir[1] : 0, curve };
29271
+ return { dx, dy, L, R, v: vectors.v, vRaw: vectors.ray, e: squareExtension(this.opts.width, perpendicular[0], perpendicular[1]), pe: [perpendicular[0], perpendicular[1]], curveEnd: !!drawDir, tangentLength: drawDir ? drawDir[0] * drawDir[0] + drawDir[1] * drawDir[1] : 0, curve };
29233
29272
  }
29234
29273
  push(p, v) {
29235
29274
  this.pts.push([p[0] + v[0], p[1] + v[1]]);
@@ -29324,7 +29363,7 @@ var Outliner = class {
29324
29363
  return side === "R" ? s.v : [-s.v[0], -s.v[1]];
29325
29364
  }
29326
29365
  /** A cap at `p` from the `from` side of `s` round to the other side (`start`: around the back). */
29327
- cap(p, s, start) {
29366
+ cap(p, s, start, scale = 1) {
29328
29367
  const from = start ? "L" : "R";
29329
29368
  const to = start ? "R" : "L";
29330
29369
  const { cap } = this.opts;
@@ -29342,7 +29381,8 @@ var Outliner = class {
29342
29381
  const sv = this.capSide(s, from);
29343
29382
  const ev = this.capSide(s, to);
29344
29383
  if (cap === "square") {
29345
- const e = start ? [-s.e[0], -s.e[1]] : s.e;
29384
+ const e0 = scale === 1 ? s.e : squareExtension(this.opts.width, s.pe[0], s.pe[1], scale);
29385
+ const e = start ? [-e0[0], -e0[1]] : e0;
29346
29386
  this.push(p, [sv[0] + e[0], sv[1] + e[1]]);
29347
29387
  this.push(p, [ev[0] + e[0], ev[1] + e[1]]);
29348
29388
  } else {
@@ -29438,7 +29478,7 @@ var Outliner = class {
29438
29478
  * lets a single point stand for a zero-length dash along `dirs[0]`;
29439
29479
  * `draws` (curve end tangents) picks draw vertices only.
29440
29480
  */
29441
- open(P, dirs, draws, curves) {
29481
+ open(P, dirs, draws, curves, startScale = 1, endScale = 1) {
29442
29482
  if (P.length < 2 && !dirs?.[0]) {
29443
29483
  this.dot(P[0]);
29444
29484
  return;
@@ -29449,12 +29489,12 @@ var Outliner = class {
29449
29489
  }
29450
29490
  if (segs2.length === 0) {
29451
29491
  const s = this.seg(P[0], P[0], dirs?.[0], draws?.[0], curves?.[0]);
29452
- this.cap(P[0], s, true);
29453
- this.cap(P[0], s, false);
29492
+ this.cap(P[0], s, true, startScale);
29493
+ this.cap(P[0], s, false, endScale);
29454
29494
  this.flush();
29455
29495
  return;
29456
29496
  }
29457
- this.cap(P[0], segs2[0], true);
29497
+ this.cap(P[0], segs2[0], true, startScale);
29458
29498
  for (let i = 0; i + 1 < segs2.length; i++) {
29459
29499
  const s = segs2[i];
29460
29500
  const t = segs2[i + 1];
@@ -29465,7 +29505,7 @@ var Outliner = class {
29465
29505
  this.join(P[i + 1], s, t, "R", tr < 0);
29466
29506
  }
29467
29507
  }
29468
- this.cap(P[P.length - 1], segs2[segs2.length - 1], false);
29508
+ this.cap(P[P.length - 1], segs2[segs2.length - 1], false, endScale);
29469
29509
  for (let i = segs2.length - 1; i > 0; i--) {
29470
29510
  const s = segs2[i];
29471
29511
  const t = segs2[i - 1];
@@ -29521,7 +29561,7 @@ var Outliner = class {
29521
29561
  this.flush();
29522
29562
  }
29523
29563
  };
29524
- function dashPieces(P, tangents, pattern, shorten) {
29564
+ function dashPieces(P, tangents, curveFlags, pattern, shorten, wholePixelVectors) {
29525
29565
  const out = [];
29526
29566
  const dashes = [];
29527
29567
  for (let i = 0; i < pattern.length; i += 2) {
@@ -29536,12 +29576,22 @@ function dashPieces(P, tangents, pattern, shorten) {
29536
29576
  let idx = 0;
29537
29577
  let left = dashes[0];
29538
29578
  let on = true;
29539
- let cur = { pts: [P[0]], dirs: [], draws: [] };
29579
+ let cur = { pts: [P[0]], dirs: [], draws: [], curves: [], startScale: 1, endScale: 1 };
29540
29580
  for (let i = 0; i + 1 < P.length; i++) {
29541
29581
  const [x0, y0] = P[i];
29542
29582
  const [x1, y1] = P[i + 1];
29543
29583
  const dir = [x1 - x0, y1 - y0];
29544
- const len = Math.hypot(dir[0], dir[1]);
29584
+ const real = Math.hypot(dir[0], dir[1]);
29585
+ const len = wholePixelVectors ? Math.hypot(Math.floor(dir[0] / 16), Math.floor(dir[1] / 16)) * 16 : Math.hypot(dir[0], dir[1]);
29586
+ if (len === 0) {
29587
+ if (on && cur) {
29588
+ cur.pts.push(P[i + 1]);
29589
+ cur.dirs.push(dir);
29590
+ cur.draws.push(tangents[i]);
29591
+ cur.curves.push(curveFlags[i]);
29592
+ }
29593
+ continue;
29594
+ }
29545
29595
  let t = 0;
29546
29596
  while (len - t > left || left === 0 && on) {
29547
29597
  t += left;
@@ -29550,10 +29600,12 @@ function dashPieces(P, tangents, pattern, shorten) {
29550
29600
  cur.pts.push(q);
29551
29601
  cur.dirs.push(dir);
29552
29602
  cur.draws.push(tangents[i]);
29603
+ cur.curves.push(curveFlags[i]);
29604
+ cur.endScale = cur.pts[0] === P[0] && q[0] === P[0][0] && q[1] === P[0][1] ? 1 : real / len;
29553
29605
  out.push(cur);
29554
29606
  cur = null;
29555
29607
  } else {
29556
- cur = { pts: [q], dirs: [], draws: [] };
29608
+ cur = { pts: [q], dirs: [], draws: [], curves: [], startScale: real / len, endScale: 1 };
29557
29609
  }
29558
29610
  on = !on;
29559
29611
  idx = (idx + 1) % dashes.length;
@@ -29564,9 +29616,12 @@ function dashPieces(P, tangents, pattern, shorten) {
29564
29616
  cur.pts.push(P[i + 1]);
29565
29617
  cur.dirs.push(dir);
29566
29618
  cur.draws.push(tangents[i]);
29619
+ cur.curves.push(curveFlags[i]);
29620
+ cur.endScale = real / len;
29567
29621
  }
29568
29622
  }
29569
29623
  if (on && cur && cur.dirs.length > 0) {
29624
+ cur.endScale = 1;
29570
29625
  out.push(cur);
29571
29626
  }
29572
29627
  return out;
@@ -29601,10 +29656,13 @@ function widenPath(path, opts) {
29601
29656
  if (dashed) {
29602
29657
  const run = closed ? [...P, P[0]] : P;
29603
29658
  const shorten = opts.cap === "flat" || opts.shortenDashes === false ? 0 : opts.width;
29604
- for (const piece of dashPieces(run, closed ? [...dirs, void 0] : dirs, opts.dashes, shorten)) {
29659
+ const runTangents = closed ? [...dirs, void 0] : dirs;
29660
+ const runCurves = closed ? [...curves, !!fig.roundWiden] : curves;
29661
+ for (const piece of dashPieces(run, runTangents, runCurves, opts.dashes, shorten, !!opts.wholePixelDashVectors)) {
29605
29662
  const pts = [piece.pts[0]];
29606
29663
  const pdirs = [];
29607
29664
  const pdraws = [];
29665
+ const pcurves = [];
29608
29666
  for (let i = 1; i < piece.pts.length; i++) {
29609
29667
  const q = piece.pts[i];
29610
29668
  const last = pts[pts.length - 1];
@@ -29612,9 +29670,10 @@ function widenPath(path, opts) {
29612
29670
  pts.push(q);
29613
29671
  pdirs.push(piece.dirs[i - 1]);
29614
29672
  pdraws.push(piece.draws[i - 1]);
29673
+ pcurves.push(piece.curves[i - 1]);
29615
29674
  }
29616
29675
  }
29617
- outliner.open(pts, pdirs.length > 0 ? pdirs : [piece.dirs[0]], pdirs.length > 0 ? pdraws : [piece.draws[0]], pts.map(() => !!fig.roundWiden));
29676
+ outliner.open(pts, pdirs.length > 0 ? pdirs : [piece.dirs[0]], pdirs.length > 0 ? pdraws : [piece.draws[0]], pdirs.length > 0 ? pcurves : [piece.curves[0]], piece.startScale, piece.endScale);
29618
29677
  }
29619
29678
  } else if (closed && P.length >= 3) {
29620
29679
  outliner.closed(P, dirs, curves);
@@ -29822,7 +29881,10 @@ function penWidenOptions(rCtx, opts = {}) {
29822
29881
  join: opts.roundPen ? "round" : opts.rectangle && !state.penExtended ? "miter" : !state.penExtended || joinBits === 0 ? "round" : joinBits === 4096 ? "bevel" : "miter",
29823
29882
  miterLimit: state.miterLimit ?? 10,
29824
29883
  dashes: dashes ? dashes.map((v) => v * 16) : null,
29825
- shortenDashes: (flags & 15) !== 7
29884
+ shortenDashes: (flags & 15) !== 7,
29885
+ // One logical unit per device pixel, unrotated: GDI measures dash
29886
+ // segments in whole pixels.
29887
+ wholePixelDashVectors: Math.abs(matrix[0] - 1) < 1e-6 && Math.abs(matrix[3] - 1) < 1e-6 && Math.abs(matrix[1]) < 1e-6 && Math.abs(matrix[2]) < 1e-6
29826
29888
  };
29827
29889
  }
29828
29890
  function hatchBackgroundMask(hatch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emf-converter",
3
- "version": "4.8.0",
3
+ "version": "4.8.2",
4
4
  "description": "Convert EMF/WMF metafile binaries to PNG data URLs or SVG (markup, data URL, React/JSX)",
5
5
  "keywords": [
6
6
  "canvas",