@bendyline/squisq-formats 2.8.16 → 2.8.17
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/{chunk-MF4ZQQJ7.js → chunk-BGQDPDCD.js} +12 -3
- package/dist/{chunk-KZUP6TIV.js → chunk-JB3ZVN7D.js} +1 -1
- package/dist/{chunk-SWLDHEZI.js → chunk-PIEDORQL.js} +362 -48
- package/dist/{chunk-ZJERBWTB.js → chunk-WPLCWAN3.js} +10 -4
- package/dist/docx/index.js +1 -1
- package/dist/epub/index.js +1 -1
- package/dist/index.js +5 -5
- package/dist/outside-in/index.js +2 -2
- package/dist/pptx/index.js +1 -1
- package/dist/registry/index.js +1 -1
- package/package.json +2 -2
- /package/dist/{chunk-LSUMEIQ7.js → chunk-2IG4BEAX.js} +0 -0
|
@@ -570,12 +570,13 @@ function convertCodeBlock(node) {
|
|
|
570
570
|
return parts.join("");
|
|
571
571
|
}
|
|
572
572
|
function convertTable(node, ctx) {
|
|
573
|
+
const columnCount = Math.max(1, ...node.children.map((row) => row.children.length));
|
|
573
574
|
const rows = [];
|
|
574
575
|
for (let ri = 0; ri < node.children.length; ri++) {
|
|
575
576
|
const row = node.children[ri];
|
|
576
577
|
rows.push(convertTableRow(row, ctx, ri === 0, node.align));
|
|
577
578
|
}
|
|
578
|
-
return `<w:tbl><w:tblPr><w:tblStyle w:val="TableGrid"/><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders></w:tblPr><w:tblGrid
|
|
579
|
+
return `<w:tbl><w:tblPr><w:tblStyle w:val="TableGrid"/><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders></w:tblPr><w:tblGrid>${"<w:gridCol/>".repeat(columnCount)}</w:tblGrid>` + rows.join("") + `</w:tbl>`;
|
|
579
580
|
}
|
|
580
581
|
function convertTableRow(row, ctx, isHeader, align) {
|
|
581
582
|
const cells = [];
|
|
@@ -588,11 +589,19 @@ function convertTableRow(row, ctx, isHeader, align) {
|
|
|
588
589
|
return `<w:tr>${trPr}${cells.join("")}</w:tr>`;
|
|
589
590
|
}
|
|
590
591
|
function convertTableCell(cell, ctx, isHeader, align) {
|
|
591
|
-
const
|
|
592
|
+
const format = isHeader ? { ...bodyFormat(ctx), bold: true } : bodyFormat(ctx);
|
|
593
|
+
const runs = convertInlines(cell.children, ctx, format);
|
|
592
594
|
const jcMap = { left: "left", center: "center", right: "right" };
|
|
593
595
|
const jc = align ? `<w:jc w:val="${jcMap[align]}"/>` : "";
|
|
594
596
|
const pPr = `<w:pPr><w:spacing w:after="0"/>${jc}</w:pPr>`;
|
|
595
|
-
|
|
597
|
+
const tcPr = isHeader ? `<w:tcPr><w:shd w:val="clear" w:color="auto" w:fill="${tableHeaderFill(ctx)}"/></w:tcPr>` : "";
|
|
598
|
+
return `<w:tc>${tcPr}<w:p>${pPr}${runs}</w:p></w:tc>`;
|
|
599
|
+
}
|
|
600
|
+
function tableHeaderFill(ctx) {
|
|
601
|
+
const channels = (hex, fallback) => (/^[0-9a-f]{6}$/i.test(hex ?? "") ? hex : fallback).match(/../g).map((pair) => parseInt(pair, 16));
|
|
602
|
+
const page = channels(ctx.backgroundColor, "FFFFFF");
|
|
603
|
+
const ink = channels(ctx.bodyColor, "000000");
|
|
604
|
+
return page.map((channel, index) => Math.round(channel + (ink[index] - channel) * 0.1)).map((channel) => channel.toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
596
605
|
}
|
|
597
606
|
function convertThematicBreak() {
|
|
598
607
|
return `<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="auto"/></w:pBdr></w:pPr></w:p>`;
|
|
@@ -79,7 +79,8 @@ import {
|
|
|
79
79
|
isGeometryBoundTemplate,
|
|
80
80
|
isTemplateBlock,
|
|
81
81
|
resolvePersistentLayers,
|
|
82
|
-
resolveThemeForDoc
|
|
82
|
+
resolveThemeForDoc,
|
|
83
|
+
shapePath
|
|
83
84
|
} from "@bendyline/squisq/doc";
|
|
84
85
|
import { splitIconMarkers as splitIconMarkers3 } from "@bendyline/squisq/icon-marker";
|
|
85
86
|
import {
|
|
@@ -90,11 +91,24 @@ import {
|
|
|
90
91
|
|
|
91
92
|
// src/pptx/pictureLayers.ts
|
|
92
93
|
import { svgSourceFromBytes } from "@bendyline/squisq/markdown";
|
|
93
|
-
function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight, hyperlinkRelId, svgRelId) {
|
|
94
|
+
function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight, hyperlinkRelId, svgRelId, srcRect = "") {
|
|
94
95
|
const name = escapeXml(img.alt || "Picture");
|
|
95
96
|
const descr = img.alt ? ` descr="${escapeXml(img.alt)}"` : "";
|
|
96
97
|
const link = hyperlinkRelId ? `<a:hlinkClick r:id="${hyperlinkRelId}"/>` : "";
|
|
97
|
-
return `<p:pic><p:nvPicPr>` + (link ? `<p:cNvPr id="${shapeId}" name="${name}"${descr}>${link}</p:cNvPr>` : `<p:cNvPr id="${shapeId}" name="${name}"${descr}/>`) + `<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill>` + blipXml(img.relId, svgRelId) + `<a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
98
|
+
return `<p:pic><p:nvPicPr>` + (link ? `<p:cNvPr id="${shapeId}" name="${name}"${descr}>${link}</p:cNvPr>` : `<p:cNvPr id="${shapeId}" name="${name}"${descr}/>`) + `<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill>` + blipXml(img.relId, svgRelId) + srcRect + `<a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
99
|
+
}
|
|
100
|
+
function fitPicture(box, size, fit) {
|
|
101
|
+
if (!size || size.width <= 0 || size.height <= 0 || fit === "fill" || box.height <= 0)
|
|
102
|
+
return { rect: box, srcRect: "" };
|
|
103
|
+
if (fit !== "cover") return { rect: fitCentered(box, size.width, size.height), srcRect: "" };
|
|
104
|
+
const image = size.width / size.height;
|
|
105
|
+
const frame = box.width / box.height;
|
|
106
|
+
if (Math.abs(image - frame) < 1e-3) return { rect: box, srcRect: "" };
|
|
107
|
+
const edge = Math.round((image > frame ? 1 - frame / image : 1 - image / frame) / 2 * 1e5);
|
|
108
|
+
return {
|
|
109
|
+
rect: box,
|
|
110
|
+
srcRect: image > frame ? `<a:srcRect l="${edge}" r="${edge}"/>` : `<a:srcRect t="${edge}" b="${edge}"/>`
|
|
111
|
+
};
|
|
98
112
|
}
|
|
99
113
|
function fitCentered(area, width, height) {
|
|
100
114
|
const scale = Math.min(area.width / width, area.height / height);
|
|
@@ -449,9 +463,30 @@ function cssGradientToString(gradient) {
|
|
|
449
463
|
// src/pptx/drawingXml.ts
|
|
450
464
|
function officeTypeface(family, fallback) {
|
|
451
465
|
const resolved = resolveFontFamily(family, fallback);
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
466
|
+
const names = resolved.split(",").map((name) => name.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
467
|
+
const named = names.find((name) => !GENERIC_FONT_FAMILIES.has(name.toLowerCase()));
|
|
468
|
+
if (named) return named;
|
|
469
|
+
const kind = names.map((name) => name.toLowerCase());
|
|
470
|
+
if (kind.includes("monospace") || kind.includes("ui-monospace")) return "Consolas";
|
|
471
|
+
if (kind.includes("serif") || kind.includes("ui-serif")) return "Georgia";
|
|
472
|
+
return names.length ? "Segoe UI" : fallback;
|
|
473
|
+
}
|
|
474
|
+
var GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
|
|
475
|
+
"system-ui",
|
|
476
|
+
"-apple-system",
|
|
477
|
+
"blinkmacsystemfont",
|
|
478
|
+
"ui-sans-serif",
|
|
479
|
+
"ui-serif",
|
|
480
|
+
"ui-monospace",
|
|
481
|
+
"ui-rounded",
|
|
482
|
+
"sans-serif",
|
|
483
|
+
"serif",
|
|
484
|
+
"monospace",
|
|
485
|
+
"cursive",
|
|
486
|
+
"fantasy",
|
|
487
|
+
"emoji",
|
|
488
|
+
"math"
|
|
489
|
+
]);
|
|
455
490
|
function solidColorXml(color) {
|
|
456
491
|
const alpha = color.alpha < 1e5 ? `<a:alpha val="${color.alpha}"/>` : "";
|
|
457
492
|
return `<a:solidFill><a:srgbClr val="${color.hex}">${alpha}</a:srgbClr></a:solidFill>`;
|
|
@@ -507,13 +542,17 @@ function patternFillXml(pattern, ground) {
|
|
|
507
542
|
function buildLayerShape(layer, shapeId) {
|
|
508
543
|
const rect = toEmuRect(resolveLayerRect(layer.position, 100, 100));
|
|
509
544
|
const geometry = layer.content.shape === "circle" ? "ellipse" : layer.content.shape === "line" ? "line" : layer.content.borderRadius ? "roundRect" : "rect";
|
|
545
|
+
const box = resolveLayerRect(layer.position, 100, 100);
|
|
546
|
+
const shorter = Math.min(box.width, box.height);
|
|
547
|
+
const radius = Math.min(shorter / 2, Math.max(0, layer.content.borderRadius ?? 0));
|
|
548
|
+
const adjust = geometry === "roundRect" && shorter > 0 ? `<a:avLst><a:gd name="adj" fmla="val ${Math.round(radius / shorter * 1e5)}"/></a:avLst>` : "<a:avLst/>";
|
|
510
549
|
const fill = layer.content.shape === "line" ? "<a:noFill/>" : layer.content.pattern ? patternFillXml(layer.content.pattern) : fillXml(layer.content.fill, layer.content.fillOpacity, layer.content.gradient);
|
|
511
550
|
const stroke = lineXml(
|
|
512
551
|
layer.content.stroke ?? (layer.content.shape === "line" ? "#ffffff" : void 0),
|
|
513
552
|
layer.content.strokeWidth ?? (layer.content.shape === "line" ? 2 : void 0),
|
|
514
553
|
layer.content.borderStyle
|
|
515
554
|
);
|
|
516
|
-
return `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="${geometry}"
|
|
555
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="${geometry}">${adjust}</a:prstGeom>` + fill + stroke + `</p:spPr></p:sp>`;
|
|
517
556
|
}
|
|
518
557
|
|
|
519
558
|
// src/pptx/backdrop.ts
|
|
@@ -752,6 +791,235 @@ function planDeckBackdrops(blocks) {
|
|
|
752
791
|
return { master: { base: masterBase, overlays: masterOverlays }, layouts, slides };
|
|
753
792
|
}
|
|
754
793
|
|
|
794
|
+
// src/pptx/pathGeometry.ts
|
|
795
|
+
var ARITY = {
|
|
796
|
+
M: 2,
|
|
797
|
+
L: 2,
|
|
798
|
+
H: 1,
|
|
799
|
+
V: 1,
|
|
800
|
+
C: 6,
|
|
801
|
+
S: 4,
|
|
802
|
+
Q: 4,
|
|
803
|
+
T: 2,
|
|
804
|
+
A: 7,
|
|
805
|
+
Z: 0
|
|
806
|
+
};
|
|
807
|
+
function arcToCubics(x0, y0, rx, ry, rotation, largeArc, sweep, x, y) {
|
|
808
|
+
if (x0 === x && y0 === y || !Number.isFinite(rx + ry)) return [];
|
|
809
|
+
rx = Math.abs(rx);
|
|
810
|
+
ry = Math.abs(ry);
|
|
811
|
+
if (rx === 0 || ry === 0) return [{ op: "L", x, y }];
|
|
812
|
+
const phi = rotation * Math.PI / 180;
|
|
813
|
+
const [cos, sin] = [Math.cos(phi), Math.sin(phi)];
|
|
814
|
+
const dx = (x0 - x) / 2;
|
|
815
|
+
const dy = (y0 - y) / 2;
|
|
816
|
+
const x1 = cos * dx + sin * dy;
|
|
817
|
+
const y1 = -sin * dx + cos * dy;
|
|
818
|
+
const lambda = x1 * x1 / (rx * rx) + y1 * y1 / (ry * ry);
|
|
819
|
+
if (lambda > 1) {
|
|
820
|
+
rx *= Math.sqrt(lambda);
|
|
821
|
+
ry *= Math.sqrt(lambda);
|
|
822
|
+
}
|
|
823
|
+
const numerator = rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1;
|
|
824
|
+
const denominator = rx * rx * y1 * y1 + ry * ry * x1 * x1;
|
|
825
|
+
const factor = (largeArc === sweep ? -1 : 1) * Math.sqrt(Math.max(0, numerator / (denominator || 1)));
|
|
826
|
+
const cxp = factor * rx * y1 / ry;
|
|
827
|
+
const cyp = -factor * ry * x1 / rx;
|
|
828
|
+
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
829
|
+
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
830
|
+
const angle = (ux, uy, vx, vy) => {
|
|
831
|
+
const turn = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
832
|
+
return turn;
|
|
833
|
+
};
|
|
834
|
+
const start = angle(1, 0, (x1 - cxp) / rx, (y1 - cyp) / ry);
|
|
835
|
+
let delta = angle((x1 - cxp) / rx, (y1 - cyp) / ry, (-x1 - cxp) / rx, (-y1 - cyp) / ry);
|
|
836
|
+
if (!sweep && delta > 0) delta -= 2 * Math.PI;
|
|
837
|
+
if (sweep && delta < 0) delta += 2 * Math.PI;
|
|
838
|
+
const pieces = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
|
|
839
|
+
const step = delta / pieces;
|
|
840
|
+
const handle = 4 / 3 * Math.tan(step / 4);
|
|
841
|
+
const point = (theta) => [
|
|
842
|
+
cx + rx * Math.cos(theta) * cos - ry * Math.sin(theta) * sin,
|
|
843
|
+
cy + rx * Math.cos(theta) * sin + ry * Math.sin(theta) * cos
|
|
844
|
+
];
|
|
845
|
+
const tangent = (theta) => [
|
|
846
|
+
-rx * Math.sin(theta) * cos - ry * Math.cos(theta) * sin,
|
|
847
|
+
-rx * Math.sin(theta) * sin + ry * Math.cos(theta) * cos
|
|
848
|
+
];
|
|
849
|
+
const segments = [];
|
|
850
|
+
for (let i = 0; i < pieces; i++) {
|
|
851
|
+
const from = start + step * i;
|
|
852
|
+
const to = from + step;
|
|
853
|
+
const [px, py] = point(from);
|
|
854
|
+
const [qx, qy] = point(to);
|
|
855
|
+
const [tx0, ty0] = tangent(from);
|
|
856
|
+
const [tx1, ty1] = tangent(to);
|
|
857
|
+
segments.push({
|
|
858
|
+
op: "C",
|
|
859
|
+
x1: px + handle * tx0,
|
|
860
|
+
y1: py + handle * ty0,
|
|
861
|
+
x2: qx - handle * tx1,
|
|
862
|
+
y2: qy - handle * ty1,
|
|
863
|
+
// The last piece ends exactly on the authored endpoint.
|
|
864
|
+
x: i === pieces - 1 ? x : qx,
|
|
865
|
+
y: i === pieces - 1 ? y : qy
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
return segments;
|
|
869
|
+
}
|
|
870
|
+
function parseSvgPath(d) {
|
|
871
|
+
const tokens = d.match(/[a-zA-Z]|[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/gu);
|
|
872
|
+
if (!tokens || !/^[Mm]$/u.test(tokens[0])) return null;
|
|
873
|
+
const segments = [];
|
|
874
|
+
let [cx, cy] = [0, 0];
|
|
875
|
+
let [startX, startY] = [0, 0];
|
|
876
|
+
let control;
|
|
877
|
+
let index = 0;
|
|
878
|
+
while (index < tokens.length) {
|
|
879
|
+
const command = tokens[index++];
|
|
880
|
+
const upper = command.toUpperCase();
|
|
881
|
+
const arity = ARITY[upper];
|
|
882
|
+
if (arity === void 0 || !/^[a-zA-Z]$/u.test(command)) return null;
|
|
883
|
+
const relative = command !== upper;
|
|
884
|
+
if (upper === "Z") {
|
|
885
|
+
segments.push({ op: "Z" });
|
|
886
|
+
[cx, cy] = [startX, startY];
|
|
887
|
+
control = void 0;
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
let first = true;
|
|
891
|
+
while (first || index < tokens.length && !/^[a-zA-Z]$/u.test(tokens[index])) {
|
|
892
|
+
const numbers = tokens.slice(index, index + arity).map(Number);
|
|
893
|
+
if (numbers.length < arity || numbers.some((value) => !Number.isFinite(value))) return null;
|
|
894
|
+
index += arity;
|
|
895
|
+
const [ox, oy] = relative ? [cx, cy] : [0, 0];
|
|
896
|
+
let next;
|
|
897
|
+
switch (upper) {
|
|
898
|
+
case "M": {
|
|
899
|
+
[cx, cy] = [ox + numbers[0], oy + numbers[1]];
|
|
900
|
+
if (first) {
|
|
901
|
+
segments.push({ op: "M", x: cx, y: cy });
|
|
902
|
+
[startX, startY] = [cx, cy];
|
|
903
|
+
} else segments.push({ op: "L", x: cx, y: cy });
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
case "L":
|
|
907
|
+
[cx, cy] = [ox + numbers[0], oy + numbers[1]];
|
|
908
|
+
segments.push({ op: "L", x: cx, y: cy });
|
|
909
|
+
break;
|
|
910
|
+
case "H":
|
|
911
|
+
cx = ox + numbers[0];
|
|
912
|
+
segments.push({ op: "L", x: cx, y: cy });
|
|
913
|
+
break;
|
|
914
|
+
case "V":
|
|
915
|
+
cy = oy + numbers[0];
|
|
916
|
+
segments.push({ op: "L", x: cx, y: cy });
|
|
917
|
+
break;
|
|
918
|
+
case "C":
|
|
919
|
+
case "S": {
|
|
920
|
+
const smooth = upper === "S";
|
|
921
|
+
const x1 = smooth ? control?.kind === "C" ? 2 * cx - control.x : cx : ox + numbers[0];
|
|
922
|
+
const y1 = smooth ? control?.kind === "C" ? 2 * cy - control.y : cy : oy + numbers[1];
|
|
923
|
+
const rest = numbers.slice(smooth ? 0 : 2);
|
|
924
|
+
const [x2, y2, x, y] = [ox + rest[0], oy + rest[1], ox + rest[2], oy + rest[3]];
|
|
925
|
+
segments.push({ op: "C", x1, y1, x2, y2, x, y });
|
|
926
|
+
next = { kind: "C", x: x2, y: y2 };
|
|
927
|
+
[cx, cy] = [x, y];
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
case "Q":
|
|
931
|
+
case "T": {
|
|
932
|
+
const smooth = upper === "T";
|
|
933
|
+
const x1 = smooth ? control?.kind === "Q" ? 2 * cx - control.x : cx : ox + numbers[0];
|
|
934
|
+
const y1 = smooth ? control?.kind === "Q" ? 2 * cy - control.y : cy : oy + numbers[1];
|
|
935
|
+
const rest = numbers.slice(smooth ? 0 : 2);
|
|
936
|
+
const [x, y] = [ox + rest[0], oy + rest[1]];
|
|
937
|
+
segments.push({ op: "Q", x1, y1, x, y });
|
|
938
|
+
next = { kind: "Q", x: x1, y: y1 };
|
|
939
|
+
[cx, cy] = [x, y];
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "A": {
|
|
943
|
+
const [x, y] = [ox + numbers[5], oy + numbers[6]];
|
|
944
|
+
segments.push(
|
|
945
|
+
...arcToCubics(
|
|
946
|
+
cx,
|
|
947
|
+
cy,
|
|
948
|
+
numbers[0],
|
|
949
|
+
numbers[1],
|
|
950
|
+
numbers[2],
|
|
951
|
+
numbers[3] !== 0,
|
|
952
|
+
numbers[4] !== 0,
|
|
953
|
+
x,
|
|
954
|
+
y
|
|
955
|
+
)
|
|
956
|
+
);
|
|
957
|
+
[cx, cy] = [x, y];
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
control = next;
|
|
962
|
+
first = false;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return segments;
|
|
966
|
+
}
|
|
967
|
+
function pathBounds(segments) {
|
|
968
|
+
const xs = [];
|
|
969
|
+
const ys = [];
|
|
970
|
+
for (const segment of segments) {
|
|
971
|
+
if (segment.op === "Z") continue;
|
|
972
|
+
xs.push(segment.x);
|
|
973
|
+
ys.push(segment.y);
|
|
974
|
+
if (segment.op === "C" || segment.op === "Q") {
|
|
975
|
+
xs.push(segment.x1);
|
|
976
|
+
ys.push(segment.y1);
|
|
977
|
+
}
|
|
978
|
+
if (segment.op === "C") {
|
|
979
|
+
xs.push(segment.x2);
|
|
980
|
+
ys.push(segment.y2);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (!xs.length) return null;
|
|
984
|
+
const [x, y] = [Math.min(...xs), Math.min(...ys)];
|
|
985
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
986
|
+
}
|
|
987
|
+
function isStraightLine(segments) {
|
|
988
|
+
const points = segments.filter((segment) => segment.op !== "Z");
|
|
989
|
+
if (points.length < 2 || points.length !== segments.length) return false;
|
|
990
|
+
if (points.some((segment) => segment.op === "C" || segment.op === "Q")) return false;
|
|
991
|
+
const first = points[0];
|
|
992
|
+
const last = points[points.length - 1];
|
|
993
|
+
const [dx, dy] = [last.x - first.x, last.y - first.y];
|
|
994
|
+
const length = Math.hypot(dx, dy);
|
|
995
|
+
if (length === 0) return false;
|
|
996
|
+
return points.every((segment) => {
|
|
997
|
+
const point = segment;
|
|
998
|
+
return Math.abs((point.x - first.x) * dy - (point.y - first.y) * dx) / length < 0.5;
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
function customGeometryXml(segments, bounds, scale, filled) {
|
|
1002
|
+
const unit = (value, origin) => Math.round((value - origin) * scale);
|
|
1003
|
+
const pt = (x, y) => `<a:pt x="${unit(x, bounds.x)}" y="${unit(y, bounds.y)}"/>`;
|
|
1004
|
+
const body = segments.map((segment) => {
|
|
1005
|
+
switch (segment.op) {
|
|
1006
|
+
case "M":
|
|
1007
|
+
return `<a:moveTo>${pt(segment.x, segment.y)}</a:moveTo>`;
|
|
1008
|
+
case "L":
|
|
1009
|
+
return `<a:lnTo>${pt(segment.x, segment.y)}</a:lnTo>`;
|
|
1010
|
+
case "C":
|
|
1011
|
+
return `<a:cubicBezTo>${pt(segment.x1, segment.y1)}${pt(segment.x2, segment.y2)}${pt(segment.x, segment.y)}</a:cubicBezTo>`;
|
|
1012
|
+
case "Q":
|
|
1013
|
+
return `<a:quadBezTo>${pt(segment.x1, segment.y1)}${pt(segment.x, segment.y)}</a:quadBezTo>`;
|
|
1014
|
+
case "Z":
|
|
1015
|
+
return "<a:close/>";
|
|
1016
|
+
}
|
|
1017
|
+
}).join("");
|
|
1018
|
+
const width = Math.max(1, Math.round(bounds.width * scale));
|
|
1019
|
+
const height = Math.max(1, Math.round(bounds.height * scale));
|
|
1020
|
+
return `<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="0" t="0" r="r" b="b"/><a:pathLst><a:path w="${width}" h="${height}"${filled ? "" : ' fill="none"'}>${body}</a:path></a:pathLst></a:custGeom>`;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
755
1023
|
// src/pptx/layerText.ts
|
|
756
1024
|
import { splitIconMarkers as splitIconMarkers2 } from "@bendyline/squisq/icon-marker";
|
|
757
1025
|
|
|
@@ -1131,7 +1399,8 @@ function buildLayerTextShape(layer, shapeId, maxBottom, listIndentPx, textScale
|
|
|
1131
1399
|
const rect = toEmuRect(virtualRect);
|
|
1132
1400
|
const align = style.textAlign === "center" ? "ctr" : style.textAlign === "right" ? "r" : "l";
|
|
1133
1401
|
const inset = Math.max(0, Math.round(padding * EMU_PER_CANVAS_PX));
|
|
1134
|
-
const
|
|
1402
|
+
const unbreakable = !visibleBox && sourceText.trim() !== "" && sourceText.split("\n").every((line) => !/\s/u.test(line.trim()));
|
|
1403
|
+
const bodyPr = `<a:bodyPr wrap="${unbreakable ? "none" : "square"}" anchor="${vertical}" vertOverflow="clip" horzOverflow="${unbreakable ? "overflow" : "clip"}" lIns="${inset}" rIns="${inset}" tIns="${inset}" bIns="${inset}"><a:noAutofit/></a:bodyPr>`;
|
|
1135
1404
|
const paragraphs = layer.content.paragraphs || blockGaps ? buildStructuredParagraphs(paragraphSource, renderedStyle, align, {
|
|
1136
1405
|
// A list with no marker column of its own hangs its bullets by the
|
|
1137
1406
|
// player's list indent.
|
|
@@ -1658,7 +1927,7 @@ function buildLayerSlideXml(block, ctx, plan) {
|
|
|
1658
1927
|
}
|
|
1659
1928
|
const textScale = isGeometryBoundTemplate(block.template) ? 1 : estimateSlideTextScale(layers);
|
|
1660
1929
|
const units = mergeTextFlows(layers, textScale);
|
|
1661
|
-
const
|
|
1930
|
+
const textBoxes = units.filter(({ layer }) => layer.type === "text").map(({ layer }) => resolveLayerRect(layer.position, 1920, 1));
|
|
1662
1931
|
for (const layer of layers) {
|
|
1663
1932
|
if (layer.type !== "text") continue;
|
|
1664
1933
|
for (const run of splitIconMarkers3(layer.content.text ?? "")) {
|
|
@@ -1668,8 +1937,11 @@ function buildLayerSlideXml(block, ctx, plan) {
|
|
|
1668
1937
|
for (let index = 0; index < units.length; index++) {
|
|
1669
1938
|
if ((index & 31) === 0) ctx.signal?.throwIfAborted();
|
|
1670
1939
|
const { layer, listIndentPx } = units[index];
|
|
1671
|
-
const
|
|
1672
|
-
const
|
|
1940
|
+
const current = layer.type === "text" ? resolveLayerRect(layer.position, 1920, 1) : void 0;
|
|
1941
|
+
const currentY = current?.y;
|
|
1942
|
+
const nextTextY = current === void 0 ? void 0 : textBoxes.filter(
|
|
1943
|
+
(candidate) => candidate.y > current.y + 1 && candidate.x < current.x + current.width - TEXT_COLUMN_OVERLAP_PX && candidate.x + candidate.width > current.x + TEXT_COLUMN_OVERLAP_PX
|
|
1944
|
+
).map((candidate) => candidate.y).sort((a, b) => a - b)[0];
|
|
1673
1945
|
const textBottom = nextTextY === void 0 || currentY === void 0 ? void 0 : Math.max(currentY + 1, nextTextY - 8);
|
|
1674
1946
|
shapes.push(
|
|
1675
1947
|
...layer.type === "text" ? [buildLayerTextShape(layer, ctx.allocShapeId(), textBottom, listIndentPx, textScale)] : convertLayerToShapes(layer, ctx, textBottom)
|
|
@@ -1694,10 +1966,24 @@ function convertLayerToShapes(layer, ctx, textBottom) {
|
|
|
1694
1966
|
const data = svg ? void 0 : ctx.images?.get(layer.content.src);
|
|
1695
1967
|
if (data) {
|
|
1696
1968
|
const image = ctx.addImage(layer.content.src, data, layer.content.alt);
|
|
1697
|
-
const rect =
|
|
1969
|
+
const { rect, srcRect } = fitPicture(
|
|
1970
|
+
toEmuRect(resolveLayerRect(layer.position, 1920, 1080)),
|
|
1971
|
+
readImageDimensions(data),
|
|
1972
|
+
layer.content.fit
|
|
1973
|
+
);
|
|
1698
1974
|
const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === image);
|
|
1699
1975
|
return [
|
|
1700
|
-
buildImageShape(
|
|
1976
|
+
buildImageShape(
|
|
1977
|
+
embedded,
|
|
1978
|
+
ctx.allocShapeId(),
|
|
1979
|
+
rect.x,
|
|
1980
|
+
rect.y,
|
|
1981
|
+
rect.width,
|
|
1982
|
+
rect.height,
|
|
1983
|
+
null,
|
|
1984
|
+
void 0,
|
|
1985
|
+
srcRect
|
|
1986
|
+
)
|
|
1701
1987
|
];
|
|
1702
1988
|
}
|
|
1703
1989
|
return [
|
|
@@ -1897,46 +2183,74 @@ function treeItemsToText(items, depth = 0) {
|
|
|
1897
2183
|
}
|
|
1898
2184
|
return lines.filter(Boolean).join("\n");
|
|
1899
2185
|
}
|
|
2186
|
+
function pathLineXml(layer) {
|
|
2187
|
+
const { stroke, strokeWidth } = layer.content;
|
|
2188
|
+
if (stroke === "none" || stroke === "transparent" || strokeWidth === 0)
|
|
2189
|
+
return "<a:ln><a:noFill/></a:ln>";
|
|
2190
|
+
const color = parseCssColor(stroke, "FFFFFF");
|
|
2191
|
+
const width = Math.max(1, Math.round((strokeWidth ?? 2) * EMU_PER_CANVAS_PX));
|
|
2192
|
+
const head = markerXml("headEnd", layer.content.startMarker);
|
|
2193
|
+
const tail = markerXml("tailEnd", layer.content.endMarker);
|
|
2194
|
+
const dash = dashXml(layer.content.borderStyle, layer.content.dasharray);
|
|
2195
|
+
return `<a:ln w="${width}">${solidColorXml(color)}${dash}${head}${tail}</a:ln>`;
|
|
2196
|
+
}
|
|
2197
|
+
var TEXT_COLUMN_OVERLAP_PX = 8;
|
|
1900
2198
|
function buildPathLayerShapes(layer, ctx) {
|
|
2199
|
+
let d = layer.content.d;
|
|
1901
2200
|
if (layer.content.shapeKind) {
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const
|
|
2201
|
+
const box = resolveLayerRect(layer.position, 100, 100);
|
|
2202
|
+
const named = shapePath(layer.content.shapeKind, box.x, box.y, box.width, box.height);
|
|
2203
|
+
if (!named) {
|
|
2204
|
+
const shape = {
|
|
2205
|
+
id: layer.id,
|
|
2206
|
+
type: "shape",
|
|
2207
|
+
position: layer.position,
|
|
2208
|
+
content: {
|
|
2209
|
+
shape: layer.content.shapeKind === "circle" ? "circle" : "rect",
|
|
2210
|
+
fill: layer.content.fill,
|
|
2211
|
+
fillOpacity: layer.content.fillOpacity,
|
|
2212
|
+
gradient: layer.content.gradient,
|
|
2213
|
+
stroke: layer.content.stroke,
|
|
2214
|
+
strokeWidth: layer.content.strokeWidth,
|
|
2215
|
+
borderStyle: layer.content.borderStyle
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
return [buildLayerShape(shape, ctx.allocShapeId())];
|
|
2219
|
+
}
|
|
2220
|
+
d = named;
|
|
2221
|
+
}
|
|
2222
|
+
const segments = parseSvgPath(d);
|
|
2223
|
+
const bounds = segments ? pathBounds(segments) : null;
|
|
2224
|
+
if (!segments || !bounds) return [];
|
|
2225
|
+
const { fill, gradient, fillOpacity } = layer.content;
|
|
2226
|
+
const filled = !!gradient || !!fill && fill !== "none" && fill !== "transparent";
|
|
2227
|
+
const name = `<p:nvSpPr><p:cNvPr id="${ctx.allocShapeId()}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>`;
|
|
2228
|
+
if (!filled && isStraightLine(segments)) {
|
|
2229
|
+
const points = segments.filter(
|
|
2230
|
+
(segment) => segment.op === "M" || segment.op === "L"
|
|
2231
|
+
);
|
|
2232
|
+
const from = points[0];
|
|
2233
|
+
const to = points[points.length - 1];
|
|
2234
|
+
const rect2 = toEmuRect({
|
|
2235
|
+
x: Math.min(from.x, to.x),
|
|
2236
|
+
y: Math.min(from.y, to.y),
|
|
2237
|
+
width: Math.max(1, Math.abs(to.x - from.x)),
|
|
2238
|
+
height: Math.max(1, Math.abs(to.y - from.y))
|
|
2239
|
+
});
|
|
2240
|
+
const flipH = to.x < from.x ? ' flipH="1"' : "";
|
|
2241
|
+
const flipV = to.y < from.y ? ' flipV="1"' : "";
|
|
2242
|
+
return [
|
|
2243
|
+
`<p:sp>${name}<p:spPr><a:xfrm${flipH}${flipV}><a:off x="${rect2.x}" y="${rect2.y}"/><a:ext cx="${rect2.width}" cy="${rect2.height}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:noFill/>` + pathLineXml(layer) + `</p:spPr></p:sp>`
|
|
2244
|
+
];
|
|
2245
|
+
}
|
|
1924
2246
|
const rect = toEmuRect({
|
|
1925
|
-
x:
|
|
1926
|
-
y:
|
|
1927
|
-
width: Math.max(1,
|
|
1928
|
-
height: Math.max(1,
|
|
2247
|
+
x: bounds.x,
|
|
2248
|
+
y: bounds.y,
|
|
2249
|
+
width: Math.max(1, bounds.width),
|
|
2250
|
+
height: Math.max(1, bounds.height)
|
|
1929
2251
|
});
|
|
1930
|
-
const flipH = x2 < x1 ? ' flipH="1"' : "";
|
|
1931
|
-
const flipV = y2 < y1 ? ' flipV="1"' : "";
|
|
1932
|
-
const stroke = parseCssColor(layer.content.stroke, "FFFFFF");
|
|
1933
|
-
const width = Math.max(1, Math.round((layer.content.strokeWidth ?? 2) * EMU_PER_CANVAS_PX));
|
|
1934
|
-
const head = markerXml("headEnd", layer.content.startMarker);
|
|
1935
|
-
const tail = markerXml("tailEnd", layer.content.endMarker);
|
|
1936
|
-
const dash = dashXml(layer.content.borderStyle, layer.content.dasharray);
|
|
1937
|
-
const shapeId = ctx.allocShapeId();
|
|
1938
2252
|
return [
|
|
1939
|
-
`<p:sp
|
|
2253
|
+
`<p:sp>${name}<p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm>` + customGeometryXml(segments, bounds, EMU_PER_CANVAS_PX, filled) + (filled ? fillXml(fill, fillOpacity, gradient) : "<a:noFill/>") + pathLineXml(layer) + `</p:spPr></p:sp>`
|
|
1940
2254
|
];
|
|
1941
2255
|
}
|
|
1942
2256
|
function textLayerForPlaceholder(position, text, ctx) {
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
// src/epub/export.ts
|
|
30
30
|
import JSZip from "jszip";
|
|
31
|
-
import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
31
|
+
import { contrastRatio, isHex, resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
32
32
|
import { resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
33
33
|
import { isHtmlLineBreak, readFrontmatterThemeId, sanitizeUrl } from "@bendyline/squisq/markdown";
|
|
34
34
|
async function markdownDocToEpub(authoredDoc, options = {}) {
|
|
@@ -669,14 +669,20 @@ ${navItems}
|
|
|
669
669
|
</body>
|
|
670
670
|
</html>`;
|
|
671
671
|
}
|
|
672
|
+
function readsOnPaper(color) {
|
|
673
|
+
return !!color && isHex(color) && contrastRatio(color, "#ffffff") >= 4.5;
|
|
674
|
+
}
|
|
672
675
|
function generateStylesheet(themeId, doc, registry, iconFonts = []) {
|
|
673
676
|
let themeVars = "";
|
|
674
677
|
if (themeId) {
|
|
675
678
|
const theme = resolveThemeForDoc(doc, themeId, registry);
|
|
679
|
+
const ink = [theme.colors.primary, theme.colors.secondary, theme.colors.highlight].find(
|
|
680
|
+
(color) => readsOnPaper(color)
|
|
681
|
+
);
|
|
676
682
|
themeVars = `
|
|
677
|
-
--epub-bg: ${theme.colors.background}
|
|
678
|
-
--epub-text: ${theme.colors.text}
|
|
679
|
-
--epub-primary: ${
|
|
683
|
+
--epub-bg: ${theme.colors.background};${readsOnPaper(theme.colors.text) ? `
|
|
684
|
+
--epub-text: ${theme.colors.text};` : ""}${ink ? `
|
|
685
|
+
--epub-primary: ${ink};` : ""}
|
|
680
686
|
--epub-heading-font: ${resolveFontFamily(theme.typography.titleFont, "serif")};
|
|
681
687
|
--epub-body-font: ${resolveFontFamily(theme.typography.bodyFont, "sans-serif")};`;
|
|
682
688
|
}
|
package/dist/docx/index.js
CHANGED
package/dist/epub/index.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
docToEpub,
|
|
11
11
|
markdownDocToEpub
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WPLCWAN3.js";
|
|
13
13
|
import {
|
|
14
14
|
BUILTIN_FORMAT_IDS
|
|
15
15
|
} from "./chunk-2P5HJAQL.js";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
renderOutsideInDocument,
|
|
30
30
|
withOutsideInMarkdownEditing,
|
|
31
31
|
withOutsideInMetadata
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-JB3ZVN7D.js";
|
|
33
33
|
import {
|
|
34
34
|
OUTSIDE_IN_FORMAT_IDS,
|
|
35
35
|
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
defaultRegistry,
|
|
46
46
|
prepareConversion,
|
|
47
47
|
resolveConversionLimits
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-2IG4BEAX.js";
|
|
49
49
|
import {
|
|
50
50
|
ConversionError
|
|
51
51
|
} from "./chunk-KXOZMWBS.js";
|
|
@@ -54,13 +54,13 @@ import {
|
|
|
54
54
|
docxToDoc,
|
|
55
55
|
docxToMarkdownDoc,
|
|
56
56
|
markdownDocToDocx
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-BGQDPDCD.js";
|
|
58
58
|
import {
|
|
59
59
|
docToPptx,
|
|
60
60
|
markdownDocToPptx,
|
|
61
61
|
pptxToDoc,
|
|
62
62
|
pptxToMarkdownDoc
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-PIEDORQL.js";
|
|
64
64
|
import "./chunk-6N2J7C2B.js";
|
|
65
65
|
import "./chunk-WMIDXNGR.js";
|
|
66
66
|
import "./chunk-DHAK4PII.js";
|
package/dist/outside-in/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
renderOutsideInDocument,
|
|
8
8
|
withOutsideInMarkdownEditing,
|
|
9
9
|
withOutsideInMetadata
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-JB3ZVN7D.js";
|
|
11
11
|
import {
|
|
12
12
|
OUTSIDE_IN_FORMAT_IDS,
|
|
13
13
|
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isOutsideInTargetPath,
|
|
16
16
|
resolveOutsideInLayout
|
|
17
17
|
} from "../chunk-WJTC5OD6.js";
|
|
18
|
-
import "../chunk-
|
|
18
|
+
import "../chunk-2IG4BEAX.js";
|
|
19
19
|
import "../chunk-KXOZMWBS.js";
|
|
20
20
|
import "../chunk-7AWFHP5U.js";
|
|
21
21
|
export {
|
package/dist/pptx/index.js
CHANGED
package/dist/registry/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-formats",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.17",
|
|
4
4
|
"description": "Document format converters — DOCX, PDF, OOXML import/export",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"typecheck": "tsc --noEmit"
|
|
136
136
|
},
|
|
137
137
|
"dependencies": {
|
|
138
|
-
"@bendyline/squisq": "2.13.
|
|
138
|
+
"@bendyline/squisq": "2.13.15",
|
|
139
139
|
"@pdf-lib/fontkit": "1.1.1",
|
|
140
140
|
"@pdf-lib/upng": "1.0.1",
|
|
141
141
|
"@xmldom/xmldom": "0.9.12",
|
|
File without changes
|