@muhammara/native-core 7.0.0-beta.2 → 7.0.0-beta.4

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.
@@ -379,7 +379,6 @@ exports.text = function text(text = "", x, y, options = {}) {
379
379
  let columnIndex = 1;
380
380
 
381
381
  toWriteTextObjects.some((toWriteTextObject, index) => {
382
- const isHTML = toWriteTextObject.writeOptions.html;
383
382
  const { text, lineHeight, lineWidth, lineID, spaceWidth } =
384
383
  toWriteTextObject;
385
384
 
@@ -484,6 +483,9 @@ exports.text = function text(text = "", x, y, options = {}) {
484
483
  // nothing to write, so simply escape.
485
484
  return next_x;
486
485
  }
486
+ // The opacity and rotation path below resets x to the text box edge;
487
+ // text-markup annotations need the line's own start.
488
+ const lineX = x;
487
489
 
488
490
  if (options.underline || options.strikeOut) {
489
491
  options.lineWidth = lineWidth;
@@ -607,18 +609,38 @@ exports.text = function text(text = "", x, y, options = {}) {
607
609
 
608
610
  const { textHeight } = options;
609
611
 
612
+ var markupLeft = lineX;
613
+ var markupBottom = y - textHeight * 0.2;
614
+ var markupWidth = _justify ? next_x - lineX : currentLineWidth;
615
+ var markupHeight = textHeight * 1.4;
616
+ if (textBox.wrap === "clip") {
617
+ // Clipped runs retain the first overflowing word, so measure the
618
+ // drawn text instead of using the preceding fitting line's width.
619
+ var markupRight = Math.min(
620
+ lineX + new Word(text, options).dimensions.xMax,
621
+ nx + textBox.width,
622
+ );
623
+ var markupTop = Math.min(markupBottom + markupHeight, y + lineHeight);
624
+ markupLeft = Math.max(markupLeft, nx);
625
+ markupBottom = Math.max(markupBottom, y);
626
+ markupWidth = markupRight - markupLeft;
627
+ markupHeight = markupTop - markupBottom;
628
+ if (markupWidth <= 0 || markupHeight <= 0) return next_x;
629
+ }
630
+
610
631
  for (let key in targetAnnotations) {
611
632
  const subtype = this._getTextMarkupAnnotationSubtype(key);
612
- if (subtype) {
633
+ if (subtype && targetAnnotations[key]) {
634
+ // Copy so the caller's markup options are not modified.
613
635
  const markupOption =
614
636
  typeof targetAnnotations[key] != "object"
615
637
  ? {}
616
- : targetAnnotations[key];
638
+ : { ...targetAnnotations[key] };
617
639
  const { title, open, richText, flag, icon, date, subject } =
618
640
  targetAnnotations;
619
641
  Object.assign(markupOption, {
620
- height: textHeight * 1.4,
621
- width: currentLineWidth,
642
+ height: markupHeight,
643
+ width: markupWidth,
622
644
  text: markupOption.text || "",
623
645
  _textHeight: textHeight,
624
646
  // add options to annotation
@@ -630,7 +652,10 @@ exports.text = function text(text = "", x, y, options = {}) {
630
652
  date: date || "",
631
653
  subject: subject || "",
632
654
  });
633
- const { ox, oy } = this._reverseCoordinate(x, y - textHeight * 0.2);
655
+ const { ox, oy } = this._reverseCoordinate(
656
+ markupLeft,
657
+ markupBottom,
658
+ );
634
659
 
635
660
  this.annot(ox, oy, subtype, markupOption);
636
661
  }
@@ -639,6 +664,29 @@ exports.text = function text(text = "", x, y, options = {}) {
639
664
  return next_x;
640
665
  };
641
666
 
667
+ /** Queues a text link limited to the run's visible clipping region. */
668
+ var queueTextLink = function (content, x, y, nextX) {
669
+ if (!content.writeOptions.link) return;
670
+ var left = x;
671
+ var width = nextX ? nextX - x : content.lineWidth;
672
+ if (textBox.wrap === "clip") {
673
+ var right = Math.min(
674
+ x + new Word(content.text, content.writeOptions).dimensions.xMax,
675
+ nx + textBox.width,
676
+ );
677
+ left = Math.max(left, nx);
678
+ width = right - left;
679
+ if (width <= 0) return;
680
+ }
681
+ linkAnnotations.push({
682
+ url: content.writeOptions.link,
683
+ left,
684
+ bottom: y,
685
+ width,
686
+ height: content.lineHeight,
687
+ });
688
+ };
689
+
642
690
  if (!isContinued) {
643
691
  // flush out current line before processing next one
644
692
  let next_x = 0;
@@ -646,15 +694,7 @@ exports.text = function text(text = "", x, y, options = {}) {
646
694
  const x = next_x || getStartX(content.startX, content);
647
695
  const y = currentY;
648
696
  next_x = writeText(context, x, y, content);
649
- if (content.writeOptions.link) {
650
- linkAnnotations.push({
651
- url: content.writeOptions.link,
652
- left: x,
653
- bottom: y,
654
- width: next_x ? next_x - x : content.lineWidth,
655
- height: content.lineHeight,
656
- });
657
- }
697
+ queueTextLink(content, x, y, next_x);
658
698
  });
659
699
  // The line offset from the last line in the
660
700
  // group determines Y positioning for next line.
@@ -683,6 +723,7 @@ exports.text = function text(text = "", x, y, options = {}) {
683
723
  }
684
724
 
685
725
  if (overflow && this._overflowNotifier) {
726
+ flushTextLinks(this, linkAnnotations);
686
727
  let orders = this._overflowNotifier(this);
687
728
  if (orders === true) {
688
729
  return true; // stop processing remaining text.
@@ -723,18 +764,16 @@ exports.text = function text(text = "", x, y, options = {}) {
723
764
 
724
765
  const startX = nx + currentLineWidth;
725
766
 
726
- // This text will only ever have this strange tag when the
727
- // HTML option is being used and an explicit linebreak encountered.
728
- if (text != "[@@DONOT_RENDER_THIS@@]") {
729
- toWriteTextObject.startX = toWriteTextObject.startX || startX;
730
- toWriteContents.push(toWriteTextObject);
731
- }
767
+ toWriteTextObject.startX = toWriteTextObject.startX || startX;
768
+ toWriteContents.push(toWriteTextObject);
732
769
 
733
770
  // To handle text that has been split in middle of word,
734
- // need to decide if current text ends with a space.
771
+ // need to decide if current text ends with a space. HTML segments carry
772
+ // their own separating space, so adding one unconditionally in HTML mode
773
+ // measured every line a space wider than it is drawn and pushed aligned
774
+ // lines left.
735
775
  currentLineWidth +=
736
- lineWidth +
737
- (isHTML || toWriteTextObject.text.endsWith(" ") ? spaceWidth : 0);
776
+ lineWidth + (toWriteTextObject.text.endsWith(" ") ? spaceWidth : 0);
738
777
 
739
778
  // Processing last text object?
740
779
  if (index === toWriteTextObjects.length - 1) {
@@ -753,15 +792,7 @@ exports.text = function text(text = "", x, y, options = {}) {
753
792
  const x = next_x || getStartX(content.startX, content);
754
793
  const y = currentY;
755
794
  next_x = writeText(context, x, y, content);
756
- if (content.writeOptions.link) {
757
- linkAnnotations.push({
758
- url: content.writeOptions.link,
759
- left: x,
760
- bottom: y,
761
- width: next_x ? next_x - x : content.lineWidth,
762
- height: content.lineHeight,
763
- });
764
- }
795
+ queueTextLink(content, x, y, next_x);
765
796
  }
766
797
 
767
798
  // Flush any left over text objects.
@@ -772,23 +803,29 @@ exports.text = function text(text = "", x, y, options = {}) {
772
803
  });
773
804
 
774
805
  if (clipResult && typeof textBox.onClip === "function") {
775
- // The active text operation owns the page context until it returns.
806
+ // The callback may finish the page or start drawing on another one.
807
+ flushTextLinks(this, linkAnnotations);
776
808
  textBox.onClip(this, clipResult);
777
809
  }
778
810
  }
779
811
 
780
- linkAnnotations.forEach((annotation) => {
812
+ flushTextLinks(this, linkAnnotations);
813
+ return this;
814
+ };
815
+
816
+ /** Writes pending text links before a callback can change the active page. @private */
817
+ function flushTextLinks(recipe, annotations) {
818
+ for (var annotation of annotations.splice(0)) {
781
819
  linkPdf(
782
- this,
820
+ recipe,
783
821
  annotation.url,
784
822
  annotation.left,
785
823
  annotation.bottom,
786
824
  annotation.width,
787
825
  annotation.height,
788
826
  );
789
- });
790
- return this;
791
- };
827
+ }
828
+ }
792
829
 
793
830
  exports._layoutText = function _layoutText(textObjects, textBox, pathOptions) {
794
831
  let totalHeight = 0;
@@ -831,19 +868,52 @@ exports._layoutText = function _layoutText(textObjects, textBox, pathOptions) {
831
868
 
832
869
  let firstLineHeight;
833
870
  let toWriteTextObjects = [];
834
- /** Reports whether a layout node contains content other than the break sentinel. */
871
+ /** Reports whether a layout node contains text, not only line breaks. */
835
872
  const hasRenderableContent = (textObject) =>
836
873
  (textObject.value !== undefined &&
837
874
  textObject.value !== null &&
838
- textObject.value !== "" &&
839
- textObject.value !== "[@@DONOT_RENDER_THIS@@]") ||
875
+ textObject.value !== "") ||
840
876
  textObject.childs?.some(hasRenderableContent);
841
877
 
878
+ /** Adds laid-out objects, recording the first line height. */
879
+ const addLaidOutObjects = (newToWriteObjects, paragraphHeight) => {
880
+ toWriteTextObjects = [...toWriteTextObjects, ...newToWriteObjects];
881
+
882
+ if (!firstLineHeight) {
883
+ this._lineHeight = firstLineHeight = toWriteTextObjects[0].lineHeight;
884
+ if (!this._firstLineHeight) {
885
+ this._firstLineHeight = this._lineHeight; // used in textbox coordinate computation
886
+ }
887
+ }
888
+ totalHeight += paragraphHeight;
889
+ };
890
+
842
891
  const writeValue = (textObject) => {
843
892
  textObject.lineID = textObject.lineID || Date.now() * Math.random();
844
- textObject.lineID = textObject.needsLineBreaker
845
- ? Date.now() * Math.random()
846
- : textObject.lineID;
893
+ textObject.lineID =
894
+ textObject.needsLineBreaker || textObject.lineBreak
895
+ ? Date.now() * Math.random()
896
+ : textObject.lineID;
897
+ if (textObject.lineBreak) {
898
+ // A line break lays out as one empty line sized like its text; the
899
+ // normalization below turns it into line state, never into text.
900
+ const { toWriteTextObjects: newToWriteObjects, paragraphHeight } =
901
+ makeTextObjects(
902
+ this,
903
+ Object.assign({}, textObject, { value: "" }),
904
+ pathOptions,
905
+ textBox,
906
+ );
907
+ const lineBreak = newToWriteObjects[newToWriteObjects.length - 1];
908
+ lineBreak.lineBreak = true;
909
+ // Flowed text marks the last word of the previous line, so an empty
910
+ // break line still carries one empty word.
911
+ if (!lineBreak.wordsInLine.length) {
912
+ lineBreak.wordsInLine.push(new Word("", pathOptions));
913
+ }
914
+ addLaidOutObjects(newToWriteObjects, paragraphHeight);
915
+ return;
916
+ }
847
917
  // Want to allow empty string to pass through. Undefined and null elements, stay out!
848
918
  if (textObject.value !== undefined && textObject.value !== null) {
849
919
  textObject.styles.color = textObject.styles.color
@@ -851,16 +921,7 @@ exports._layoutText = function _layoutText(textObjects, textBox, pathOptions) {
851
921
  : pathOptions.color;
852
922
  const { toWriteTextObjects: newToWriteObjects, paragraphHeight } =
853
923
  makeTextObjects(this, textObject, pathOptions, textBox);
854
-
855
- toWriteTextObjects = [...toWriteTextObjects, ...newToWriteObjects];
856
-
857
- if (!firstLineHeight) {
858
- this._lineHeight = firstLineHeight = toWriteTextObjects[0].lineHeight;
859
- if (!this._firstLineHeight) {
860
- this._firstLineHeight = this._lineHeight; // used in textbox coordinate computation
861
- }
862
- }
863
- totalHeight += paragraphHeight;
924
+ addLaidOutObjects(newToWriteObjects, paragraphHeight);
864
925
  }
865
926
  if (textObject.tag && textObject.childs.length) {
866
927
  // console.log(textObject);
@@ -931,14 +992,18 @@ exports._layoutText = function _layoutText(textObjects, textBox, pathOptions) {
931
992
  });
932
993
  }
933
994
  };
995
+ // Top-level nodes share a line, as children of a block element do, so
996
+ // inline runs outside any element are not split onto separate lines.
997
+ const topLevelLineID = Date.now() * Math.random();
934
998
  textObjects.forEach((textObject) => {
999
+ textObject.lineID = textObject.lineID || topLevelLineID;
935
1000
  writeValue(textObject);
936
1001
  });
937
1002
 
938
1003
  const normalizedTextObjects = [];
939
1004
  let pendingBreaks = [];
940
1005
  const replacementLineIDs = new Map();
941
- /** Converts pending break sentinels into line state without rendering their text. */
1006
+ /** Converts pending line breaks into line state: blank lines and line IDs. */
942
1007
  const appendPendingBreaks = (nextTextObject) => {
943
1008
  const previous = normalizedTextObjects[normalizedTextObjects.length - 1];
944
1009
  if (previous) previous.lineComplete = true;
@@ -963,12 +1028,13 @@ exports._layoutText = function _layoutText(textObjects, textBox, pathOptions) {
963
1028
  pendingBreaks = [];
964
1029
  };
965
1030
  toWriteTextObjects.forEach((textObject, index, objects) => {
966
- const sentinel = textObject.text.trim() == "[@@DONOT_RENDER_THIS@@]";
967
- const beforeSentinel =
1031
+ // Whitespace that only precedes a line break would start a blank line.
1032
+ const beforeLineBreak =
1033
+ !textObject.lineBreak &&
968
1034
  textObject.text.trim() == "" &&
969
- objects[index + 1]?.text.trim() == "[@@DONOT_RENDER_THIS@@]";
970
- if (beforeSentinel) return;
971
- if (sentinel) {
1035
+ objects[index + 1]?.lineBreak;
1036
+ if (beforeLineBreak) return;
1037
+ if (textObject.lineBreak) {
972
1038
  pendingBreaks.push(textObject);
973
1039
  return;
974
1040
  }
@@ -1297,7 +1363,8 @@ function makeTextObjects(self, textObject = {}, pathOptions, textBox = {}) {
1297
1363
  opacity: parseFloat(textObject.styles.opacity || pathOptions.opacity || 1),
1298
1364
  underline: textObject.underline || pathOptions.underline,
1299
1365
  strikeOut: textObject.strikeOut || pathOptions.strikeOut,
1300
- size: textObject.size,
1366
+ // Text outside any HTML element has no inherited size of its own.
1367
+ size: size,
1301
1368
  alignHorizontal: alignHorizontal,
1302
1369
  alignVertical: alignVertical,
1303
1370
  font: self._getFont(textObject),
@@ -1339,7 +1406,7 @@ function makeTextObjects(self, textObject = {}, pathOptions, textBox = {}) {
1339
1406
  const previousLine = toWriteTextObjects[end];
1340
1407
  let lineComplete, fini;
1341
1408
 
1342
- if (text === "" && !self._flow) {
1409
+ if (text === "" && !self._flow && !textObject.lineBreak) {
1343
1410
  // turning off flow with empty text so
1344
1411
  previousLine.lastLine = true; // need to make previous line, the last.
1345
1412
  }
@@ -1,5 +1,28 @@
1
1
  const ANNOTATION_PREFIX = "Annots";
2
2
 
3
+ /** Recipe page content-stream lifecycle states. */
4
+ const PAGE_CONTEXT_STATE = Object.freeze({
5
+ IDLE: "idle",
6
+ ACTIVE_NEW: "active-new",
7
+ ACTIVE_EDIT: "active-edit",
8
+ PAUSED_NEW: "paused-new",
9
+ PAUSED_EDIT: "paused-edit",
10
+ });
11
+
12
+ /**
13
+ * Copies nested option objects and arrays without discarding callback values.
14
+ * @private
15
+ */
16
+ function cloneOptions(value) {
17
+ if (Array.isArray(value)) return value.map(cloneOptions);
18
+ if (value && typeof value === "object") {
19
+ return Object.fromEntries(
20
+ Object.entries(value).map(([key, item]) => [key, cloneOptions(item)]),
21
+ );
22
+ }
23
+ return value;
24
+ }
25
+
3
26
  /**
4
27
  * Append PDF Page with annotations.
5
28
  *
@@ -72,7 +95,36 @@ function appendPDFPagesFromPDFWithAnnotations(
72
95
  }
73
96
  }
74
97
 
98
+ /**
99
+ * Resolves a text font size from `size`, its `fontSize` alias, or a fallback,
100
+ * rejecting a size that is not greater than zero. Zero and negative sizes have
101
+ * no usable meaning: they draw nothing readable and measure to nonsensical font
102
+ * metrics, so they are reported as invalid input naming the option and the
103
+ * value. Omitting both options, or passing `null` or `undefined`, selects the
104
+ * fallback.
105
+ * @param {Object} [options] - Options holding `size` or `fontSize`.
106
+ * @param {number} [fallback] - Size used when neither option is given.
107
+ * @returns {number} The resolved font size in PDF points.
108
+ * @throws {RangeError} If the given size is not greater than zero.
109
+ */
110
+ function resolveFontSize(options = {}, fallback) {
111
+ const name = options.size == null ? "fontSize" : "size";
112
+ const size = options[name];
113
+ if (size == null) {
114
+ return fallback;
115
+ }
116
+ if (!(size > 0)) {
117
+ throw new RangeError(
118
+ `Text ${name} must be a number greater than zero, received ${size}`,
119
+ );
120
+ }
121
+ return size;
122
+ }
123
+
75
124
  exports.ANNOTATION_PREFIX = ANNOTATION_PREFIX;
125
+ exports.PAGE_CONTEXT_STATE = PAGE_CONTEXT_STATE;
126
+ exports.cloneOptions = cloneOptions;
127
+ exports.resolveFontSize = resolveFontSize;
76
128
  exports.appendPDFPageFromPDFWithAnnotations =
77
129
  appendPDFPageFromPDFWithAnnotations;
78
130
  exports.appendPDFPagesFromPDFWithAnnotations =
@@ -1,4 +1,5 @@
1
1
  const { xObjectForm } = require("./xObjectForm");
2
+ const { resolveFontSize } = require("./utils");
2
3
 
3
4
  exports._getPathOptions = function _getPathOptions(
4
5
  options = {},
@@ -13,7 +14,7 @@ exports._getPathOptions = function _getPathOptions(
13
14
  originX,
14
15
  originY,
15
16
  font: this._getFont(options),
16
- size: options.size || this.current.defaultFontSize,
17
+ size: resolveFontSize(options, this.current.defaultFontSize),
17
18
  charSpace: options.charSpace || 0,
18
19
  underline: false,
19
20
  strikeOut: false,
@@ -54,12 +55,6 @@ exports._getPathOptions = function _getPathOptions(
54
55
  pathOptions.strokeGsId = extGStates.stroke;
55
56
  pathOptions.fillGsId = extGStates.fill;
56
57
 
57
- if (options.size || options.fontSize) {
58
- const size = options.size || options.fontSize;
59
- if (!isNaN(size)) {
60
- pathOptions.size = size <= 0 ? 1 : size;
61
- }
62
- }
63
58
  if (options.width || options.lineWidth) {
64
59
  const width = options.width || options.lineWidth;
65
60
  if (!isNaN(width)) {
@@ -668,7 +668,6 @@ exports.opacity = function opacity(value) {
668
668
  * @name fill
669
669
  * @function
670
670
  * @memberof Recipe#
671
- * @param {string|number[]} [color] - The requested fill color.
672
671
  * @returns {Recipe} The recipe instance.
673
672
  */
674
673
  exports.fill = function fill() {
@@ -682,7 +681,6 @@ exports.fill = function fill() {
682
681
  * @name stroke
683
682
  * @function
684
683
  * @memberof Recipe#
685
- * @param {string|number[]} [color] - The requested stroke color.
686
684
  * @returns {Recipe} The recipe instance.
687
685
  */
688
686
  exports.stroke = function stroke() {
package/muhammara.d.ts CHANGED
@@ -109,6 +109,7 @@ declare namespace muhammara {
109
109
  }
110
110
 
111
111
  export interface GraphicOptions extends ColorOptions {
112
+ /** "clip" intersects the clipping region without painting; scope with q()/Q(). */
112
113
  type?: "stroke" | "fill" | "clip";
113
114
  width?: number;
114
115
  close?: boolean;
@@ -215,11 +216,15 @@ declare namespace muhammara {
215
216
  ): this;
216
217
  TJ(value: string | Glyph, options?: TextRenderOptions): this;
217
218
  writeFreeCode(freeCode: string): this;
219
+ /** Require at least two complete finite coordinate pairs; invalid input emits no operators. */
218
220
  drawPath(...parameters: any[]): this; // This can't be materialized in TypeScript
219
221
  ////drawPath(...xyPairs: number[], options: GraphicOptions): this;
220
222
  drawPath(xyPairs: Array<[number, number]>, options: GraphicOptions): this;
223
+ /** Coordinates, radius, and calculated circle geometry must remain finite. */
221
224
  drawCircle(x: PosX, y: PosY, r: number, options?: GraphicOptions): this;
225
+ /** Coordinates and edge length must be finite. */
222
226
  drawSquare(x: PosX, y: PosY, l: number, options?: GraphicOptions): this;
227
+ /** Coordinates and dimensions must be finite. */
223
228
  drawRectangle(
224
229
  x: PosX,
225
230
  y: PosY,
@@ -227,6 +232,7 @@ declare namespace muhammara {
227
232
  h: number,
228
233
  options?: GraphicOptions,
229
234
  ): this;
235
+ /** Coordinates, font size, and calculated underline geometry must remain finite. */
230
236
  writeText(text: string, x: PosX, y: PosY, options?: WriteTextOptions): this;
231
237
  drawImage(
232
238
  x: PosX,
@@ -287,7 +293,8 @@ declare namespace muhammara {
287
293
  export interface PDFWriterToContinueOptions {
288
294
  modifiedFilePath?: string;
289
295
  modifiedStream?: PDFRStreamForFile; // TODO
290
- log?: string;
296
+ /** Log file path or synchronous byte writer returning the number of bytes written. */
297
+ log?: string | ByteWriter;
291
298
  }
292
299
 
293
300
  export interface PDFRecryptOptions extends PDFWriterOptions {
@@ -863,6 +870,7 @@ declare namespace muhammara {
863
870
  source: FilePath | ReadStream,
864
871
  options?: AppendOptions,
865
872
  ): number[];
873
+ /** Calls the optional callback with no arguments and globalThis as its receiver. */
866
874
  mergePDFPagesToPage(
867
875
  page: PDFPage,
868
876
  file: FilePath | PDFRStreamForFile,
@@ -1226,6 +1234,7 @@ declare namespace muhammara {
1226
1234
  /** Make the rendered text open this URL. */
1227
1235
  link?: string;
1228
1236
  charSpace?: number;
1237
+ /** Text fill color: `#gg`, `#rrggbb`, `#ccmmyykk`, `%r,g,b` percentages, a 0-255 component array, or a name registered with `chroma()`. Missing or unknown colors use `#1777d1`. */
1229
1238
  color?: Color;
1230
1239
  colorspace?: Colorspace;
1231
1240
  flow?: boolean;
@@ -1235,9 +1244,9 @@ declare namespace muhammara {
1235
1244
  rotation?: number;
1236
1245
  rotationOrigin?: readonly [number, number];
1237
1246
  font?: string;
1238
- /** Font size in points for text() and textDimensions(); defaults to 14 when both fontSize and size are omitted. */
1247
+ /** Font size in points for text() and textDimensions(); defaults to 14 when both fontSize and size are omitted. A size that is not greater than zero throws RangeError. */
1239
1248
  fontSize?: number;
1240
- /** Font size in points for text() and textDimensions(); defaults to 14. */
1249
+ /** Font size in points for text() and textDimensions(); defaults to 14. A size that is not greater than zero throws RangeError. */
1241
1250
  size?: number;
1242
1251
  bold?: boolean;
1243
1252
  italic?: boolean;
@@ -1301,14 +1310,20 @@ declare namespace muhammara {
1301
1310
  name: Field;
1302
1311
  text?: string;
1303
1312
  width?: number;
1313
+ /** Cell text-box options, including onClip callbacks preserved during table layout. */
1304
1314
  cell?: TextBox;
1315
+ /** Header text styles, independent of body styles; booleans use the default header style. Table-level header options take precedence. */
1305
1316
  header?: boolean | TextOptions;
1317
+ /** Final header text-box overrides, applied after header styles and alignToData. */
1306
1318
  hcell?: TextBox;
1307
1319
  renderer?: (
1308
1320
  this: void,
1321
+ /** The own cell value; missing, inherited, and nullish values arrive as `""`. */
1309
1322
  text: undefined extends TableFieldValue<RecordType, Field>
1310
- ? Exclude<TableFieldValue<RecordType, Field>, undefined> | ""
1311
- : TableFieldValue<RecordType, Field>,
1323
+ ? Exclude<TableFieldValue<RecordType, Field>, null | undefined> | ""
1324
+ : null extends TableFieldValue<RecordType, Field>
1325
+ ? Exclude<TableFieldValue<RecordType, Field>, null | undefined> | ""
1326
+ : TableFieldValue<RecordType, Field>,
1312
1327
  record: RecordType,
1313
1328
  field: Field,
1314
1329
  row: number,
@@ -1335,16 +1350,20 @@ declare namespace muhammara {
1335
1350
  interface TableOptions<
1336
1351
  RecordType extends object = Record<string, unknown>,
1337
1352
  > extends Omit<TextOptions, "overflow"> {
1353
+ /** Per-segment height, also bounded by the current page's bottom margin. */
1338
1354
  height?: number;
1355
+ /** Comma-separated names are trimmed; array entries preserve exact keys. */
1339
1356
  order?:
1340
1357
  | string
1341
1358
  | TableField<RecordType>[]
1342
1359
  | readonly [TableField<RecordType>, ...TableField<RecordType>[]];
1343
1360
  columns?: readonly TableColumnOptions<RecordType>[];
1361
+ /** Enables headers and overrides column header styles; body text styles are not inherited. */
1344
1362
  header?:
1345
1363
  boolean | (TextOptions & { alignToData?: boolean; cell?: TextBox });
1346
1364
  border?: boolean | PolygonOptions;
1347
1365
  row?: TextOptions & { nth?: "even" | "odd"; cell?: TextBox };
1366
+ /** Called once per overflow. A continuing destination must fit the row and repeated header or table() throws RangeError; ending the page without starting another throws Error. */
1348
1367
  overflow?: (
1349
1368
  this: Recipe,
1350
1369
  recipe: Recipe,
@@ -1363,6 +1382,8 @@ declare namespace muhammara {
1363
1382
  attributes: Array<{ name: string; value: string | null }>;
1364
1383
  styles: Record<string, string | number | number[]>;
1365
1384
  needsLineBreaker: boolean;
1385
+ /** True for a `<br>` element, which ends the current line. */
1386
+ lineBreak: boolean;
1366
1387
  size: number | undefined;
1367
1388
  sizeRatio: number;
1368
1389
  sizeRatios: number[];
@@ -1697,9 +1718,9 @@ declare namespace muhammara {
1697
1718
  lineWidth(width: number): Recipe;
1698
1719
  /** Set fill and stroke opacity from 0 (transparent) to 1 (opaque). */
1699
1720
  opacity(opacity: number): Recipe;
1700
- fill(color?: Recipe.Color): Recipe;
1701
- stroke(color?: Recipe.Color): Recipe;
1702
- fillAndStroke(fill?: Recipe.Color, stroke?: Recipe.Color): Recipe;
1721
+ fill(): Recipe;
1722
+ stroke(): Recipe;
1723
+ fillAndStroke(): Recipe;
1703
1724
  n_gon(
1704
1725
  cx: number,
1705
1726
  cy: number,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhammara/native-core",
3
- "version": "7.0.0-beta.2",
3
+ "version": "7.0.0-beta.4",
4
4
  "description": "Shared JavaScript API for MuhammaraJS native packages",
5
5
  "homepage": "https://muhammarajs.readthedocs.io/",
6
6
  "license": "Apache-2.0",