reamkit 1.18.0 → 1.19.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.
@@ -992,6 +992,12 @@ export interface ShapeFill {
992
992
  readonly kind: ShapeFillKind;
993
993
  readonly colorHex?: string;
994
994
  readonly gradient?: ShapeGradient;
995
+ /**
996
+ * §14.1.2.5 `@type="tile"` — the picture REPEATS at its own size rather than
997
+ * stretching over the box. A parchment behind a page, a texture behind a
998
+ * text box: stretched, such a fill is a blur where it should be a pattern.
999
+ */
1000
+ readonly tiled?: boolean;
995
1001
  /**
996
1002
  * §20.1.8.14 `a:blipFill` — the picture painted across the shape's box. A
997
1003
  * DrawingML picture IS a shape with one of these, which is how a `pic:pic`
@@ -69,6 +69,13 @@ function hslToRgb(h, s, l) {
69
69
  b + m
70
70
  ];
71
71
  }
72
+ function toLinear(c) {
73
+ return c <= .04045 ? c / 12.92 : Math.pow((c + .055) / 1.055, 2.4);
74
+ }
75
+ function toSrgb(c) {
76
+ const v = clamp01(c);
77
+ return v <= .0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - .055;
78
+ }
72
79
  /**
73
80
  * Apply DrawingML colour transforms ({@link ColorMod}s) to a 6-hex value,
74
81
  * returning a 6-hex value. `shade`/`tint` scale in RGB; `lumMod`/`lumOff` adjust
@@ -85,13 +92,13 @@ function applyColorMods(hex, mods) {
85
92
  let g = (n >> 8 & 255) / 255;
86
93
  let b = (n & 255) / 255;
87
94
  for (const m of mods) if (m.kind === "shade") {
88
- r *= m.val;
89
- g *= m.val;
90
- b *= m.val;
95
+ r = toSrgb(toLinear(r) * m.val);
96
+ g = toSrgb(toLinear(g) * m.val);
97
+ b = toSrgb(toLinear(b) * m.val);
91
98
  } else if (m.kind === "tint") {
92
- r = r * m.val + (1 - m.val);
93
- g = g * m.val + (1 - m.val);
94
- b = b * m.val + (1 - m.val);
99
+ r = toSrgb(toLinear(r) * m.val + (1 - m.val));
100
+ g = toSrgb(toLinear(g) * m.val + (1 - m.val));
101
+ b = toSrgb(toLinear(b) * m.val + (1 - m.val));
95
102
  } else if (m.kind === "lumMod" || m.kind === "lumOff") {
96
103
  const [h, s, l] = rgbToHsl(r, g, b);
97
104
  const l2 = m.kind === "lumMod" ? l * m.val : clamp01(l + m.val);
@@ -34,6 +34,15 @@ export declare function poAttrLocal(node: PoNode | undefined, name: string): str
34
34
  export declare function poIsLocal(node: PoNode | undefined, name: string): boolean;
35
35
  /** {@link poAttr} parsed as a finite number, or undefined when absent/non-numeric. */
36
36
  export declare function poIntAttr(node: PoNode | undefined, name: string): number | undefined;
37
+ /**
38
+ * §22.9.2.15 ST_UniversalMeasure — a number with a UNIT (`"28.35pt"`, `"1cm"`)
39
+ * as twips. Producers write whole page geometries, tab stops and indents this
40
+ * way; read as a plain number they are nothing at all.
41
+ *
42
+ * @param raw The attribute value.
43
+ * @returns The measure in twips, or undefined when it names no unit.
44
+ */
45
+ export declare function universalMeasureTwips(raw: string): number | undefined;
37
46
  /** Shorthand for the `val` attribute (`@w:val`/…). */
38
47
  export declare function poVal(node: PoNode | undefined): string | undefined;
39
48
  /**
@@ -76,6 +76,14 @@ var MEASURE_TWIPS = new Map([
76
76
  ["cm", 1440 / 2.54],
77
77
  ["mm", 144 / 2.54]
78
78
  ]);
79
+ /**
80
+ * §22.9.2.15 ST_UniversalMeasure — a number with a UNIT (`"28.35pt"`, `"1cm"`)
81
+ * as twips. Producers write whole page geometries, tab stops and indents this
82
+ * way; read as a plain number they are nothing at all.
83
+ *
84
+ * @param raw The attribute value.
85
+ * @returns The measure in twips, or undefined when it names no unit.
86
+ */
79
87
  function universalMeasureTwips(raw) {
80
88
  const m = /^\s*(-?[0-9]+(?:\.[0-9]+)?)(pt|pc|pi|in|cm|mm)\s*$/iu.exec(raw);
81
89
  if (!m) return void 0;
@@ -134,4 +142,4 @@ function poFindDescendant(node, tag) {
134
142
  }
135
143
  }
136
144
  //#endregion
137
- export { poAttr, poAttrLocal, poChildren, poChildrenWith, poFindByPath, poFindDescendant, poFirstChild, poIntAttr, poIs, poIsLocal, poTag, poText, poToggle, poVal };
145
+ export { poAttr, poAttrLocal, poChildren, poChildrenWith, poFindByPath, poFindDescendant, poFirstChild, poIntAttr, poIs, poIsLocal, poTag, poText, poToggle, poVal, universalMeasureTwips };
@@ -432,7 +432,7 @@ function layoutStyledDocument(body, options) {
432
432
  const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
433
433
  const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
434
434
  return {
435
- pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, options.pageBackgroundFill?.imageResource !== void 0 ? imageResources.get(options.pageBackgroundFill.imageResource)?.resourceName : void 0),
435
+ pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources)),
436
436
  resources: options.resources ?? new ResourceStore(),
437
437
  fontResources,
438
438
  imageResources,
@@ -609,7 +609,7 @@ function balancedColumnHeight(bandHeightPt, colCount, roomPt) {
609
609
  /** Total laid-out height of a run of blocks, paragraph spacing included. */
610
610
  function blocksHeight(blocks) {
611
611
  return blocks.reduce((sum, b) => {
612
- if (b.kind !== "paragraph" && b.kind !== "table" && isOutOfFlowFloat(b.float)) return sum;
612
+ if (b.kind !== "paragraph" && isOutOfFlowFloat(b.float)) return sum;
613
613
  return sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : b.heightPt);
614
614
  }, 0);
615
615
  }
@@ -1024,7 +1024,12 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1024
1024
  heightPt = maxHeight;
1025
1025
  }
1026
1026
  const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
1027
- const fillImageResourceName = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource)?.resourceName : void 0;
1027
+ const fillImage = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource) : void 0;
1028
+ const fillImageResourceName = fillImage?.resourceName;
1029
+ const fillImageTile = shape.fill.kind === "picture" && shape.fill.tiled === true && fillImage?.prepared ? {
1030
+ widthPt: fillImage.prepared.widthPx * 72 / 96,
1031
+ heightPt: fillImage.prepared.heightPx * 72 / 96
1032
+ } : void 0;
1028
1033
  const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
1029
1034
  const stroke = buildStroke(shape.line);
1030
1035
  const t = shape.transform;
@@ -1178,6 +1183,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1178
1183
  paths,
1179
1184
  ...markerPaths.length > 0 ? { markerPaths } : {},
1180
1185
  ...fillImageResourceName ? { fillImageResourceName } : {},
1186
+ ...fillImageTile ? { fillImageTile } : {},
1181
1187
  ...fillImageResourceName && shape.fill.imageCrop ? { fillImageCrop: shape.fill.imageCrop } : {},
1182
1188
  ...fillColorHex ? { fillColorHex } : {},
1183
1189
  ...fillGradient ? { fillGradient } : {},
@@ -1612,6 +1618,21 @@ function makeChartLabelLine(text, font, sizePt, colorHex) {
1612
1618
  isLastInParagraph: true
1613
1619
  };
1614
1620
  }
1621
+ /**
1622
+ * §17.2.1 — the page background's picture, with the size a TILE of it is drawn
1623
+ * at: the picture's own pixels read at 96 dpi, which is the resolution Office
1624
+ * measures a picture in.
1625
+ */
1626
+ function backgroundImageOf(options, imageResources) {
1627
+ const id = options.pageBackgroundFill?.imageResource;
1628
+ const res = id === void 0 ? void 0 : imageResources.get(id);
1629
+ if (!res?.resourceName || !res.prepared) return void 0;
1630
+ return {
1631
+ name: res.resourceName,
1632
+ widthPt: res.prepared.widthPx * 72 / 96,
1633
+ heightPt: res.prepared.heightPx * 72 / 96
1634
+ };
1635
+ }
1615
1636
  function collectImageResources(body, headersFooters, options) {
1616
1637
  const out = /* @__PURE__ */ new Map();
1617
1638
  if (!options.resources || options.resources.size === 0) return out;
@@ -1794,20 +1815,38 @@ function emitCellFloat(cf, ctx, frame) {
1794
1815
  if (float.wrap !== "none") ctx.exclude(float, x, topYUp, cf.block.widthPt, cf.block.heightPt);
1795
1816
  }
1796
1817
  function emitShapeItems(sh, x, bottomYUp, sink, pageHeight, figId) {
1797
- if (sh.fillImageResourceName) sink.push({
1798
- type: "image",
1799
- x: pt(x),
1800
- y: pt(pageHeight - (bottomYUp + sh.heightPt)),
1801
- width: pt(sh.widthPt),
1802
- height: pt(sh.heightPt),
1803
- imageResourceName: sh.fillImageResourceName,
1804
- ...sh.fillImageCrop ? { crop: sh.fillImageCrop } : {},
1805
- clip: {
1818
+ if (sh.fillImageResourceName) {
1819
+ const clip = {
1806
1820
  paths: sh.paths,
1807
1821
  transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
1808
- },
1809
- ...figId !== void 0 ? { structId: figId } : {}
1810
- });
1822
+ };
1823
+ const fig = figId !== void 0 ? { structId: figId } : {};
1824
+ const tile = sh.fillImageTile;
1825
+ if (tile) for (const item of PageAssembler.tileItems(sh.fillImageResourceName, tile, {
1826
+ x,
1827
+ y: pageHeight - (bottomYUp + sh.heightPt),
1828
+ width: sh.widthPt,
1829
+ height: sh.heightPt
1830
+ })) {
1831
+ if (item.type !== "image") continue;
1832
+ sink.push({
1833
+ ...item,
1834
+ clip,
1835
+ ...fig
1836
+ });
1837
+ }
1838
+ else sink.push({
1839
+ type: "image",
1840
+ x: pt(x),
1841
+ y: pt(pageHeight - (bottomYUp + sh.heightPt)),
1842
+ width: pt(sh.widthPt),
1843
+ height: pt(sh.heightPt),
1844
+ imageResourceName: sh.fillImageResourceName,
1845
+ ...sh.fillImageCrop ? { crop: sh.fillImageCrop } : {},
1846
+ clip,
1847
+ ...fig
1848
+ });
1849
+ }
1811
1850
  if (sh.markerPaths && sh.stroke) sink.push({
1812
1851
  type: "shape",
1813
1852
  shape: {
@@ -1906,6 +1945,15 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
1906
1945
  const ordered = blocks.some(isBehind) ? [...blocks.filter(isBehind), ...blocks.filter((b) => !isBehind(b))] : blocks;
1907
1946
  for (const block of ordered) {
1908
1947
  if (block.kind === "table") {
1948
+ const anchored = isOutOfFlowFloat(block.float) ? bandAnchor(block.float, block.totalWidthPt, contentWidth, startX) : void 0;
1949
+ if (anchored !== void 0 && block.float) {
1950
+ let rowY = bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
1951
+ for (const row of block.rows) {
1952
+ emitRowChunk(out, row, startX + anchored, rowY, pageHeight, block.colCount);
1953
+ rowY -= row.heightPt;
1954
+ }
1955
+ continue;
1956
+ }
1909
1957
  const tableX = startX + block.xOffsetPt;
1910
1958
  for (const row of block.rows) {
1911
1959
  emitRowChunk(out, row, tableX, cursorY, pageHeight, block.colCount);
@@ -3211,9 +3259,67 @@ function alignmentOffset(alignment, lineWidth, available) {
3211
3259
  if (alignment === "center") return Math.max(0, (available - lineWidth) / 2);
3212
3260
  return 0;
3213
3261
  }
3262
+ /**
3263
+ * §17.4.85 `w:vMerge` — a vertical merge is ONE cell, so its continuation rows
3264
+ * are painted with the START cell's shading. Word writes the fill on the cell
3265
+ * that opens the merge and nothing on the rest; painting only what is written,
3266
+ * tdf123104.docx's three-row banner came out a staircase of yellow with white
3267
+ * notches where every merged cell continued.
3268
+ */
3269
+ function withMergedShading(table, roles) {
3270
+ const columnOf = table.rows.map((row) => {
3271
+ const at = [];
3272
+ let col = row.properties.gridBefore ?? 0;
3273
+ for (const cell of row.cells) {
3274
+ at.push(col);
3275
+ col += Math.max(1, cell.properties.colSpan ?? 1);
3276
+ }
3277
+ return at;
3278
+ });
3279
+ let changed = false;
3280
+ const rows = table.rows.map((row, r) => {
3281
+ const cells = row.cells.map((cell, c) => {
3282
+ const role = roles[r]?.[c] ?? "standalone";
3283
+ if (role !== "middle" && role !== "end") return cell;
3284
+ if (cell.properties.shading?.colorHex) return cell;
3285
+ const col = columnOf[r]?.[c];
3286
+ if (col === void 0) return cell;
3287
+ for (let up = r - 1; up >= 0; up--) {
3288
+ const idx = columnOf[up]?.indexOf(col) ?? -1;
3289
+ if (idx < 0) break;
3290
+ const above = table.rows[up]?.cells[idx];
3291
+ const aboveRole = roles[up]?.[idx] ?? "standalone";
3292
+ if (!above) break;
3293
+ if (aboveRole === "start") {
3294
+ const shading = above.properties.shading;
3295
+ if (!shading?.colorHex) break;
3296
+ changed = true;
3297
+ return {
3298
+ ...cell,
3299
+ properties: {
3300
+ ...cell.properties,
3301
+ shading
3302
+ }
3303
+ };
3304
+ }
3305
+ if (aboveRole !== "middle") break;
3306
+ }
3307
+ return cell;
3308
+ });
3309
+ return {
3310
+ ...row,
3311
+ cells
3312
+ };
3313
+ });
3314
+ return changed ? {
3315
+ ...table,
3316
+ rows
3317
+ } : table;
3318
+ }
3214
3319
  function layoutTableBlock(table, options, fontResources, imageResources, contentWidth) {
3215
3320
  const columnWidthsPt = computeColumnWidths(table, options, fontResources, contentWidth);
3216
3321
  const mergeRoles = table.rows.map((r) => r.cells.map((c) => c.properties.merge ?? "standalone"));
3322
+ table = withMergedShading(table, mergeRoles);
3217
3323
  const rows = [];
3218
3324
  const colCount = columnWidthsPt.length;
3219
3325
  let aboveBordersByCol = [];
@@ -3792,12 +3898,12 @@ var PageAssembler = class PageAssembler {
3792
3898
  * `undefined` when there are none.
3793
3899
  * @param bookmarkPositions Out-param: bookmark name → its GoTo destination.
3794
3900
  */
3795
- constructor(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName) {
3901
+ constructor(sectionCtxs, builder, notes, bookmarkPositions, backgroundImage) {
3796
3902
  this.sectionCtxs = sectionCtxs;
3797
3903
  this.builder = builder;
3798
3904
  this.notes = notes;
3799
3905
  this.bookmarkPositions = bookmarkPositions;
3800
- this.backgroundImageName = backgroundImageName;
3906
+ this.backgroundImage = backgroundImage;
3801
3907
  this.ctx = sectionCtxs[0];
3802
3908
  this.pageStartCtx = this.ctx;
3803
3909
  this.pageNumber = this.ctx.properties.pageNumberStart ?? 1;
@@ -4305,19 +4411,49 @@ var PageAssembler = class PageAssembler {
4305
4411
  *
4306
4412
  * @returns The background items for this page (at most one).
4307
4413
  */
4414
+ /**
4415
+ * §14.1.2.5 `@type="tile"` — the picture repeated over a box at its own size,
4416
+ * as image items. A tile is a texture, so its natural size is what makes it
4417
+ * one: stretched over the page it is a blur, and its 128 pixels are 96 points
4418
+ * at the resolution Office measures a picture in.
4419
+ */
4420
+ static tileItems = (resourceName, natural, box) => {
4421
+ const tw = Math.max(1, natural.widthPt);
4422
+ const th = Math.max(1, natural.heightPt);
4423
+ const cols = Math.min(200, Math.ceil(box.width / tw));
4424
+ const rows = Math.min(200, Math.ceil(box.height / th));
4425
+ const out = [];
4426
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) out.push({
4427
+ type: "image",
4428
+ x: pt(box.x + c * tw),
4429
+ y: pt(box.y + r * th),
4430
+ width: pt(tw),
4431
+ height: pt(th),
4432
+ imageResourceName: resourceName
4433
+ });
4434
+ return out;
4435
+ };
4308
4436
  pageBackgroundItems = () => {
4309
4437
  const options = this.ctx.options;
4310
4438
  const w = this.ctx.pageWidth;
4311
4439
  const h = this.ctx.pageHeight;
4312
4440
  const fill = options?.pageBackgroundFill;
4313
- if (fill?.kind === "picture" && this.backgroundImageName !== void 0) return [{
4314
- type: "image",
4315
- x: pt(0),
4316
- y: pt(0),
4317
- width: pt(w),
4318
- height: pt(h),
4319
- imageResourceName: this.backgroundImageName
4320
- }];
4441
+ if (fill?.kind === "picture" && this.backgroundImage !== void 0) {
4442
+ const bg = this.backgroundImage;
4443
+ return fill.tiled ? PageAssembler.tileItems(bg.name, bg, {
4444
+ x: 0,
4445
+ y: 0,
4446
+ width: w,
4447
+ height: h
4448
+ }) : [{
4449
+ type: "image",
4450
+ x: pt(0),
4451
+ y: pt(0),
4452
+ width: pt(w),
4453
+ height: pt(h),
4454
+ imageResourceName: bg.name
4455
+ }];
4456
+ }
4321
4457
  if (fill?.kind === "gradient" && fill.gradient) return [{
4322
4458
  type: "shape",
4323
4459
  shape: {
@@ -4401,9 +4537,9 @@ var PageAssembler = class PageAssembler {
4401
4537
  this.balanceHeightPt = 0;
4402
4538
  };
4403
4539
  };
4404
- function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImageName) {
4540
+ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImage) {
4405
4541
  if (sectionCtxs.length === 0) return [];
4406
- const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName);
4542
+ const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImage);
4407
4543
  const balanceIfEndsContinuous = (fromBlock) => {
4408
4544
  const here = asm.ctx;
4409
4545
  const next = sectionCtxs[asm.secIdx + 1];
@@ -838,11 +838,13 @@ function vmlFill(shape, shapeType, resolveImage) {
838
838
  const fillEl = poChildren(shape).find((c) => poIs(c, "v:fill"));
839
839
  if (fillEl && (poAttr(fillEl, "type") === "none" || poAttr(fillEl, "on") === "f")) return { kind: "none" };
840
840
  const base = vmlColor(poAttr(shape, "fillcolor")) ?? (fillEl ? vmlColor(poAttr(fillEl, "color")) : void 0) ?? vmlColor(shapeType ? poAttr(shapeType, "fillcolor") : void 0) ?? "FFFFFF";
841
- const fillRelId = fillEl && poAttr(fillEl, "type") === "frame" ? poAttr(fillEl, "r:id") : void 0;
841
+ const fillType = fillEl ? poAttr(fillEl, "type") : void 0;
842
+ const fillRelId = fillType === "frame" || fillType === "tile" ? poAttr(fillEl, "r:id") : void 0;
842
843
  const picture = fillRelId !== void 0 ? resolveImage?.(fillRelId) : void 0;
843
844
  if (picture !== void 0) return {
844
845
  kind: "picture",
845
- imageResource: picture
846
+ imageResource: picture,
847
+ ...fillType === "tile" ? { tiled: true } : {}
846
848
  };
847
849
  const gradient = fillEl ? vmlGradient(fillEl, base) : void 0;
848
850
  if (gradient) return {
@@ -1802,6 +1804,7 @@ function fillFromNode(child, resolveColor, resolveImage) {
1802
1804
  return {
1803
1805
  kind: "picture",
1804
1806
  imageResource: resource,
1807
+ ...poChildren(child).some((c) => poIs(c, "a:tile")) ? { tiled: true } : {},
1805
1808
  ...crop ? { imageCrop: crop } : {}
1806
1809
  };
1807
1810
  }
@@ -131,8 +131,8 @@ function parseParagraphProperties(pPr, autoSpacingPt = 0) {
131
131
  }
132
132
  if ("w:ind" in el) {
133
133
  const node = el["w:ind"];
134
- const left = parseIntAttr(node, "left");
135
- const right = parseIntAttr(node, "right");
134
+ const left = parseIntAttr(node, "left") ?? parseIntAttr(node, "start");
135
+ const right = parseIntAttr(node, "right") ?? parseIntAttr(node, "end");
136
136
  const firstLine = parseIntAttr(node, "firstLine");
137
137
  const hanging = parseIntAttr(node, "hanging");
138
138
  if (left !== void 0) out.indentLeft = twipsToPt(left);
@@ -1,5 +1,5 @@
1
1
  import { halfPtToPt, twipsToPt } from "../core/ir/units.js";
2
- import { asElement, getAttr, getVal, parseIntAttr, parseToggle } from "./xml-helpers.js";
2
+ import { asElement, getAttr, getVal, parseHalfPointAttr, parseToggle } from "./xml-helpers.js";
3
3
  import { shadingFillHex } from "./shading.js";
4
4
  //#region src/word/run-properties.ts
5
5
  var UNDERLINE_STYLES = new Set([
@@ -81,7 +81,7 @@ function parseRunProperties(rPr) {
81
81
  if (c && /^[0-9A-Fa-f]{6}$/u.test(c)) out.underlineColorHex = c.toUpperCase();
82
82
  }
83
83
  if ("w:sz" in el) {
84
- const v = parseIntAttr(el["w:sz"], "val");
84
+ const v = parseHalfPointAttr(el["w:sz"], "val");
85
85
  if (v !== void 0) out.fontSizePt = halfPtToPt(v);
86
86
  }
87
87
  if ("w:color" in el) {
@@ -36,6 +36,18 @@ export declare function getVal(node: unknown): string | undefined;
36
36
  * @param name The local attribute name.
37
37
  * @returns The parsed number, or `undefined` when absent or not finite.
38
38
  */
39
+ /**
40
+ * §17.3.2.38 ST_HpsMeasure — a size in HALF-POINTS, or the same universal
41
+ * measure with a unit on it ("20pt"). The two units differ by a factor of ten
42
+ * from the twips a length is written in, so a size is read here rather than
43
+ * through {@link parseIntAttr}: tdf108408.docx writes `w:sz w:val="20pt"` and
44
+ * read as twips it set its sample text 200 points tall.
45
+ *
46
+ * @param node The element carrying the attribute.
47
+ * @param name The attribute name.
48
+ * @returns The size in half-points, or undefined.
49
+ */
50
+ export declare function parseHalfPointAttr(node: unknown, name: string): number | undefined;
39
51
  export declare function parseIntAttr(node: unknown, name: string): number | undefined;
40
52
  /**
41
53
  * Parse a toggle (boolean) property per ECMA-376 Part 1 §17.17.4:
@@ -1,3 +1,4 @@
1
+ import { universalMeasureTwips } from "../core/po-helpers.js";
1
2
  //#region src/word/xml-helpers.ts
2
3
  /**
3
4
  * Narrow an arbitrary parsed value to an {@link XmlElement}.
@@ -47,12 +48,31 @@ function getVal(node) {
47
48
  * @param name The local attribute name.
48
49
  * @returns The parsed number, or `undefined` when absent or not finite.
49
50
  */
51
+ /**
52
+ * §17.3.2.38 ST_HpsMeasure — a size in HALF-POINTS, or the same universal
53
+ * measure with a unit on it ("20pt"). The two units differ by a factor of ten
54
+ * from the twips a length is written in, so a size is read here rather than
55
+ * through {@link parseIntAttr}: tdf108408.docx writes `w:sz w:val="20pt"` and
56
+ * read as twips it set its sample text 200 points tall.
57
+ *
58
+ * @param node The element carrying the attribute.
59
+ * @param name The attribute name.
60
+ * @returns The size in half-points, or undefined.
61
+ */
62
+ function parseHalfPointAttr(node, name) {
63
+ const v = getAttr(node, name);
64
+ if (v === void 0) return void 0;
65
+ const n = Number(v);
66
+ if (Number.isFinite(n)) return n;
67
+ const twips = universalMeasureTwips(v);
68
+ return twips === void 0 ? void 0 : twips / 10;
69
+ }
50
70
  function parseIntAttr(node, name) {
51
71
  const v = getAttr(node, name);
52
72
  if (v === void 0) return void 0;
53
73
  const n = Number(v);
54
- if (!Number.isFinite(n)) return void 0;
55
- return n;
74
+ if (Number.isFinite(n)) return n;
75
+ return universalMeasureTwips(v);
56
76
  }
57
77
  /**
58
78
  * Parse a toggle (boolean) property per ECMA-376 Part 1 §17.17.4:
@@ -71,4 +91,4 @@ function parseToggle(node) {
71
91
  return !(v === "false" || v === "0" || v === "off");
72
92
  }
73
93
  //#endregion
74
- export { asArray, asElement, getAttr, getVal, parseIntAttr, parseToggle };
94
+ export { asArray, asElement, getAttr, getVal, parseHalfPointAttr, parseIntAttr, parseToggle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",