@muhammara/native-core 7.0.0 → 7.1.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.
@@ -1,4 +1,3 @@
1
- const muhammara = require("../muhammara");
2
1
  const { PAGE_CONTEXT_STATE } = require("./utils");
3
2
  const { PageLayout, Colorspace } = require("../recipe-constants");
4
3
  const {
@@ -541,11 +540,17 @@ function writePageLabels(writer, copyingContext, pageLabels) {
541
540
  * @param {number} [margins.bottom] - Bottom margin.
542
541
  * @returns {Recipe} The recipe instance.
543
542
  * @throws {Error} If pages were deleted with deletePage() on this Recipe.
543
+ * @throws {Error} If another page is still active; call endPage() first.
544
544
  */
545
545
  exports.createPage = function createPage(pageWidth, pageHeight, margins) {
546
546
  if (this.deletedPages?.size) {
547
547
  throw new Error("createPage cannot be combined with deletePage");
548
548
  }
549
+ // Opening a second page over an active one leaves the first page
550
+ // unfinished: the writer then crashes or silently drops it at endPDF().
551
+ if (this.page) {
552
+ throw new Error("Finish the current page before creating another page");
553
+ }
549
554
  if (!pageWidth && !pageHeight) {
550
555
  [pageWidth, pageHeight] = this.default.pageSize;
551
556
  } else if (
@@ -605,17 +610,45 @@ exports.createPage = function createPage(pageWidth, pageHeight, margins) {
605
610
  }
606
611
 
607
612
  this.moveTo(0, 0);
613
+ resetTextBox(this, 0, 0);
608
614
  return this;
609
615
  };
610
616
 
611
- // setPageBox() also accepts the PageBox names.
612
- const PAGE_BOX_CONSTANTS = {
613
- [muhammara.PageBox.MEDIA]: muhammara.ePDFPageBoxMediaBox,
614
- [muhammara.PageBox.CROP]: muhammara.ePDFPageBoxCropBox,
615
- [muhammara.PageBox.BLEED]: muhammara.ePDFPageBoxBleedBox,
616
- [muhammara.PageBox.TRIM]: muhammara.ePDFPageBoxTrimBox,
617
- [muhammara.PageBox.ART]: muhammara.ePDFPageBoxArtBox,
618
- };
617
+ /**
618
+ * Forget the previous page's text box, so text() without coordinates starts
619
+ * at the margins of the new active page instead of the last text position,
620
+ * and movedown() starts from the given cursor.
621
+ * @private
622
+ * @param {Recipe} recipe - The recipe instance.
623
+ * @param {number} x - The text cursor x on the new active page.
624
+ * @param {number} y - The text cursor y on the new active page.
625
+ * @returns {void}
626
+ */
627
+ function resetTextBox(recipe, x, y) {
628
+ recipe.x = x;
629
+ recipe.y = y;
630
+ recipe.box = undefined;
631
+ recipe._textOptions = undefined;
632
+ recipe._previousTextObjects = [];
633
+ recipe._firstLineHeight = 0;
634
+ }
635
+
636
+ /**
637
+ * Map the PageBox names, which setPageBox() also accepts, to the addon's
638
+ * ePDFPageBox constants.
639
+ * @private
640
+ * @param {Object} muhammara - The addon API the Recipe was created for.
641
+ * @returns {Object<string, number>} The page box constants by name.
642
+ */
643
+ function pageBoxConstants(muhammara) {
644
+ return {
645
+ [muhammara.PageBox.MEDIA]: muhammara.ePDFPageBoxMediaBox,
646
+ [muhammara.PageBox.CROP]: muhammara.ePDFPageBoxCropBox,
647
+ [muhammara.PageBox.BLEED]: muhammara.ePDFPageBoxBleedBox,
648
+ [muhammara.PageBox.TRIM]: muhammara.ePDFPageBoxTrimBox,
649
+ [muhammara.PageBox.ART]: muhammara.ePDFPageBoxArtBox,
650
+ };
651
+ }
619
652
 
620
653
  /**
621
654
  * Validate a page rotation. PDF allows only multiples of 90 degrees.
@@ -677,7 +710,8 @@ exports.rotate = function rotate(rotation) {
677
710
  * @throws {TypeError} If no page is active.
678
711
  */
679
712
  exports.setPageBox = function setPageBox(box, left, bottom, right, top) {
680
- box = PAGE_BOX_CONSTANTS[box] ?? box;
713
+ const muhammara = this.muhammara;
714
+ box = pageBoxConstants(muhammara)[box] ?? box;
681
715
  if ([left, bottom, right, top].some((value) => typeof value === "bigint")) {
682
716
  throw new TypeError("setPageBox coordinates must be numbers");
683
717
  }
@@ -720,6 +754,8 @@ exports.endPage = function endPage() {
720
754
  if (!this.page) {
721
755
  return this;
722
756
  }
757
+ // A flow still waiting for its end is drawn on the page it started on.
758
+ this._flushTextFlow();
723
759
 
724
760
  if (this.page.endContext) {
725
761
  if (this.contextState === PAGE_CONTEXT_STATE.ACTIVE_EDIT)
@@ -746,6 +782,7 @@ exports.endPage = function endPage() {
746
782
  * @returns {Recipe} The recipe instance.
747
783
  * @throws {Error} If the Recipe was not constructed from an existing PDF.
748
784
  * @throws {Error} If the page does not exist in the source PDF.
785
+ * @throws {Error} If another page is still active; call endPage() first.
749
786
  */
750
787
  exports.editPage = function editPage(pageNumber) {
751
788
  if (this.isNewPDF) {
@@ -753,9 +790,14 @@ exports.editPage = function editPage(pageNumber) {
753
790
  "editPage requires a Recipe constructed from an existing PDF",
754
791
  );
755
792
  }
793
+ // As in createPage, an active page left unfinished crashes the writer or is
794
+ // silently dropped at endPDF().
795
+ if (this.page) {
796
+ throw new Error("Finish the current page before editing another page");
797
+ }
756
798
  const pdfWriter = this.writer;
757
799
  const pageIndex = pageNumber - 1;
758
- const pageModifier = new muhammara.PDFPageModifier(
800
+ const pageModifier = new this.muhammara.PDFPageModifier(
759
801
  pdfWriter,
760
802
  pageIndex,
761
803
  true,
@@ -766,6 +808,7 @@ exports.editPage = function editPage(pageNumber) {
766
808
  this.editingPage = true;
767
809
  this.contextState = PAGE_CONTEXT_STATE.ACTIVE_EDIT;
768
810
  this.modifiedSourcePages.add(pageNumber);
811
+ resetTextBox(this, this._margin.left, this._margin.top);
769
812
 
770
813
  this._resumePageRotation(pageNumber);
771
814
 
@@ -214,7 +214,7 @@ function _n_gon(sides, cx, cy, radius, options = {}) {
214
214
  * @param {number} [options.lineWidth] - The line width
215
215
  * @param {number} [options.opacity] - The opacity
216
216
  * @param {number[]} [options.dash] - The dash style [number, number]
217
- * @param {number} [options.rotation=0] - Accept: +/- 0 through 360.
217
+ * @param {number} [options.rotation=0] - Clockwise rotation in degrees, +/- 0 through 360.
218
218
  * @param {number[]} [options.rotationOrigin=[cx,cy]] - [originX, originY]
219
219
  * @param {number} [options.rotationVertice] - the number of the vertice to be used as rotation origin
220
220
  * @param {number} [options.skewX] - the angle skew off the x-axis
@@ -293,7 +293,7 @@ function _oddStar(ngon) {
293
293
  * @param {number} [options.lineWidth] - The line width
294
294
  * @param {number} [options.opacity] - The opacity
295
295
  * @param {number[]} [options.dash] - The dash style [number, number]
296
- * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
296
+ * @param {number} [options.rotation] - Clockwise rotation in degrees, +/- 0 through 360. Default: 0
297
297
  * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
298
298
  * @param {number} [options.skewX] - the angle skew off the x-axis
299
299
  * @param {number} [options.skewY] - the angle skew off the y-axis.
@@ -508,7 +508,7 @@ function flipY(x, ngon) {
508
508
  * @param {number} [options.lineWidth] - The line width
509
509
  * @param {number} [options.opacity] - The opacity
510
510
  * @param {number[]} [options.dash] - The dash style [number, number]
511
- * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
511
+ * @param {number} [options.rotation] - Clockwise rotation in degrees, +/- 0 through 360. Default: 0
512
512
  * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
513
513
  * @param {number} [options.skewX] - the angle skew off the x-axis
514
514
  * @param {number} [options.skewY] - the angle skew off the y-axis.
@@ -1,4 +1,3 @@
1
- const muhammara = require("../muhammara");
2
1
  const path = require("path");
3
2
  const utils = require("./utils");
4
3
 
@@ -19,7 +18,7 @@ exports.split = function split(outputDir = "", prefix) {
19
18
  prefix = prefix || this.filename;
20
19
  for (let i = 0; i < this.metadata.pages; i++) {
21
20
  const newPdf = path.join(outputDir, `${prefix}-${i + 1}.pdf`);
22
- const pdfWriter = muhammara.createWriter(newPdf);
21
+ const pdfWriter = this.muhammara.createWriter(newPdf);
23
22
  utils.appendPDFPageFromPDFWithAnnotations(pdfWriter, this._getReader(), i);
24
23
  pdfWriter.end();
25
24
  }
@@ -212,6 +212,8 @@ exports.table = function table(x, y, contents, options = {}) {
212
212
  if (!Array.isArray(contents) || contents.length === 0) {
213
213
  return this;
214
214
  }
215
+ // An open text flow is drawn before the table starts its own text.
216
+ this._flushTextFlow();
215
217
  var columns = tableFields(contents, options).map((field) => {
216
218
  var column =
217
219
  options.columns &&
@@ -1,4 +1,4 @@
1
- var { cloneOptions, resolveFontSize } = require("./utils");
1
+ var { cloneOptions, resolveFontSize, trimBreakableEnd } = require("./utils");
2
2
  var { HorizontalAlign, VerticalAlign } = require("../recipe-constants");
3
3
 
4
4
  /**
@@ -70,8 +70,8 @@ const Word = class Word {
70
70
  }
71
71
 
72
72
  /**
73
- * Mark the word as the last of its line, trimming trailing space and
74
- * measuring it again.
73
+ * Mark the word as the last of its line, trimming the spaces around it
74
+ * but a trailing non-breaking space, and measuring it again when trimmed.
75
75
  * @param {boolean} [value=true] - Whether the word is last.
76
76
  * @returns {void}
77
77
  */
@@ -79,7 +79,9 @@ const Word = class Word {
79
79
  // indicate last word in line (for justification)
80
80
  this._last = value;
81
81
  if (this._last) {
82
- this._value = this._value.trim(); // wack any trailing space
82
+ const trimmed = trimBreakableEnd(this._value.trimStart());
83
+ if (trimmed === this._text) return;
84
+ this._value = trimmed;
83
85
  this._text = this._value;
84
86
  this._dimensions = this._pathOptions.font.calculateTextDimensions(
85
87
  this._text,