emf-converter 4.20.1 → 4.20.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/browser.js CHANGED
@@ -29076,16 +29076,11 @@ function monoBox(o) {
29076
29076
  let xMax = Math.floor((cxMax + 32) / 64);
29077
29077
  let yMin = Math.floor((cyMin + 31) / 64);
29078
29078
  let yMax = Math.floor((cyMax + 32) / 64);
29079
- const rem = (v, add) => (v + add & 63) - add;
29080
- if (xMin === xMax) {
29081
- if (rem(cxMin, 31) + rem(cxMax, 32) < 0) xMin -= 1;
29082
- else xMax += 1;
29083
- }
29084
- if (yMin === yMax) {
29085
- if (rem(cyMin, 31) + rem(cyMax, 32) < 0) yMin -= 1;
29086
- else yMax += 1;
29087
- }
29088
- return { xMin, xMax, yMin, yMax };
29079
+ const collapsedX = xMin === xMax;
29080
+ const collapsedY = yMin === yMax;
29081
+ if (collapsedX) xMax += 1;
29082
+ if (collapsedY) yMax += 1;
29083
+ return { xMin, xMax, yMin, yMax, collapsedX, collapsedY };
29089
29084
  }
29090
29085
  function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossings26Dot6 = false) {
29091
29086
  const box = fixedBox ?? monoBox(o);
@@ -29152,7 +29147,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29152
29147
  break;
29153
29148
  case 1:
29154
29149
  case 5:
29155
- const stub = monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29150
+ const stub = monoContour && box.collapsedX ? false : monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29156
29151
  if (stub === true) return;
29157
29152
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= r.half)) {
29158
29153
  return;
@@ -29213,7 +29208,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29213
29208
  break;
29214
29209
  case 1:
29215
29210
  case 5:
29216
- const stub = monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29211
+ const stub = monoContour && box.collapsedY ? false : monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29217
29212
  if (stub === true) return;
29218
29213
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= h.half)) {
29219
29214
  return;
@@ -29577,6 +29572,7 @@ var RasterRealizedFont = class {
29577
29572
  fsSelection: face.italic ? 1 : 0,
29578
29573
  macStyle: 0,
29579
29574
  winAscent: this.ascent,
29575
+ winDescent: this.descent,
29580
29576
  unitsPerEm: this.ppem || 1
29581
29577
  };
29582
29578
  }
@@ -29764,6 +29760,11 @@ var RealizedFont = class {
29764
29760
  }
29765
29761
  return base + (this.syntheticBold ? 1 : 0);
29766
29762
  }
29763
+ /** GDI+ rounds tracked hmtx advances to font units before device scaling. */
29764
+ designAdvance(index, tracking) {
29765
+ const { advance } = this.ttf.hMetrics(index < this.ttf.numGlyphs ? index : NOTDEF);
29766
+ return Math.round(advance * tracking) * this.ppemX / this.ttf.unitsPerEm + (this.syntheticBold ? 1 : 0);
29767
+ }
29767
29768
  /**
29768
29769
  * Advance of glyph `index` in a font realised with an escapement: GDI
29769
29770
  * does not use the hinted/hdmx widths there but the linearly scaled
@@ -40451,6 +40452,35 @@ function surfaceSize5(ctx) {
40451
40452
  return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
40452
40453
  }
40453
40454
 
40455
+ // src/emf-plus-text-alignment.ts
40456
+ var SIMPLE_SCRIPT = /^(?:\p{Script=Latin}|\p{Script=Common}|\p{Co})$/u;
40457
+ var SHAPING_CHARACTER = /[\p{M}\p{Cf}\p{Emoji_Presentation}]/u;
40458
+ function layoutPlusAlignedString(font, codes, format, x, y, width, height) {
40459
+ const horizontal = format.alignment, vertical = format.lineAlignment;
40460
+ if (!horizontal && !vertical || horizontal < 0 || horizontal > 2 || vertical < 0 || vertical > 2 || font.gridFit || font.mode !== "gray" || !font.designAdvance || font.ttf.winDescent === void 0 || font.syntheticBold || font.syntheticItalic || format.flags & 3 || ![x, y, width, height].every(Number.isFinite) || width <= 0 || height <= 0 || codes.some((c) => !Number.isInteger(c) || c < 32 || c > 1114111 || c >= 127 && c <= 159 || c === 8232 || c === 8233)) return null;
40461
+ if (codes.some((c) => {
40462
+ const character = String.fromCodePoint(c);
40463
+ return !SIMPLE_SCRIPT.test(character) || SHAPING_CHARACTER.test(character) || font.glyphIndex(c) === 0;
40464
+ })) return null;
40465
+ const em = font.ppem;
40466
+ const tracking = format.tracking ?? 1.03;
40467
+ const leading = em * (format.leadingMargin ?? 1 / 6);
40468
+ const trailing = em * (format.trailingMargin ?? 1 / 6);
40469
+ if (![tracking, leading, trailing].every((v) => Number.isFinite(v) && v >= 0)) return null;
40470
+ if (!(format.flags & 4) && (font.ttf.fsSelection & 1 || font.ttf.macStyle & 2)) return null;
40471
+ const dx = codes.map((c) => font.designAdvance(font.glyphIndex(c), tracking));
40472
+ let measuredCount = codes.length;
40473
+ if (!(format.flags & 2048)) while (measuredCount > 0 && codes[measuredCount - 1] === 32) measuredCount--;
40474
+ const measuredWidth = dx.slice(0, measuredCount).reduce((a, b) => a + b, 0) + leading + trailing;
40475
+ const measuredHeight = em * (font.ttf.winAscent + font.ttf.winDescent) / font.ttf.unitsPerEm + (format.flags & 4 ? 0 : em / 8);
40476
+ if (measuredWidth > width || measuredHeight > height) return null;
40477
+ return {
40478
+ x: Math.fround(x + horizontal / 2 * (width - measuredWidth) + leading),
40479
+ y: Math.fround(y + vertical / 2 * (height - measuredHeight)) + Math.round(em * font.ttf.winAscent / font.ttf.unitsPerEm),
40480
+ dx
40481
+ };
40482
+ }
40483
+
40454
40484
  // src/emf-plus-text-image-handlers.ts
40455
40485
  function imageAttributesWrap(rCtx, attributesId) {
40456
40486
  const obj = attributesId <= 255 ? rCtx.objectTable.get(attributesId) : void 0;
@@ -40719,25 +40749,32 @@ function paintPlusEngineRun(rCtx, ef, run) {
40719
40749
  );
40720
40750
  return true;
40721
40751
  }
40722
- function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, paint, alignment, format, brushSampler2 = null) {
40723
- if (alignment !== 0) {
40724
- return false;
40725
- }
40752
+ function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, layoutW, layoutH, paint, alignment, format, brushSampler2 = null) {
40726
40753
  const ef = realizePlusEngineFont(rCtx, font, paint, brushSampler2);
40727
40754
  if (!ef) {
40728
40755
  return false;
40729
40756
  }
40730
40757
  const { realized, emPx, m, unhinted } = ef;
40758
+ const codes = Array.from(text, (character) => character.codePointAt(0));
40759
+ if (format && (alignment !== 0 || format.lineAlignment !== 0) && unhinted && m[0] === 1 && m[3] === 1 && emPx === realized.ppem) {
40760
+ const aligned = layoutPlusAlignedString(
40761
+ realized,
40762
+ codes,
40763
+ format,
40764
+ layoutX + m[4],
40765
+ layoutY + m[5],
40766
+ layoutW,
40767
+ layoutH
40768
+ );
40769
+ if (aligned) return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, ...aligned, dy: null });
40770
+ }
40771
+ if (alignment !== 0) return false;
40731
40772
  const ttf = realized.ttf;
40732
40773
  const exactAscent = emPx * ttf.winAscent / ttf.unitsPerEm;
40733
40774
  const ascent = unhinted ? Math.round(exactAscent) : Math.ceil(exactAscent);
40734
40775
  let x = m[0] * layoutX + m[4] + emPx * (format?.leadingMargin ?? PLUS_LEADING_EM);
40735
40776
  if (!unhinted) x = Math.round(x);
40736
40777
  const y = m[3] * layoutY + m[5] + ascent;
40737
- const codes = [];
40738
- for (const character of text) {
40739
- codes.push(character.codePointAt(0));
40740
- }
40741
40778
  const tracking = format?.tracking ?? PLUS_DEFAULT_TRACKING;
40742
40779
  const dx = unhinted ? codes.map((c) => realized.advance(realized.glyphIndex(c)) * tracking) : null;
40743
40780
  return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, x, y, dx, dy: null });
@@ -40819,8 +40856,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40819
40856
  const strLen = view.getUint32(dataOff + 8, true);
40820
40857
  const layoutX = view.getFloat32(dataOff + 12, true);
40821
40858
  const layoutY = view.getFloat32(dataOff + 16, true);
40822
- view.getFloat32(dataOff + 20, true);
40823
- view.getFloat32(dataOff + 24, true);
40859
+ const layoutW = view.getFloat32(dataOff + 20, true);
40860
+ const layoutH = view.getFloat32(dataOff + 24, true);
40824
40861
  const fontId = recFlags & 255;
40825
40862
  const font = objectTable.get(fontId);
40826
40863
  if (strLen > 0 && dataOff + 28 + strLen * 2 <= dataOff + recDataSize) {
@@ -40836,6 +40873,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40836
40873
  text,
40837
40874
  layoutX,
40838
40875
  layoutY,
40876
+ layoutW,
40877
+ layoutH,
40839
40878
  paint,
40840
40879
  alignment,
40841
40880
  format,
package/dist/browser.mjs CHANGED
@@ -29074,16 +29074,11 @@ function monoBox(o) {
29074
29074
  let xMax = Math.floor((cxMax + 32) / 64);
29075
29075
  let yMin = Math.floor((cyMin + 31) / 64);
29076
29076
  let yMax = Math.floor((cyMax + 32) / 64);
29077
- const rem = (v, add) => (v + add & 63) - add;
29078
- if (xMin === xMax) {
29079
- if (rem(cxMin, 31) + rem(cxMax, 32) < 0) xMin -= 1;
29080
- else xMax += 1;
29081
- }
29082
- if (yMin === yMax) {
29083
- if (rem(cyMin, 31) + rem(cyMax, 32) < 0) yMin -= 1;
29084
- else yMax += 1;
29085
- }
29086
- return { xMin, xMax, yMin, yMax };
29077
+ const collapsedX = xMin === xMax;
29078
+ const collapsedY = yMin === yMax;
29079
+ if (collapsedX) xMax += 1;
29080
+ if (collapsedY) yMax += 1;
29081
+ return { xMin, xMax, yMin, yMax, collapsedX, collapsedY };
29087
29082
  }
29088
29083
  function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossings26Dot6 = false) {
29089
29084
  const box = fixedBox ?? monoBox(o);
@@ -29150,7 +29145,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29150
29145
  break;
29151
29146
  case 1:
29152
29147
  case 5:
29153
- const stub = monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29148
+ const stub = monoContour && box.collapsedX ? false : monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29154
29149
  if (stub === true) return;
29155
29150
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= r.half)) {
29156
29151
  return;
@@ -29211,7 +29206,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29211
29206
  break;
29212
29207
  case 1:
29213
29208
  case 5:
29214
- const stub = monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29209
+ const stub = monoContour && box.collapsedY ? false : monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29215
29210
  if (stub === true) return;
29216
29211
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= h.half)) {
29217
29212
  return;
@@ -29575,6 +29570,7 @@ var RasterRealizedFont = class {
29575
29570
  fsSelection: face.italic ? 1 : 0,
29576
29571
  macStyle: 0,
29577
29572
  winAscent: this.ascent,
29573
+ winDescent: this.descent,
29578
29574
  unitsPerEm: this.ppem || 1
29579
29575
  };
29580
29576
  }
@@ -29762,6 +29758,11 @@ var RealizedFont = class {
29762
29758
  }
29763
29759
  return base + (this.syntheticBold ? 1 : 0);
29764
29760
  }
29761
+ /** GDI+ rounds tracked hmtx advances to font units before device scaling. */
29762
+ designAdvance(index, tracking) {
29763
+ const { advance } = this.ttf.hMetrics(index < this.ttf.numGlyphs ? index : NOTDEF);
29764
+ return Math.round(advance * tracking) * this.ppemX / this.ttf.unitsPerEm + (this.syntheticBold ? 1 : 0);
29765
+ }
29765
29766
  /**
29766
29767
  * Advance of glyph `index` in a font realised with an escapement: GDI
29767
29768
  * does not use the hinted/hdmx widths there but the linearly scaled
@@ -40449,6 +40450,35 @@ function surfaceSize5(ctx) {
40449
40450
  return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
40450
40451
  }
40451
40452
 
40453
+ // src/emf-plus-text-alignment.ts
40454
+ var SIMPLE_SCRIPT = /^(?:\p{Script=Latin}|\p{Script=Common}|\p{Co})$/u;
40455
+ var SHAPING_CHARACTER = /[\p{M}\p{Cf}\p{Emoji_Presentation}]/u;
40456
+ function layoutPlusAlignedString(font, codes, format, x, y, width, height) {
40457
+ const horizontal = format.alignment, vertical = format.lineAlignment;
40458
+ if (!horizontal && !vertical || horizontal < 0 || horizontal > 2 || vertical < 0 || vertical > 2 || font.gridFit || font.mode !== "gray" || !font.designAdvance || font.ttf.winDescent === void 0 || font.syntheticBold || font.syntheticItalic || format.flags & 3 || ![x, y, width, height].every(Number.isFinite) || width <= 0 || height <= 0 || codes.some((c) => !Number.isInteger(c) || c < 32 || c > 1114111 || c >= 127 && c <= 159 || c === 8232 || c === 8233)) return null;
40459
+ if (codes.some((c) => {
40460
+ const character = String.fromCodePoint(c);
40461
+ return !SIMPLE_SCRIPT.test(character) || SHAPING_CHARACTER.test(character) || font.glyphIndex(c) === 0;
40462
+ })) return null;
40463
+ const em = font.ppem;
40464
+ const tracking = format.tracking ?? 1.03;
40465
+ const leading = em * (format.leadingMargin ?? 1 / 6);
40466
+ const trailing = em * (format.trailingMargin ?? 1 / 6);
40467
+ if (![tracking, leading, trailing].every((v) => Number.isFinite(v) && v >= 0)) return null;
40468
+ if (!(format.flags & 4) && (font.ttf.fsSelection & 1 || font.ttf.macStyle & 2)) return null;
40469
+ const dx = codes.map((c) => font.designAdvance(font.glyphIndex(c), tracking));
40470
+ let measuredCount = codes.length;
40471
+ if (!(format.flags & 2048)) while (measuredCount > 0 && codes[measuredCount - 1] === 32) measuredCount--;
40472
+ const measuredWidth = dx.slice(0, measuredCount).reduce((a, b) => a + b, 0) + leading + trailing;
40473
+ const measuredHeight = em * (font.ttf.winAscent + font.ttf.winDescent) / font.ttf.unitsPerEm + (format.flags & 4 ? 0 : em / 8);
40474
+ if (measuredWidth > width || measuredHeight > height) return null;
40475
+ return {
40476
+ x: Math.fround(x + horizontal / 2 * (width - measuredWidth) + leading),
40477
+ y: Math.fround(y + vertical / 2 * (height - measuredHeight)) + Math.round(em * font.ttf.winAscent / font.ttf.unitsPerEm),
40478
+ dx
40479
+ };
40480
+ }
40481
+
40452
40482
  // src/emf-plus-text-image-handlers.ts
40453
40483
  function imageAttributesWrap(rCtx, attributesId) {
40454
40484
  const obj = attributesId <= 255 ? rCtx.objectTable.get(attributesId) : void 0;
@@ -40717,25 +40747,32 @@ function paintPlusEngineRun(rCtx, ef, run) {
40717
40747
  );
40718
40748
  return true;
40719
40749
  }
40720
- function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, paint, alignment, format, brushSampler2 = null) {
40721
- if (alignment !== 0) {
40722
- return false;
40723
- }
40750
+ function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, layoutW, layoutH, paint, alignment, format, brushSampler2 = null) {
40724
40751
  const ef = realizePlusEngineFont(rCtx, font, paint, brushSampler2);
40725
40752
  if (!ef) {
40726
40753
  return false;
40727
40754
  }
40728
40755
  const { realized, emPx, m, unhinted } = ef;
40756
+ const codes = Array.from(text, (character) => character.codePointAt(0));
40757
+ if (format && (alignment !== 0 || format.lineAlignment !== 0) && unhinted && m[0] === 1 && m[3] === 1 && emPx === realized.ppem) {
40758
+ const aligned = layoutPlusAlignedString(
40759
+ realized,
40760
+ codes,
40761
+ format,
40762
+ layoutX + m[4],
40763
+ layoutY + m[5],
40764
+ layoutW,
40765
+ layoutH
40766
+ );
40767
+ if (aligned) return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, ...aligned, dy: null });
40768
+ }
40769
+ if (alignment !== 0) return false;
40729
40770
  const ttf = realized.ttf;
40730
40771
  const exactAscent = emPx * ttf.winAscent / ttf.unitsPerEm;
40731
40772
  const ascent = unhinted ? Math.round(exactAscent) : Math.ceil(exactAscent);
40732
40773
  let x = m[0] * layoutX + m[4] + emPx * (format?.leadingMargin ?? PLUS_LEADING_EM);
40733
40774
  if (!unhinted) x = Math.round(x);
40734
40775
  const y = m[3] * layoutY + m[5] + ascent;
40735
- const codes = [];
40736
- for (const character of text) {
40737
- codes.push(character.codePointAt(0));
40738
- }
40739
40776
  const tracking = format?.tracking ?? PLUS_DEFAULT_TRACKING;
40740
40777
  const dx = unhinted ? codes.map((c) => realized.advance(realized.glyphIndex(c)) * tracking) : null;
40741
40778
  return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, x, y, dx, dy: null });
@@ -40817,8 +40854,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40817
40854
  const strLen = view.getUint32(dataOff + 8, true);
40818
40855
  const layoutX = view.getFloat32(dataOff + 12, true);
40819
40856
  const layoutY = view.getFloat32(dataOff + 16, true);
40820
- view.getFloat32(dataOff + 20, true);
40821
- view.getFloat32(dataOff + 24, true);
40857
+ const layoutW = view.getFloat32(dataOff + 20, true);
40858
+ const layoutH = view.getFloat32(dataOff + 24, true);
40822
40859
  const fontId = recFlags & 255;
40823
40860
  const font = objectTable.get(fontId);
40824
40861
  if (strLen > 0 && dataOff + 28 + strLen * 2 <= dataOff + recDataSize) {
@@ -40834,6 +40871,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40834
40871
  text,
40835
40872
  layoutX,
40836
40873
  layoutY,
40874
+ layoutW,
40875
+ layoutH,
40837
40876
  paint,
40838
40877
  alignment,
40839
40878
  format,
package/dist/index.js CHANGED
@@ -29077,16 +29077,11 @@ function monoBox(o) {
29077
29077
  let xMax = Math.floor((cxMax + 32) / 64);
29078
29078
  let yMin = Math.floor((cyMin + 31) / 64);
29079
29079
  let yMax = Math.floor((cyMax + 32) / 64);
29080
- const rem = (v, add) => (v + add & 63) - add;
29081
- if (xMin === xMax) {
29082
- if (rem(cxMin, 31) + rem(cxMax, 32) < 0) xMin -= 1;
29083
- else xMax += 1;
29084
- }
29085
- if (yMin === yMax) {
29086
- if (rem(cyMin, 31) + rem(cyMax, 32) < 0) yMin -= 1;
29087
- else yMax += 1;
29088
- }
29089
- return { xMin, xMax, yMin, yMax };
29080
+ const collapsedX = xMin === xMax;
29081
+ const collapsedY = yMin === yMax;
29082
+ if (collapsedX) xMax += 1;
29083
+ if (collapsedY) yMax += 1;
29084
+ return { xMin, xMax, yMin, yMax, collapsedX, collapsedY };
29090
29085
  }
29091
29086
  function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossings26Dot6 = false) {
29092
29087
  const box = fixedBox ?? monoBox(o);
@@ -29153,7 +29148,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29153
29148
  break;
29154
29149
  case 1:
29155
29150
  case 5:
29156
- const stub = monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29151
+ const stub = monoContour && box.collapsedX ? false : monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29157
29152
  if (stub === true) return;
29158
29153
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= r.half)) {
29159
29154
  return;
@@ -29214,7 +29209,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29214
29209
  break;
29215
29210
  case 1:
29216
29211
  case 5:
29217
- const stub = monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29212
+ const stub = monoContour && box.collapsedY ? false : monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29218
29213
  if (stub === true) return;
29219
29214
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= h.half)) {
29220
29215
  return;
@@ -29578,6 +29573,7 @@ var RasterRealizedFont = class {
29578
29573
  fsSelection: face.italic ? 1 : 0,
29579
29574
  macStyle: 0,
29580
29575
  winAscent: this.ascent,
29576
+ winDescent: this.descent,
29581
29577
  unitsPerEm: this.ppem || 1
29582
29578
  };
29583
29579
  }
@@ -29765,6 +29761,11 @@ var RealizedFont = class {
29765
29761
  }
29766
29762
  return base + (this.syntheticBold ? 1 : 0);
29767
29763
  }
29764
+ /** GDI+ rounds tracked hmtx advances to font units before device scaling. */
29765
+ designAdvance(index, tracking) {
29766
+ const { advance } = this.ttf.hMetrics(index < this.ttf.numGlyphs ? index : NOTDEF);
29767
+ return Math.round(advance * tracking) * this.ppemX / this.ttf.unitsPerEm + (this.syntheticBold ? 1 : 0);
29768
+ }
29768
29769
  /**
29769
29770
  * Advance of glyph `index` in a font realised with an escapement: GDI
29770
29771
  * does not use the hinted/hdmx widths there but the linearly scaled
@@ -40452,6 +40453,35 @@ function surfaceSize5(ctx) {
40452
40453
  return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
40453
40454
  }
40454
40455
 
40456
+ // src/emf-plus-text-alignment.ts
40457
+ var SIMPLE_SCRIPT = /^(?:\p{Script=Latin}|\p{Script=Common}|\p{Co})$/u;
40458
+ var SHAPING_CHARACTER = /[\p{M}\p{Cf}\p{Emoji_Presentation}]/u;
40459
+ function layoutPlusAlignedString(font, codes, format, x, y, width, height) {
40460
+ const horizontal = format.alignment, vertical = format.lineAlignment;
40461
+ if (!horizontal && !vertical || horizontal < 0 || horizontal > 2 || vertical < 0 || vertical > 2 || font.gridFit || font.mode !== "gray" || !font.designAdvance || font.ttf.winDescent === void 0 || font.syntheticBold || font.syntheticItalic || format.flags & 3 || ![x, y, width, height].every(Number.isFinite) || width <= 0 || height <= 0 || codes.some((c) => !Number.isInteger(c) || c < 32 || c > 1114111 || c >= 127 && c <= 159 || c === 8232 || c === 8233)) return null;
40462
+ if (codes.some((c) => {
40463
+ const character = String.fromCodePoint(c);
40464
+ return !SIMPLE_SCRIPT.test(character) || SHAPING_CHARACTER.test(character) || font.glyphIndex(c) === 0;
40465
+ })) return null;
40466
+ const em = font.ppem;
40467
+ const tracking = format.tracking ?? 1.03;
40468
+ const leading = em * (format.leadingMargin ?? 1 / 6);
40469
+ const trailing = em * (format.trailingMargin ?? 1 / 6);
40470
+ if (![tracking, leading, trailing].every((v) => Number.isFinite(v) && v >= 0)) return null;
40471
+ if (!(format.flags & 4) && (font.ttf.fsSelection & 1 || font.ttf.macStyle & 2)) return null;
40472
+ const dx = codes.map((c) => font.designAdvance(font.glyphIndex(c), tracking));
40473
+ let measuredCount = codes.length;
40474
+ if (!(format.flags & 2048)) while (measuredCount > 0 && codes[measuredCount - 1] === 32) measuredCount--;
40475
+ const measuredWidth = dx.slice(0, measuredCount).reduce((a, b) => a + b, 0) + leading + trailing;
40476
+ const measuredHeight = em * (font.ttf.winAscent + font.ttf.winDescent) / font.ttf.unitsPerEm + (format.flags & 4 ? 0 : em / 8);
40477
+ if (measuredWidth > width || measuredHeight > height) return null;
40478
+ return {
40479
+ x: Math.fround(x + horizontal / 2 * (width - measuredWidth) + leading),
40480
+ y: Math.fround(y + vertical / 2 * (height - measuredHeight)) + Math.round(em * font.ttf.winAscent / font.ttf.unitsPerEm),
40481
+ dx
40482
+ };
40483
+ }
40484
+
40455
40485
  // src/emf-plus-text-image-handlers.ts
40456
40486
  function imageAttributesWrap(rCtx, attributesId) {
40457
40487
  const obj = attributesId <= 255 ? rCtx.objectTable.get(attributesId) : void 0;
@@ -40720,25 +40750,32 @@ function paintPlusEngineRun(rCtx, ef, run) {
40720
40750
  );
40721
40751
  return true;
40722
40752
  }
40723
- function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, paint, alignment, format, brushSampler2 = null) {
40724
- if (alignment !== 0) {
40725
- return false;
40726
- }
40753
+ function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, layoutW, layoutH, paint, alignment, format, brushSampler2 = null) {
40727
40754
  const ef = realizePlusEngineFont(rCtx, font, paint, brushSampler2);
40728
40755
  if (!ef) {
40729
40756
  return false;
40730
40757
  }
40731
40758
  const { realized, emPx, m, unhinted } = ef;
40759
+ const codes = Array.from(text, (character) => character.codePointAt(0));
40760
+ if (format && (alignment !== 0 || format.lineAlignment !== 0) && unhinted && m[0] === 1 && m[3] === 1 && emPx === realized.ppem) {
40761
+ const aligned = layoutPlusAlignedString(
40762
+ realized,
40763
+ codes,
40764
+ format,
40765
+ layoutX + m[4],
40766
+ layoutY + m[5],
40767
+ layoutW,
40768
+ layoutH
40769
+ );
40770
+ if (aligned) return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, ...aligned, dy: null });
40771
+ }
40772
+ if (alignment !== 0) return false;
40732
40773
  const ttf = realized.ttf;
40733
40774
  const exactAscent = emPx * ttf.winAscent / ttf.unitsPerEm;
40734
40775
  const ascent = unhinted ? Math.round(exactAscent) : Math.ceil(exactAscent);
40735
40776
  let x = m[0] * layoutX + m[4] + emPx * (format?.leadingMargin ?? PLUS_LEADING_EM);
40736
40777
  if (!unhinted) x = Math.round(x);
40737
40778
  const y = m[3] * layoutY + m[5] + ascent;
40738
- const codes = [];
40739
- for (const character of text) {
40740
- codes.push(character.codePointAt(0));
40741
- }
40742
40779
  const tracking = format?.tracking ?? PLUS_DEFAULT_TRACKING;
40743
40780
  const dx = unhinted ? codes.map((c) => realized.advance(realized.glyphIndex(c)) * tracking) : null;
40744
40781
  return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, x, y, dx, dy: null });
@@ -40820,8 +40857,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40820
40857
  const strLen = view.getUint32(dataOff + 8, true);
40821
40858
  const layoutX = view.getFloat32(dataOff + 12, true);
40822
40859
  const layoutY = view.getFloat32(dataOff + 16, true);
40823
- view.getFloat32(dataOff + 20, true);
40824
- view.getFloat32(dataOff + 24, true);
40860
+ const layoutW = view.getFloat32(dataOff + 20, true);
40861
+ const layoutH = view.getFloat32(dataOff + 24, true);
40825
40862
  const fontId = recFlags & 255;
40826
40863
  const font = objectTable.get(fontId);
40827
40864
  if (strLen > 0 && dataOff + 28 + strLen * 2 <= dataOff + recDataSize) {
@@ -40837,6 +40874,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40837
40874
  text,
40838
40875
  layoutX,
40839
40876
  layoutY,
40877
+ layoutW,
40878
+ layoutH,
40840
40879
  paint,
40841
40880
  alignment,
40842
40881
  format,
package/dist/index.mjs CHANGED
@@ -29075,16 +29075,11 @@ function monoBox(o) {
29075
29075
  let xMax = Math.floor((cxMax + 32) / 64);
29076
29076
  let yMin = Math.floor((cyMin + 31) / 64);
29077
29077
  let yMax = Math.floor((cyMax + 32) / 64);
29078
- const rem = (v, add) => (v + add & 63) - add;
29079
- if (xMin === xMax) {
29080
- if (rem(cxMin, 31) + rem(cxMax, 32) < 0) xMin -= 1;
29081
- else xMax += 1;
29082
- }
29083
- if (yMin === yMax) {
29084
- if (rem(cyMin, 31) + rem(cyMax, 32) < 0) yMin -= 1;
29085
- else yMax += 1;
29086
- }
29087
- return { xMin, xMax, yMin, yMax };
29078
+ const collapsedX = xMin === xMax;
29079
+ const collapsedY = yMin === yMax;
29080
+ if (collapsedX) xMax += 1;
29081
+ if (collapsedY) yMax += 1;
29082
+ return { xMin, xMax, yMin, yMax, collapsedX, collapsedY };
29088
29083
  }
29089
29084
  function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossings26Dot6 = false) {
29090
29085
  const box = fixedBox ?? monoBox(o);
@@ -29151,7 +29146,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29151
29146
  break;
29152
29147
  case 1:
29153
29148
  case 5:
29154
- const stub = monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29149
+ const stub = monoContour && box.collapsedX ? false : monoContour?.isStub(false, y, x1 / P, e2 / P, e1 / P, P);
29155
29150
  if (stub === true) return;
29156
29151
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= r.half)) {
29157
29152
  return;
@@ -29212,7 +29207,7 @@ function rasterizeMono(o, dropout, fixedBox, stubOvershoot = true, smartCrossing
29212
29207
  break;
29213
29208
  case 1:
29214
29209
  case 5:
29215
- const stub = monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29210
+ const stub = monoContour && box.collapsedY ? false : monoContour?.isStub(true, col, x1 / P, e2 / P, e1 / P, P);
29216
29211
  if (stub === true) return;
29217
29212
  if (stub == null && left.next === right && left.height <= 0 && !(stubOvershoot && left.flags & OVERSHOOT_TOP && x2 - x1 >= h.half)) {
29218
29213
  return;
@@ -29576,6 +29571,7 @@ var RasterRealizedFont = class {
29576
29571
  fsSelection: face.italic ? 1 : 0,
29577
29572
  macStyle: 0,
29578
29573
  winAscent: this.ascent,
29574
+ winDescent: this.descent,
29579
29575
  unitsPerEm: this.ppem || 1
29580
29576
  };
29581
29577
  }
@@ -29763,6 +29759,11 @@ var RealizedFont = class {
29763
29759
  }
29764
29760
  return base + (this.syntheticBold ? 1 : 0);
29765
29761
  }
29762
+ /** GDI+ rounds tracked hmtx advances to font units before device scaling. */
29763
+ designAdvance(index, tracking) {
29764
+ const { advance } = this.ttf.hMetrics(index < this.ttf.numGlyphs ? index : NOTDEF);
29765
+ return Math.round(advance * tracking) * this.ppemX / this.ttf.unitsPerEm + (this.syntheticBold ? 1 : 0);
29766
+ }
29766
29767
  /**
29767
29768
  * Advance of glyph `index` in a font realised with an escapement: GDI
29768
29769
  * does not use the hinted/hdmx widths there but the linearly scaled
@@ -40450,6 +40451,35 @@ function surfaceSize5(ctx) {
40450
40451
  return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
40451
40452
  }
40452
40453
 
40454
+ // src/emf-plus-text-alignment.ts
40455
+ var SIMPLE_SCRIPT = /^(?:\p{Script=Latin}|\p{Script=Common}|\p{Co})$/u;
40456
+ var SHAPING_CHARACTER = /[\p{M}\p{Cf}\p{Emoji_Presentation}]/u;
40457
+ function layoutPlusAlignedString(font, codes, format, x, y, width, height) {
40458
+ const horizontal = format.alignment, vertical = format.lineAlignment;
40459
+ if (!horizontal && !vertical || horizontal < 0 || horizontal > 2 || vertical < 0 || vertical > 2 || font.gridFit || font.mode !== "gray" || !font.designAdvance || font.ttf.winDescent === void 0 || font.syntheticBold || font.syntheticItalic || format.flags & 3 || ![x, y, width, height].every(Number.isFinite) || width <= 0 || height <= 0 || codes.some((c) => !Number.isInteger(c) || c < 32 || c > 1114111 || c >= 127 && c <= 159 || c === 8232 || c === 8233)) return null;
40460
+ if (codes.some((c) => {
40461
+ const character = String.fromCodePoint(c);
40462
+ return !SIMPLE_SCRIPT.test(character) || SHAPING_CHARACTER.test(character) || font.glyphIndex(c) === 0;
40463
+ })) return null;
40464
+ const em = font.ppem;
40465
+ const tracking = format.tracking ?? 1.03;
40466
+ const leading = em * (format.leadingMargin ?? 1 / 6);
40467
+ const trailing = em * (format.trailingMargin ?? 1 / 6);
40468
+ if (![tracking, leading, trailing].every((v) => Number.isFinite(v) && v >= 0)) return null;
40469
+ if (!(format.flags & 4) && (font.ttf.fsSelection & 1 || font.ttf.macStyle & 2)) return null;
40470
+ const dx = codes.map((c) => font.designAdvance(font.glyphIndex(c), tracking));
40471
+ let measuredCount = codes.length;
40472
+ if (!(format.flags & 2048)) while (measuredCount > 0 && codes[measuredCount - 1] === 32) measuredCount--;
40473
+ const measuredWidth = dx.slice(0, measuredCount).reduce((a, b) => a + b, 0) + leading + trailing;
40474
+ const measuredHeight = em * (font.ttf.winAscent + font.ttf.winDescent) / font.ttf.unitsPerEm + (format.flags & 4 ? 0 : em / 8);
40475
+ if (measuredWidth > width || measuredHeight > height) return null;
40476
+ return {
40477
+ x: Math.fround(x + horizontal / 2 * (width - measuredWidth) + leading),
40478
+ y: Math.fround(y + vertical / 2 * (height - measuredHeight)) + Math.round(em * font.ttf.winAscent / font.ttf.unitsPerEm),
40479
+ dx
40480
+ };
40481
+ }
40482
+
40453
40483
  // src/emf-plus-text-image-handlers.ts
40454
40484
  function imageAttributesWrap(rCtx, attributesId) {
40455
40485
  const obj = attributesId <= 255 ? rCtx.objectTable.get(attributesId) : void 0;
@@ -40718,25 +40748,32 @@ function paintPlusEngineRun(rCtx, ef, run) {
40718
40748
  );
40719
40749
  return true;
40720
40750
  }
40721
- function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, paint, alignment, format, brushSampler2 = null) {
40722
- if (alignment !== 0) {
40723
- return false;
40724
- }
40751
+ function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, layoutW, layoutH, paint, alignment, format, brushSampler2 = null) {
40725
40752
  const ef = realizePlusEngineFont(rCtx, font, paint, brushSampler2);
40726
40753
  if (!ef) {
40727
40754
  return false;
40728
40755
  }
40729
40756
  const { realized, emPx, m, unhinted } = ef;
40757
+ const codes = Array.from(text, (character) => character.codePointAt(0));
40758
+ if (format && (alignment !== 0 || format.lineAlignment !== 0) && unhinted && m[0] === 1 && m[3] === 1 && emPx === realized.ppem) {
40759
+ const aligned = layoutPlusAlignedString(
40760
+ realized,
40761
+ codes,
40762
+ format,
40763
+ layoutX + m[4],
40764
+ layoutY + m[5],
40765
+ layoutW,
40766
+ layoutH
40767
+ );
40768
+ if (aligned) return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, ...aligned, dy: null });
40769
+ }
40770
+ if (alignment !== 0) return false;
40730
40771
  const ttf = realized.ttf;
40731
40772
  const exactAscent = emPx * ttf.winAscent / ttf.unitsPerEm;
40732
40773
  const ascent = unhinted ? Math.round(exactAscent) : Math.ceil(exactAscent);
40733
40774
  let x = m[0] * layoutX + m[4] + emPx * (format?.leadingMargin ?? PLUS_LEADING_EM);
40734
40775
  if (!unhinted) x = Math.round(x);
40735
40776
  const y = m[3] * layoutY + m[5] + ascent;
40736
- const codes = [];
40737
- for (const character of text) {
40738
- codes.push(character.codePointAt(0));
40739
- }
40740
40777
  const tracking = format?.tracking ?? PLUS_DEFAULT_TRACKING;
40741
40778
  const dx = unhinted ? codes.map((c) => realized.advance(realized.glyphIndex(c)) * tracking) : null;
40742
40779
  return paintPlusEngineRun(rCtx, ef, { codes, glyphIndices: false, x, y, dx, dy: null });
@@ -40818,8 +40855,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40818
40855
  const strLen = view.getUint32(dataOff + 8, true);
40819
40856
  const layoutX = view.getFloat32(dataOff + 12, true);
40820
40857
  const layoutY = view.getFloat32(dataOff + 16, true);
40821
- view.getFloat32(dataOff + 20, true);
40822
- view.getFloat32(dataOff + 24, true);
40858
+ const layoutW = view.getFloat32(dataOff + 20, true);
40859
+ const layoutH = view.getFloat32(dataOff + 24, true);
40823
40860
  const fontId = recFlags & 255;
40824
40861
  const font = objectTable.get(fontId);
40825
40862
  if (strLen > 0 && dataOff + 28 + strLen * 2 <= dataOff + recDataSize) {
@@ -40835,6 +40872,8 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
40835
40872
  text,
40836
40873
  layoutX,
40837
40874
  layoutY,
40875
+ layoutW,
40876
+ layoutH,
40838
40877
  paint,
40839
40878
  alignment,
40840
40879
  format,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emf-converter",
3
- "version": "4.20.1",
3
+ "version": "4.20.2",
4
4
  "description": "Convert EMF/WMF metafile binaries to PNG or JPEG data URLs, or SVG (markup, data URL, React/JSX)",
5
5
  "keywords": [
6
6
  "canvas",