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