emf-converter 4.21.0 → 4.22.0

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/browser.mjs CHANGED
@@ -19839,11 +19839,16 @@ function recordDeviceFigures(buildPath, device) {
19839
19839
  const f = fig;
19840
19840
  f.curved = true;
19841
19841
  const n = f.pts.length;
19842
+ const near = (qx, qy) => Math.abs(qx - f.pts[n - 2]) < 1e-3 && Math.abs(qy - f.pts[n - 1]) < 1e-3;
19843
+ const degenerate = near(dx(c1x, c1y), dy(c1x, c1y)) && near(dx(c2x, c2y), dy(c2x, c2y)) && near(dx(x, y), dy(x, y));
19842
19844
  const fix2 = (v) => toPlusFix(v);
19843
19845
  const p0x = fix2(f.pts[n - 2]);
19844
19846
  const p0y = fix2(f.pts[n - 1]);
19845
19847
  f.pts[n - 2] = p0x / 16;
19846
19848
  f.pts[n - 1] = p0y / 16;
19849
+ if (degenerate) {
19850
+ (f.splits ?? (f.splits = [])).push({ x: p0x / 16, y: p0y / 16 });
19851
+ }
19847
19852
  const out = [];
19848
19853
  const ctrl = [p0x, p0y, fix2(dx(c1x, c1y)), fix2(dy(c1x, c1y)), fix2(dx(c2x, c2y)), fix2(dy(c2x, c2y)), fix2(dx(x, y)), fix2(dy(x, y))];
19849
19854
  flattenBezierGdiplus(ctrl, out);
@@ -20217,7 +20222,7 @@ function sidePoints(pts, closed, o, pen) {
20217
20222
  }
20218
20223
  return out;
20219
20224
  }
20220
- function dashFigure(pts, closed, pen) {
20225
+ function dashFigure(pts, closed, pen, splits) {
20221
20226
  const shorten = pen.dashCap !== 0 ? 2 * pen.half : 0;
20222
20227
  const dash = pen.dash.map((v, i) => {
20223
20228
  if (i % 2 === 0) {
@@ -20271,6 +20276,11 @@ function dashFigure(pts, closed, pen) {
20271
20276
  remaining -= segLen - t0;
20272
20277
  if (on && cur) {
20273
20278
  cur.push(b);
20279
+ if (splits && i + 2 < path.length && splits.some((k) => Math.abs(k.x - b.x) < 1e-6 && Math.abs(k.y - b.y) < 1e-6)) {
20280
+ out.push({ pts: cur, first: curFirst, last: false });
20281
+ cur = [b];
20282
+ curFirst = false;
20283
+ }
20274
20284
  }
20275
20285
  segLen = 0;
20276
20286
  }
@@ -20400,7 +20410,7 @@ function widenFigures(figures, pen) {
20400
20410
  bands.push([lo, hi]);
20401
20411
  }
20402
20412
  const dashPen = pen.inset && closed ? { ...pen, dashOffset: pen.dashOffset * 2 } : pen;
20403
- const pieces = pen.dash ? dashFigure(pts, closed, dashPen) : [{ pts, first: true, last: true }];
20413
+ const pieces = pen.dash ? dashFigure(pts, closed, dashPen, fig.splits) : [{ pts, first: true, last: true }];
20404
20414
  const pieceClosed = !pen.dash && closed;
20405
20415
  for (const piece of pieces) {
20406
20416
  const pp = cleanPoints(
@@ -20445,6 +20455,17 @@ function widenFigures(figures, pen) {
20445
20455
  const startCap = startCapOverride ?? (piece.first && !(pen.dash && closed) ? pen.startCap : pen.dashCap);
20446
20456
  const capEnd = capPoints(pp[pp.length - 1], fEnd, o1, o2, endCap, h);
20447
20457
  const capStart = capPoints(pp[0], f0, -o2, -o1, startCap, h);
20458
+ if (endCap === 0 && capEnd.length === 2 && left.length > 2 && right.length > 2) {
20459
+ const behind = (c2, q) => (c2.x - q.x) * fEnd.ux + (c2.y - q.y) * fEnd.uy < -1e-9;
20460
+ const lq = left[left.length - 2];
20461
+ const rq = right[1];
20462
+ if (behind(capEnd[0], lq)) {
20463
+ capEnd[0] = { x: lq.x, y: lq.y };
20464
+ }
20465
+ if (behind(capEnd[1], rq)) {
20466
+ capEnd[1] = { x: rq.x, y: rq.y };
20467
+ }
20468
+ }
20448
20469
  const poly = [...left.slice(startCap === 1 ? 1 : 0, -1), ...capEnd, ...right.slice(1, -1), ...capStart];
20449
20470
  polys.push(poly);
20450
20471
  const sign = Math.sign(signedArea(poly));
@@ -22014,6 +22035,10 @@ var HQ_REDUCTION_STEP_NUDGE = 0.027;
22014
22035
  function hqReductionStep(reciprocal) {
22015
22036
  return Math.floor(reciprocal + 0.5 + HQ_REDUCTION_STEP_NUDGE);
22016
22037
  }
22038
+ function hqStartShift(edge) {
22039
+ return edge > 512 ? 0 : edge > 256 ? 2 : edge > 64 ? 1 : 0;
22040
+ }
22041
+ var HQ_START_SHIFT_FRACTION_LIMIT = 0.9375 - 1e-9;
22017
22042
  function hqTentCdf(j) {
22018
22043
  if (j <= -128) {
22019
22044
  return -32768;
@@ -22113,9 +22138,12 @@ function resampleHqAxisAligned(rgba, width, height, spec, surface, m, premultOut
22113
22138
  const fraction2 = rawLeftEdge - Math.floor(rawLeftEdge);
22114
22139
  const offsetU = mirrorU ? 0 : snapsDown ? Math.round((1 + fraction2) * stepU) : snapsUp ? Math.round((fraction2 - 1) * stepU) : dx > 1e-9 ? Math.round((1 - dx) * stepU) : 0;
22115
22140
  const offsetV = dy > 1e-9 ? Math.round(dy * stepV) : 0;
22141
+ const fractionU = rawLeftEdge - Math.floor(rawLeftEdge);
22142
+ const startShiftU = mirrorU || fractionU >= HQ_START_SHIFT_FRACTION_LIMIT ? 0 : -hqStartShift(rawLeftEdge);
22143
+ const startShiftV = hqStartShift(rawTop);
22116
22144
  const filterU = axisFilter(kernel, scaleU, false);
22117
22145
  const filterV = axisFilter(kernel, scaleV, false);
22118
- const tapsFor = (filter, step, offset, origin, from, to, scale, mirror2 = false) => {
22146
+ const tapsFor = (filter, step, offset, origin, from, to, scale, startShift, mirror2 = false) => {
22119
22147
  const taps = [];
22120
22148
  const farBias = ((step & step - 1) === 0 ? 0 : Math.round(spec.srcW / 170)) + (mirror2 ? mirroredBias(spec, scale, dx > 1e-9) : 0);
22121
22149
  const position = (k) => mirror2 ? Math.round((mirroredPixels - k) * step) + farBias : k * step + offset;
@@ -22128,7 +22156,7 @@ function resampleHqAxisAligned(rgba, width, height, spec, surface, m, premultOut
22128
22156
  const originBins = origin * HQ_PHASES;
22129
22157
  const wholeBins = Math.abs(originBins - Math.round(originBins)) < 1e-6;
22130
22158
  if (scale < 1 && wholeBins) {
22131
- return hqReductionTaps(cubic, scale, step, position, origin, from, to);
22159
+ return hqReductionTaps(cubic, scale, step, (k) => position(k) + startShift, origin, from, to);
22132
22160
  }
22133
22161
  const tabulated = cubic && wholeBins;
22134
22162
  for (let k = from; k <= to; k++) {
@@ -22153,8 +22181,8 @@ function resampleHqAxisAligned(rgba, width, height, spec, surface, m, premultOut
22153
22181
  }
22154
22182
  return taps;
22155
22183
  };
22156
- const colTaps = tapsFor(filterU, stepU, offsetU, spec.srcX, cx0 - xFirst, cx1 - xFirst, scaleU, mirrorU);
22157
- const rowTaps = tapsFor(filterV, stepV, offsetV, spec.srcY, cy0 - yFirst, cy1 - yFirst, scaleV);
22184
+ const colTaps = tapsFor(filterU, stepU, offsetU, spec.srcX, cx0 - xFirst, cx1 - xFirst, scaleU, startShiftU, mirrorU);
22185
+ const rowTaps = tapsFor(filterV, stepV, offsetV, spec.srcY, cy0 - yFirst, cy1 - yFirst, scaleV, startShiftV);
22158
22186
  let rowMin = Infinity;
22159
22187
  let rowMax = -Infinity;
22160
22188
  for (const t of rowTaps) {
@@ -22174,11 +22202,19 @@ function resampleHqAxisAligned(rgba, width, height, spec, surface, m, premultOut
22174
22202
  rowMax = Math.min(height - 1, rowMax);
22175
22203
  }
22176
22204
  const rows = Math.max(0, rowMax - rowMin + 1);
22177
- const rowMargin = edge || Math.abs(scaleV - 1) < 1e-9 ? Infinity : Math.ceil((cubic ? 2 : 1) / Math.min(1, scaleV));
22205
+ const rowMargin = Math.abs(scaleV - 1) < 1e-9 ? Infinity : Math.ceil((cubic ? 2 : 1) / Math.min(1, scaleV));
22178
22206
  const inter = new Uint8Array(rows * w * 4);
22179
22207
  for (let r = 0; r < rows; r++) {
22180
- const rowTexel = edge ? wrapTap(rowMin + r, 0, height, edge.mirrorY, edge) : rowMin + r;
22181
- if (rowTexel === OUTSIDE_TRANSPARENT || rowTexel < spec.srcY - rowMargin || rowTexel > spec.srcY + spec.srcH + rowMargin - 1) {
22208
+ let rowTexel = rowMin + r;
22209
+ if (edge) {
22210
+ if (Number.isFinite(rowMargin)) {
22211
+ rowTexel = wrapTap(rowTexel, Math.max(0, spec.srcY - rowMargin), Math.min(height, spec.srcY + spec.srcH + rowMargin), edge.mirrorY, edge);
22212
+ }
22213
+ if (rowTexel !== OUTSIDE_TRANSPARENT && rowTexel !== OUTSIDE_CLAMP) {
22214
+ rowTexel = wrapTap(rowTexel, 0, height, edge.mirrorY, edge);
22215
+ }
22216
+ }
22217
+ if (rowTexel === OUTSIDE_TRANSPARENT || rowTexel !== OUTSIDE_CLAMP && (rowTexel < spec.srcY - rowMargin || rowTexel > spec.srcY + spec.srcH + rowMargin - 1)) {
22182
22218
  continue;
22183
22219
  }
22184
22220
  const srcRow = rowTexel * width;
@@ -26641,6 +26677,7 @@ var ceil64 = (x) => floor64(x + 63);
26641
26677
  var ON_CURVE = 1;
26642
26678
  var TOUCH_X = 8;
26643
26679
  var STEM_MIN_WIDTH = 16;
26680
+ var STEM_SMALL_SHIFT = 4;
26644
26681
  var TOUCH_Y = 16;
26645
26682
  var Zone = class _Zone {
26646
26683
  constructor(n, endPts = []) {
@@ -26729,6 +26766,8 @@ var HintedSize = class _HintedSize {
26729
26766
  this.iupy = false;
26730
26767
  /** IUP[x] has run in the current glyph program. */
26731
26768
  this.iupx = false;
26769
+ /** Points that belong to a stem pair shifted so far in the glyph program (compatible-width ClearType). */
26770
+ this.stemPoints = /* @__PURE__ */ new Set();
26732
26771
  this.count = 0;
26733
26772
  this.hintingGlyph = 0;
26734
26773
  this.font = font;
@@ -26809,8 +26848,10 @@ var HintedSize = class _HintedSize {
26809
26848
  const { advance } = this.font.hMetrics(g);
26810
26849
  const res2 = this.loadRecursive(g, 0);
26811
26850
  const n = res2.zone.n - 4;
26812
- const pp1x = res2.zone.curX[n];
26813
26851
  const pp2x = res2.zone.curX[n + 1];
26852
+ const pp1x = res2.zone.curX[n];
26853
+ const init = res2.zone.pp1Init ?? pp1x;
26854
+ const rasterShift = pp1x - (init + round64(pp1x - init));
26814
26855
  const xs = new Float64Array(n);
26815
26856
  const ys = new Float64Array(n);
26816
26857
  const onCurve = new Uint8Array(n);
@@ -26825,6 +26866,7 @@ var HintedSize = class _HintedSize {
26825
26866
  onCurve,
26826
26867
  endPts: res2.zone.endPts,
26827
26868
  advance: pp2x - pp1x,
26869
+ rasterShift,
26828
26870
  linearAdvance: mulFix(advance, this.xScale),
26829
26871
  scanControl: res2.scanControl,
26830
26872
  scanSet: res2.scanSet,
@@ -27003,15 +27045,30 @@ var HintedSize = class _HintedSize {
27003
27045
  return hinted;
27004
27046
  }
27005
27047
  if (this.hinting && !metricsFrom) {
27006
- zone.curX[total] = round64(zone.curX[total]);
27007
- zone.curX[total + 1] = round64(zone.curX[total + 1]);
27008
27048
  this.hintingGlyph = g;
27049
+ this.roundHorizontalPhantoms(zone, total, round64);
27009
27050
  this.compatAdvance(zone, total + 1);
27010
27051
  zone.curY[total + 2] = round64(zone.curY[total + 2]);
27011
27052
  zone.curY[total + 3] = round64(zone.curY[total + 3]);
27012
27053
  }
27054
+ zone.pp1Init = zone.curX[total];
27013
27055
  return { zone, ...lastScan };
27014
27056
  }
27057
+ /**
27058
+ * Places the horizontal phantom points `zone.curX[a]` (left) and `zone.curX[a + 1]` (advance) before the program runs.
27059
+ * GDI's own interpreter leaves the left point where scaling put it and rounds the advance, so `pp2 = pp1 + round(width)`:
27060
+ * Times New Roman Italic < and > (lsb 150 against xMin 39 and 37 units) start 39/64 pixel off the grid and native
27061
+ * GGO_NATIVE shows it from the first instruction on (`prefix-timesi`, 58 prefixes at 11 and 17 px). Every other
27062
+ * interpreter rounds both points.
27063
+ */
27064
+ roundHorizontalPhantoms(zone, a, round) {
27065
+ if (this.gdiMath && !this.env.clearType) {
27066
+ zone.curX[a + 1] = zone.curX[a] + round64(zone.curX[a + 1] - zone.curX[a]);
27067
+ return;
27068
+ }
27069
+ zone.curX[a] = round(zone.curX[a]);
27070
+ zone.curX[a + 1] = round(zone.curX[a + 1]);
27071
+ }
27015
27072
  /** Under compatible-width ClearType the advance phantom point is held at the black-and-white advance. */
27016
27073
  compatAdvance(zone, k) {
27017
27074
  const bw = this.env.clearType && this.env.compatibleWidths !== false ? this.env.bwAdvance?.(this.hintingGlyph) : void 0;
@@ -27024,12 +27081,12 @@ var HintedSize = class _HintedSize {
27024
27081
  const n = zone.n;
27025
27082
  if (this.hinting && roundPhantoms) {
27026
27083
  const rx = this.env.clearType ? (v) => Math.floor((v + 2) / 4) * 4 : round64;
27027
- zone.curX[n - 4] = rx(zone.curX[n - 4]);
27028
- zone.curX[n - 3] = rx(zone.curX[n - 3]);
27084
+ this.roundHorizontalPhantoms(zone, n - 4, rx);
27029
27085
  this.compatAdvance(zone, n - 3);
27030
27086
  zone.curY[n - 2] = round64(zone.curY[n - 2]);
27031
27087
  zone.curY[n - 1] = round64(zone.curY[n - 1]);
27032
27088
  }
27089
+ zone.pp1Init = zone.curX[n - 4];
27033
27090
  if (!this.glyphHinting || instructions.length === 0) {
27034
27091
  return { zone, ...this.defaultScan };
27035
27092
  }
@@ -27042,6 +27099,7 @@ var HintedSize = class _HintedSize {
27042
27099
  this.stack = [];
27043
27100
  this.iupy = false;
27044
27101
  this.iupx = false;
27102
+ this.stemPoints.clear();
27045
27103
  this.computeFuncs();
27046
27104
  try {
27047
27105
  this.execute(instructions);
@@ -27374,6 +27432,22 @@ var HintedSize = class _HintedSize {
27374
27432
  }
27375
27433
  return def.sizeTweak;
27376
27434
  }
27435
+ /**
27436
+ * GDI+ grayscale text (`shpixX`): which SHPIX shifts along x native honours. Measured with prefix copies of Arial Bold
27437
+ * `y` (40 ppem), Arial `W` (12), Times New Roman Italic `T` (22) and Courier New `R` (22) and the 31,584 real glyphs
27438
+ * (see docs/outstanding-work.md): a shift made by a function call (not a generic per-size helper, `inSizeTweak`)
27439
+ * applies; one made inline in the glyph program applies to a point touched in exactly one direction, or in both once
27440
+ * both IUPs have run, or in neither once both have run when it moves at least 32/64 px (Arial Bold `2`); smaller
27441
+ * shifts of untouched points (Courier New `R` 22) and points touched in both before the IUPs (Times New Roman Italic
27442
+ * `T` 22) are not applied: either costs pixels in the pinned controls.
27443
+ */
27444
+ plusShpixX(p, amt) {
27445
+ if (!this.env.shpixX) return false;
27446
+ if (this.callFrames.length > 0) return true;
27447
+ const t = this.zp2.tags[p];
27448
+ const tx = (t & TOUCH_X) !== 0, ty = (t & TOUCH_Y) !== 0;
27449
+ return tx !== ty || this.iupx && this.iupy && (tx || Math.abs(amt) >= 32);
27450
+ }
27377
27451
  /**
27378
27452
  * Whether backward-compatible ClearType (and the GDI+ grayscale text that runs the same interpreter) shifts a point by
27379
27453
  * a SHPIX along y: it must be touched in y, and a generic per-size helper function (`isSizeTweak`) stops shifting once
@@ -27874,7 +27948,7 @@ var HintedSize = class _HintedSize {
27874
27948
  while (gs.loop > 0) {
27875
27949
  const p = this.pop();
27876
27950
  if (p >= 0 && p < this.zp2.n) {
27877
- const keep = !this.ctCompat() || this.inComposite || gs.fvx === 0 && this.shpixAlongY(p) || gs.fvx !== 0 && !this.env.grayscale && !this.inSizeTweak();
27951
+ const keep = !this.ctCompat() || this.inComposite || gs.fvx === 0 && this.shpixAlongY(p) || gs.fvx !== 0 && !this.inSizeTweak() && (!this.env.grayscale || this.plusShpixX(p, amt));
27878
27952
  if (keep) {
27879
27953
  this.moveZp2(p, dx, dy, true);
27880
27954
  }
@@ -27917,7 +27991,7 @@ var HintedSize = class _HintedSize {
27917
27991
  }
27918
27992
  this.move(this.zp1, p, target - dist);
27919
27993
  this.carryFrom(p);
27920
- this.pairShift(gs.rp0, p);
27994
+ this.pairShift(gs.rp0, p, true);
27921
27995
  gs.rp1 = gs.rp0;
27922
27996
  gs.rp2 = p;
27923
27997
  if (op & 1) {
@@ -28483,7 +28557,7 @@ var HintedSize = class _HintedSize {
28483
28557
  }
28484
28558
  }
28485
28559
  /** A MIRP or MSIRP just placed `p` relative to rp0: if the two are the sides of a stem, shift the stem. */
28486
- pairShift(rp0, p) {
28560
+ pairShift(rp0, p, black) {
28487
28561
  if (!this.widthShiftOn() || this.iupx) {
28488
28562
  return;
28489
28563
  }
@@ -28506,6 +28580,18 @@ var HintedSize = class _HintedSize {
28506
28580
  z.shifted ?? (z.shifted = new Uint8Array(z.n));
28507
28581
  const centre = (z.orgX[rp0] + z.orgX[p]) / 2;
28508
28582
  const shift = Math.round(centre * (bw / org2 - 1));
28583
+ if (!black && Math.abs(shift) >= STEM_SMALL_SHIFT) {
28584
+ return;
28585
+ }
28586
+ if (this.stemPoints.has(rp0) && Math.abs(shift) >= STEM_SMALL_SHIFT) {
28587
+ z.applied ?? (z.applied = new Float64Array(z.n));
28588
+ const own = Math.round(z.orgX[p] * (bw / org2 - 1)) - (z.carry ? z.carry[p] * (bw - org2) : 0);
28589
+ z.curX[p] += own - z.applied[p];
28590
+ z.applied[p] = own;
28591
+ return;
28592
+ }
28593
+ this.stemPoints.add(rp0);
28594
+ this.stemPoints.add(p);
28509
28595
  const offset = bw - org2;
28510
28596
  const lo = Math.min(z.curX[rp0], z.curX[p]);
28511
28597
  const hi = Math.max(z.curX[rp0], z.curX[p]);
@@ -28638,7 +28724,7 @@ var HintedSize = class _HintedSize {
28638
28724
  }
28639
28725
  this.move(this.zp1, p, distance - curDist);
28640
28726
  this.carryFrom(p);
28641
- this.pairShift(gs.rp0, p);
28727
+ this.pairShift(gs.rp0, p, (op & 3) === 1);
28642
28728
  gs.rp1 = gs.rp0;
28643
28729
  if (op & 16) gs.rp0 = p;
28644
28730
  gs.rp2 = p;
@@ -30418,7 +30504,7 @@ var RasterRealizedFont = class {
30418
30504
  }
30419
30505
  };
30420
30506
  var RealizedFont = class {
30421
- constructor(ttf, ppem, ppemX, mode, syntheticBold, syntheticItalic, cellHeight = 0, stretchWidth = 0, gridFit = true, gdiPlus = false, playbackStretched = false) {
30507
+ constructor(ttf, ppem, ppemX, mode, syntheticBold, syntheticItalic, cellHeight = 0, stretchWidth = 0, gridFit = true, gdiPlus = false, playbackStretched = false, plusUnscaled = false) {
30422
30508
  this.gdiPlus = gdiPlus;
30423
30509
  this.playbackStretched = playbackStretched;
30424
30510
  this.rotatedSizes = /* @__PURE__ */ new Map();
@@ -30474,7 +30560,7 @@ var RealizedFont = class {
30474
30560
  ttf,
30475
30561
  ppemX,
30476
30562
  ppem,
30477
- gdiPlus && mode === "gray" ? { version: 40, grayscale: true, clearType: true, compatibleWidths: false, grayClearType: true } : { version: 35, grayscale: mode !== "mono", gdiArithmetic: !gdiPlus, reportStretched: playbackStretched },
30563
+ gdiPlus && mode === "gray" ? { version: 40, grayscale: true, clearType: true, compatibleWidths: false, grayClearType: true, shpixX: plusUnscaled } : { version: 35, grayscale: mode !== "mono", gdiArithmetic: !gdiPlus, reportStretched: playbackStretched },
30478
30564
  gridFit || gdiPlus && mode === "gray"
30479
30565
  );
30480
30566
  this.hdmx = ppemX === ppem ? ttf.hdmx.get(ppem) : void 0;
@@ -30557,7 +30643,7 @@ var RealizedFont = class {
30557
30643
  gdiArithmetic: !this.gdiPlus,
30558
30644
  smoothingBit18: !this.gdiPlus && gridFit && clearTypeSmoothsVertically(this.ttf, this.ppem),
30559
30645
  reportStretched: this.playbackStretched,
30560
- bwAdvance: !this.gdiPlus && !this.naturalWidths && gridFit ? (g) => this.advance(g) : void 0
30646
+ bwAdvance: !this.gdiPlus && !this.naturalWidths && gridFit ? (g) => this.advance(g, true) : void 0
30561
30647
  }, gridFit));
30562
30648
  const u = this.ctSize.hintGlyph(gi);
30563
30649
  o = { xs: u.xs, ys: u.ys, onCurve: u.onCurve, endPts: u.endPts };
@@ -30587,8 +30673,22 @@ var RealizedFont = class {
30587
30673
  }
30588
30674
  return g;
30589
30675
  }
30676
+ /** The scaled advance of glyph `index`, rounded once. */
30677
+ linearAdvance(index) {
30678
+ const { advance } = this.ttf.hMetrics(index < this.ttf.numGlyphs ? index : NOTDEF);
30679
+ return Math.floor(advance * this.ppemX / this.ttf.unitsPerEm + 0.5);
30680
+ }
30681
+ /** Whether glyph `index` advances linearly at this size: `head.flags` bit 4 clear, or `LTSH` (an `hdmx` record for the size comes first). */
30682
+ ltshLinear(index) {
30683
+ if (this.gdiPlus || this.ppemX !== this.ppem) return false;
30684
+ const hd = this.hdmx;
30685
+ if (hd && index < hd.length) return false;
30686
+ if ((this.ttf.headFlags & 16) === 0) return true;
30687
+ const lt = this.ttf.ltsh;
30688
+ return !!lt && index < lt.length && lt[index] > 0 && this.ppem >= lt[index];
30689
+ }
30590
30690
  /** Integer advance width of glyph `index`, as GetCharWidth32 reports it (GDI+ ClearType: its hinted phantom advance rounded). */
30591
- advance(index) {
30691
+ advance(index, hintedOnly = false) {
30592
30692
  const hd = this.hdmx;
30593
30693
  let base;
30594
30694
  if (this.stretchWidth > 0) {
@@ -30596,6 +30696,8 @@ var RealizedFont = class {
30596
30696
  base = Math.round(advance * this.stretchWidth / this.ttf.xAvgCharWidth);
30597
30697
  } else if (!this.gridFit) {
30598
30698
  base = this.outline(index).linearAdvance / 64;
30699
+ } else if (!hintedOnly && this.ltshLinear(index)) {
30700
+ base = this.linearAdvance(index);
30599
30701
  } else if (this.advanceSize) {
30600
30702
  let g = this.advanceOutlines.get(index);
30601
30703
  if (!g) {
@@ -30658,6 +30760,12 @@ var RealizedFont = class {
30658
30760
  m = roundedRotationMatrix(m, this.ppem);
30659
30761
  }
30660
30762
  let o = this.mode === "cleartype" && !m && !this.syntheticItalic ? this.ctOutline(index) : src;
30763
+ const shift = this.gdiPlus || m || this.mode === "cleartype" ? 0 : src.rasterShift ?? 0;
30764
+ if (shift !== 0) {
30765
+ const xs = new Float64Array(o.xs.length);
30766
+ for (let i = 0; i < xs.length; i++) xs[i] = o.xs[i] + shift;
30767
+ o = { ...o, xs };
30768
+ }
30661
30769
  if (this.syntheticItalic || m || subX || subY) {
30662
30770
  const base = o;
30663
30771
  const n = base.xs.length;
@@ -30923,7 +31031,8 @@ var GdiFontCollection = class _GdiFontCollection {
30923
31031
  spec.width,
30924
31032
  !spec.unhinted,
30925
31033
  spec.gdiPlus,
30926
- !!spec.playbackStretch && playbackStretchReacts(best, spec.height, spec.playbackStretch)
31034
+ !!spec.playbackStretch && playbackStretchReacts(best, spec.height, spec.playbackStretch),
31035
+ !!spec.unscaledText
30927
31036
  );
30928
31037
  tt2.naturalWidths = spec.quality === CLEARTYPE_NATURAL_QUALITY;
30929
31038
  return tt2;
@@ -33805,7 +33914,6 @@ function withoutOutline(spans, path) {
33805
33914
  var fix = (v) => Math.floor(v * 16 + 0.5) / 16;
33806
33915
  var TIE = 1e-6;
33807
33916
  var STRIP_TIE = 5e-3;
33808
- var CONTAINMENT_TIE = 5e-4;
33809
33917
  var SLANTED_NEAR = 5e-6;
33810
33918
  var CEIL_EPSILON = 1e-9;
33811
33919
  function prepareCopies(shape, full, steps) {
@@ -34015,7 +34123,6 @@ function anisotropicTieStep(g, q, tie, dx, dy, px, py) {
34015
34123
  const hit = edgeAt(g, scaleX, scaleY, px, py);
34016
34124
  const half = g.steps - tie - 0.5;
34017
34125
  if (hit?.kind === "slanted") return half;
34018
- if (hit === null && Math.abs(q - tie) >= CONTAINMENT_TIE) return half;
34019
34126
  if (hit === null) return g.steps - tie - (insideCopy(g, scaleX, px, py, scaleY) ? 0 : 1);
34020
34127
  let odd = g.steps;
34021
34128
  while (odd % 2 === 0) odd /= 2;
@@ -42514,7 +42621,8 @@ function realizePlusEngineFont(rCtx, font, paint, brushSampler2) {
42514
42621
  // AntiAlias ignores the font's gasp table (measured: Arial 16 px is
42515
42622
  // grayscale there, but single-bit under AntiAliasGridFit, as in GDI).
42516
42623
  ignoreGasp: hint === 4,
42517
- gdiPlus: true
42624
+ gdiPlus: true,
42625
+ unscaledText: m[0] === 1 && m[3] === 1
42518
42626
  };
42519
42627
  const realized = fonts.realize(spec, rCtx.fontFamilyMap);
42520
42628
  if (!realized) {