@akairos/typeset-studio 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,22 +67,22 @@ function computeStageDimensions(spec, safeArea, contentBottomGap = 20) {
67
67
  function resolveCanvasFontFamily(fontName, isServer = false) {
68
68
  const rawFirst = (fontName || "").split(",")[0] || "";
69
69
  const trimmed = rawFirst.replace(/["'\\]/g, "").trim();
70
- if (trimmed.includes("Noto Sans") || trimmed === "Noto Sans SC") {
70
+ if (trimmed.includes("Noto Sans")) {
71
71
  return isServer ? '"Hiragino Sans GB", sans-serif' : '"Noto Sans SC", "PingFang SC", sans-serif';
72
72
  }
73
- if (trimmed.includes("PingFang") || trimmed === "PingFang SC") {
73
+ if (trimmed.includes("PingFang")) {
74
74
  return isServer ? '"Hiragino Sans GB", sans-serif' : '"PingFang SC", "Noto Sans SC", sans-serif';
75
75
  }
76
- if (trimmed.includes("Noto Serif") || trimmed === "Noto Serif SC") {
76
+ if (trimmed.includes("Noto Serif")) {
77
77
  return isServer ? '"Songti SC", serif' : '"Noto Serif SC", "Songti SC", serif';
78
78
  }
79
- if (trimmed.includes("Songti") || trimmed === "Songti SC" || trimmed === "STSong") {
79
+ if (trimmed.includes("Songti") || trimmed === "STSong") {
80
80
  return isServer ? '"Songti SC", serif' : '"Songti SC", "Noto Serif SC", serif';
81
81
  }
82
82
  if (trimmed.includes("Kai") || trimmed.includes("WenKai")) {
83
83
  return isServer ? '"STKaiti", "Kaiti SC", "Songti SC", serif' : '"LXGW WenKai GB Screen", "LXGW WenKai Screen", "Kaiti SC", "STKaiti", serif';
84
84
  }
85
- if (trimmed.includes("Yuan") || trimmed.includes("Yuanti")) {
85
+ if (trimmed.includes("Yuan")) {
86
86
  return isServer ? '"STYuanti", "Yuanti SC", "Hiragino Sans GB", sans-serif' : '"Yuanti SC", "STYuanti", "PingFang SC", sans-serif';
87
87
  }
88
88
  if (trimmed.includes("Inter")) {
@@ -92,7 +92,7 @@ function resolveCanvasFontFamily(fontName, isServer = false) {
92
92
  return isServer ? 'Georgia, "Songti SC", serif' : 'Georgia, "Songti SC", serif';
93
93
  }
94
94
  if (trimmed.toLowerCase().includes("serif") || trimmed.includes("Song")) {
95
- return isServer ? `"${trimmed}", "Songti SC", serif` : `"${trimmed}", "Songti SC", serif`;
95
+ return `"${trimmed}", "Songti SC", serif`;
96
96
  }
97
97
  return isServer ? `"${trimmed}", "Hiragino Sans GB", "PingFang SC", sans-serif` : `"${trimmed}", "PingFang SC", sans-serif`;
98
98
  }
@@ -114,14 +114,14 @@ async function drawPageToContext(ctx, options) {
114
114
  ctx.fillStyle = theme.bgColor;
115
115
  ctx.fillRect(0, 0, spec.width, spec.height);
116
116
  ctx.restore();
117
- const { stageWidth, stageHeight, stageX, stageY } = computeStageDimensions(spec, algorithm.safeArea, algorithm.contentBottomGap);
117
+ const { stageX, stageY } = computeStageDimensions(spec, algorithm.safeArea, algorithm.contentBottomGap);
118
118
  const isCover = config.aspectRatio === "wechat-cover-2.35";
119
119
  const showHeader = chrome?.showHeader ?? !isCover;
120
120
  const showWatermark = (chrome?.showWatermark ?? !isCover) && Boolean(config.watermarkText?.trim());
121
121
  const showPageNum = (chrome?.showPageNumber ?? (!isCover && totalPageCount > 1)) && totalPageCount > 1;
122
122
  if (showHeader && (showWatermark || showPageNum)) {
123
123
  const headerY = Math.max(24, Math.floor(algorithm.safeArea.top * 0.45));
124
- if (showWatermark && config.watermarkText?.trim()) {
124
+ if (showWatermark) {
125
125
  ctx.save();
126
126
  ctx.font = resolveCanvasFont("600 14px PingFang SC", isServer);
127
127
  ctx.fillStyle = theme.watermarkColor;
@@ -265,7 +265,7 @@ async function drawPageToContext(ctx, options) {
265
265
  ctx.stroke();
266
266
  ctx.restore();
267
267
  }
268
- if (showFooterText && config.footerText?.trim()) {
268
+ if (showFooterText) {
269
269
  ctx.save();
270
270
  ctx.font = resolveCanvasFont("500 13px PingFang SC", isServer);
271
271
  ctx.fillStyle = theme.watermarkColor || theme.captionColor;
@@ -6004,6 +6004,7 @@ function materializeRichInlineLineRange(prepared, line) {
6004
6004
  end: line.end
6005
6005
  };
6006
6006
  }
6007
+
6007
6008
  // src/core/pretext.ts
6008
6009
  var initialized = false;
6009
6010
  function ensurePretextRuntime() {
@@ -6583,21 +6584,21 @@ function runTypeset(params) {
6583
6584
  headingFont: config.typography.headingFont,
6584
6585
  bodyFont: config.typography.bodyFont
6585
6586
  });
6587
+ const createFlowBlocks = (text) => parseContentBlocks(text).map((block) => ({
6588
+ ...blockStyles[block.kind],
6589
+ kind: block.kind,
6590
+ text: block.text,
6591
+ runs: parseInlineRuns(block.text, highlightWords),
6592
+ bullet: block.kind === "list-item" ? block.bullet ?? "•" : undefined,
6593
+ textIndent: block.kind === "list-item" ? 28 : block.kind === "quote" ? 20 : 0,
6594
+ keepWithNext: ["heading", "subheading", "minor-heading"].includes(block.kind)
6595
+ }));
6586
6596
  const pageSegments = splitTextByPages(markdown);
6587
6597
  let pages = [];
6588
6598
  if (pageSegments.length > 1) {
6589
6599
  pages = pageSegments.map((segmentText, index) => {
6590
6600
  const pageNum = index + 1;
6591
- const rawBlocks = parseContentBlocks(segmentText);
6592
- const flowBlocks = rawBlocks.map((b) => ({
6593
- ...blockStyles[b.kind],
6594
- kind: b.kind,
6595
- text: b.text,
6596
- runs: parseInlineRuns(b.text, highlightWords),
6597
- bullet: b.kind === "list-item" ? b.bullet ?? "•" : undefined,
6598
- textIndent: b.kind === "list-item" ? 28 : b.kind === "quote" ? 20 : 0,
6599
- keepWithNext: ["heading", "subheading", "minor-heading"].includes(b.kind)
6600
- }));
6601
+ const flowBlocks = createFlowBlocks(segmentText);
6601
6602
  const pageImages = images.filter((img) => {
6602
6603
  if (img.pageIndex !== undefined && img.pageIndex !== null && img.pageIndex > 0) {
6603
6604
  return img.pageIndex === pageNum;
@@ -6624,16 +6625,7 @@ function runTypeset(params) {
6624
6625
  };
6625
6626
  });
6626
6627
  } else {
6627
- const rawBlocks = parseContentBlocks(markdown);
6628
- const flowBlocks = rawBlocks.map((b) => ({
6629
- ...blockStyles[b.kind],
6630
- kind: b.kind,
6631
- text: b.text,
6632
- runs: parseInlineRuns(b.text, highlightWords),
6633
- bullet: b.kind === "list-item" ? b.bullet ?? "•" : undefined,
6634
- textIndent: b.kind === "list-item" ? 28 : b.kind === "quote" ? 20 : 0,
6635
- keepWithNext: ["heading", "subheading", "minor-heading"].includes(b.kind)
6636
- }));
6628
+ const flowBlocks = createFlowBlocks(markdown);
6637
6629
  pages = paginateBlocks(flowBlocks, stageWidth, stageHeight, images, {
6638
6630
  obstacleGap: config.algorithm.obstacleGap,
6639
6631
  pageImageLimit: config.aspectRatio === "longform-3-4" ? 2 : 1,
@@ -6696,331 +6688,357 @@ function applyTemplateImages(currentImages, templateImages) {
6696
6688
 
6697
6689
  // src/server/routes/templates.ts
6698
6690
  import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
6699
- import { join as join3 } from "path";
6700
- var TEMPLATES_DIR = join3(process.cwd(), "data", "templates");
6691
+ import { dirname, join as join3, resolve as resolve2 } from "path";
6692
+ import { fileURLToPath } from "url";
6693
+ var __dirname = "/Users/aatrox/.oh-my-zzhub/core/typeset-studio/src/server/routes";
6694
+ function resolveDataDir() {
6695
+ if (process.env.TYPESET_DATA_DIR) {
6696
+ return process.env.TYPESET_DATA_DIR;
6697
+ }
6698
+ const currentDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
6699
+ const candidateLib = resolve2(currentDir, "../../data");
6700
+ if (existsSync3(candidateLib))
6701
+ return candidateLib;
6702
+ const candidateSrc = resolve2(currentDir, "../../../data");
6703
+ if (existsSync3(candidateSrc))
6704
+ return candidateSrc;
6705
+ const candidateCwd = resolve2(process.cwd(), "data");
6706
+ if (existsSync3(candidateCwd))
6707
+ return candidateCwd;
6708
+ return candidateLib;
6709
+ }
6710
+ var TEMPLATES_DIR = join3(resolveDataDir(), "templates");
6701
6711
  function ensureTemplatesDir() {
6702
6712
  if (!existsSync3(TEMPLATES_DIR)) {
6703
- mkdirSync(TEMPLATES_DIR, { recursive: true });
6713
+ try {
6714
+ mkdirSync(TEMPLATES_DIR, { recursive: true });
6715
+ } catch {}
6704
6716
  }
6705
6717
  seedDefaultTemplates();
6706
6718
  }
6707
- function seedDefaultTemplates() {
6708
- const presets = [
6709
- {
6710
- ...getDefaultConfig("wechat-cover-2.35"),
6711
- id: "tpl_wechat_cover_minimal",
6712
- name: "微信纯净大图封面 (2.35:1)",
6713
- slot: "article_cover",
6714
- algorithm: {
6715
- ...getDefaultConfig("wechat-cover-2.35").algorithm,
6716
- baseFontSize: 58,
6717
- scaleRatio: 1.4,
6718
- verticalAlign: "center",
6719
- textAlign: "center"
6720
- },
6721
- theme: {
6722
- bgColor: "#09090b",
6723
- textColor: "#ffffff",
6724
- headingColor: "#ffffff",
6725
- accentColor: "#10b981",
6726
- quoteColor: "#71717a",
6727
- captionColor: "#71717a",
6728
- watermarkColor: "#3f3f46",
6729
- cardBg: "#18181b"
6730
- },
6731
- typography: {
6732
- headingFont: "PingFang SC",
6733
- bodyFont: "Songti SC"
6734
- },
6735
- footerText: "",
6736
- watermarkText: "",
6737
- chrome: {
6738
- showHeader: false,
6739
- showWatermark: false,
6740
- showPageNumber: false,
6741
- showFooter: false,
6742
- showFooterDivider: false,
6743
- showFooterText: false,
6744
- showFillRatio: false
6745
- },
6746
- images: [
6747
- {
6748
- id: "tpl_img_cover_bg",
6749
- src: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80",
6750
- alt: "封面装饰图",
6751
- isAbsolute: true,
6752
- x: 1350,
6753
- y: 120,
6754
- width: 880,
6755
- height: 760,
6756
- zIndex: 5,
6757
- rotate: 0,
6758
- opacity: 0.95,
6759
- borderRadius: 24,
6760
- layoutPreset: "custom"
6761
- }
6762
- ]
6719
+ var BUILTIN_PRESETS = [
6720
+ {
6721
+ ...getDefaultConfig("wechat-cover-2.35"),
6722
+ id: "tpl_wechat_cover_minimal",
6723
+ name: "微信纯净大图封面 (2.35:1)",
6724
+ slot: "article_cover",
6725
+ algorithm: {
6726
+ ...getDefaultConfig("wechat-cover-2.35").algorithm,
6727
+ baseFontSize: 58,
6728
+ scaleRatio: 1.4,
6729
+ verticalAlign: "center",
6730
+ textAlign: "center"
6763
6731
  },
6764
- {
6765
- ...getDefaultConfig("wechat-cover-2.35"),
6766
- id: "tpl_wechat_cover_editorial",
6767
- name: "微信杂志标封面 (2.35:1)",
6768
- slot: "article_cover",
6769
- algorithm: {
6770
- ...getDefaultConfig("wechat-cover-2.35").algorithm,
6771
- baseFontSize: 54,
6772
- scaleRatio: 1.35,
6773
- verticalAlign: "center",
6774
- textAlign: "left"
6775
- },
6776
- theme: {
6777
- bgColor: "#ffffff",
6778
- textColor: "#111827",
6779
- headingColor: "#000000",
6780
- accentColor: "#059669",
6781
- quoteColor: "#4b5563",
6782
- captionColor: "#6b7280",
6783
- watermarkColor: "#9ca3af",
6784
- cardBg: "#f3f4f6"
6785
- },
6786
- typography: {
6787
- headingFont: "PingFang SC",
6788
- bodyFont: "Songti SC"
6789
- },
6790
- footerText: "",
6791
- watermarkText: "SPECIAL EDITION · 专栏特辑",
6792
- chrome: {
6793
- showHeader: true,
6794
- showWatermark: true,
6795
- showPageNumber: false,
6796
- showFooter: false,
6797
- showFooterDivider: false,
6798
- showFooterText: false,
6799
- showFillRatio: false
6800
- },
6801
- images: [
6802
- {
6803
- id: "tpl_img_editorial_card",
6804
- src: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=1200&auto=format&fit=crop&q=80",
6805
- alt: "杂志插图",
6806
- isAbsolute: true,
6807
- x: 1400,
6808
- y: 140,
6809
- width: 820,
6810
- height: 720,
6811
- zIndex: 15,
6812
- rotate: -2,
6813
- opacity: 1,
6814
- borderRadius: 20,
6815
- layoutPreset: "custom"
6816
- }
6817
- ]
6732
+ theme: {
6733
+ bgColor: "#09090b",
6734
+ textColor: "#ffffff",
6735
+ headingColor: "#ffffff",
6736
+ accentColor: "#10b981",
6737
+ quoteColor: "#71717a",
6738
+ captionColor: "#71717a",
6739
+ watermarkColor: "#3f3f46",
6740
+ cardBg: "#18181b"
6818
6741
  },
6819
- {
6820
- ...getDefaultConfig("longform-3-4"),
6821
- id: "tpl_newspic_poster_cover",
6822
- name: "贴图封面海报 (3:4)",
6823
- slot: "newspic_cover",
6824
- algorithm: {
6825
- ...getDefaultConfig("longform-3-4").algorithm,
6826
- baseFontSize: 56,
6827
- scaleRatio: 1.35,
6828
- verticalAlign: "center",
6829
- textAlign: "center"
6830
- },
6831
- theme: {
6832
- bgColor: "#121212",
6833
- textColor: "#ffffff",
6834
- headingColor: "#ffffff",
6835
- accentColor: "#f59e0b",
6836
- quoteColor: "#a3a3a3",
6837
- captionColor: "#737373",
6838
- watermarkColor: "#525252",
6839
- cardBg: "#1c1c1c"
6840
- },
6841
- typography: {
6842
- headingFont: "PingFang SC",
6843
- bodyFont: "Songti SC"
6844
- },
6845
- footerText: "Typeset Studio · 贴图精选",
6846
- watermarkText: "FEATURED NEWSPIC",
6847
- chrome: {
6848
- showHeader: true,
6849
- showWatermark: true,
6850
- showPageNumber: false,
6851
- showFooter: true,
6852
- showFooterDivider: false,
6853
- showFooterText: true,
6854
- showFillRatio: false
6855
- },
6856
- images: [
6857
- {
6858
- id: "tpl_img_poster_bg",
6859
- src: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80",
6860
- alt: "海报主图",
6861
- isAbsolute: true,
6862
- x: 100,
6863
- y: 780,
6864
- width: 1000,
6865
- height: 650,
6866
- zIndex: 10,
6867
- rotate: 0,
6868
- opacity: 1,
6869
- borderRadius: 20,
6870
- layoutPreset: "custom"
6871
- }
6872
- ]
6742
+ typography: {
6743
+ headingFont: "PingFang SC",
6744
+ bodyFont: "Songti SC"
6873
6745
  },
6874
- {
6875
- ...getDefaultConfig("longform-3-4"),
6876
- id: "tpl_longform_paper_sage",
6877
- name: "纸白典雅长图文 (3:4)",
6878
- slot: "newspic_content",
6879
- theme: {
6880
- bgColor: "#fbfcfb",
6881
- textColor: "#1c1917",
6882
- headingColor: "#0c0a09",
6883
- accentColor: "#047857",
6884
- quoteColor: "#44403c",
6885
- captionColor: "#78716c",
6886
- watermarkColor: "#a8a29e",
6887
- cardBg: "#ffffff"
6888
- },
6889
- typography: {
6890
- headingFont: "Songti SC",
6891
- bodyFont: "Songti SC"
6892
- },
6893
- footerText: "Typeset Studio · 纯算法空间排版",
6894
- watermarkText: "@ANTIGRAVITY",
6895
- chrome: {
6896
- showHeader: true,
6897
- showWatermark: true,
6898
- showPageNumber: true,
6899
- showFooter: true,
6900
- showFooterDivider: true,
6901
- showFooterText: true,
6902
- showFillRatio: false
6903
- }
6746
+ footerText: "",
6747
+ watermarkText: "",
6748
+ chrome: {
6749
+ showHeader: false,
6750
+ showWatermark: false,
6751
+ showPageNumber: false,
6752
+ showFooter: false,
6753
+ showFooterDivider: false,
6754
+ showFooterText: false,
6755
+ showFillRatio: false
6904
6756
  },
6905
- {
6906
- ...getDefaultConfig("square-1-1"),
6907
- id: "tpl_square_quote_card",
6908
- name: "金句留白方卡 (1:1)",
6909
- algorithm: {
6910
- ...getDefaultConfig("square-1-1").algorithm,
6911
- baseFontSize: 50,
6912
- scaleRatio: 1.3,
6913
- verticalAlign: "center",
6914
- textAlign: "center"
6915
- },
6916
- theme: {
6917
- bgColor: "#18181b",
6918
- textColor: "#fafafa",
6919
- headingColor: "#ffffff",
6920
- accentColor: "#f59e0b",
6921
- quoteColor: "#d4d4d8",
6922
- captionColor: "#a1a1aa",
6923
- watermarkColor: "#71717a",
6924
- cardBg: "#27272a"
6925
- },
6926
- typography: {
6927
- headingFont: "PingFang SC",
6928
- bodyFont: "Kaiti SC"
6929
- },
6930
- footerText: "",
6931
- watermarkText: "",
6932
- chrome: {
6933
- showHeader: false,
6934
- showWatermark: false,
6935
- showPageNumber: false,
6936
- showFooter: false,
6937
- showFooterDivider: false,
6938
- showFooterText: false,
6939
- showFillRatio: false
6757
+ images: [
6758
+ {
6759
+ id: "tpl_img_cover_bg",
6760
+ src: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80",
6761
+ alt: "封面装饰图",
6762
+ isAbsolute: true,
6763
+ x: 1350,
6764
+ y: 120,
6765
+ width: 880,
6766
+ height: 760,
6767
+ zIndex: 5,
6768
+ rotate: 0,
6769
+ opacity: 0.95,
6770
+ borderRadius: 24,
6771
+ layoutPreset: "custom"
6940
6772
  }
6773
+ ]
6774
+ },
6775
+ {
6776
+ ...getDefaultConfig("wechat-cover-2.35"),
6777
+ id: "tpl_wechat_cover_editorial",
6778
+ name: "微信杂志标封面 (2.35:1)",
6779
+ slot: "article_cover",
6780
+ algorithm: {
6781
+ ...getDefaultConfig("wechat-cover-2.35").algorithm,
6782
+ baseFontSize: 54,
6783
+ scaleRatio: 1.35,
6784
+ verticalAlign: "center",
6785
+ textAlign: "left"
6941
6786
  },
6942
- {
6943
- ...getDefaultConfig("wide-16-9"),
6944
- id: "tpl_wide_tech_banner",
6945
- name: "极简技术宽屏 (16:9)",
6946
- algorithm: {
6947
- ...getDefaultConfig("wide-16-9").algorithm,
6948
- baseFontSize: 46,
6949
- scaleRatio: 1.3,
6950
- verticalAlign: "center",
6951
- textAlign: "left"
6952
- },
6953
- theme: {
6954
- bgColor: "#09090b",
6955
- textColor: "#f4f4f5",
6956
- headingColor: "#ffffff",
6957
- accentColor: "#3b82f6",
6958
- quoteColor: "#a1a1aa",
6959
- captionColor: "#71717a",
6960
- watermarkColor: "#52525b",
6961
- cardBg: "#18181b"
6962
- },
6963
- typography: {
6964
- headingFont: "PingFang SC",
6965
- bodyFont: "SourceHanSansSC, sans-serif"
6966
- },
6967
- footerText: "TECH ENGINEERING INSIGHTS",
6968
- watermarkText: "DEV // ARCHITECTURE",
6969
- chrome: {
6970
- showHeader: true,
6971
- showWatermark: true,
6972
- showPageNumber: false,
6973
- showFooter: true,
6974
- showFooterDivider: false,
6975
- showFooterText: true,
6976
- showFillRatio: false
6787
+ theme: {
6788
+ bgColor: "#ffffff",
6789
+ textColor: "#111827",
6790
+ headingColor: "#000000",
6791
+ accentColor: "#059669",
6792
+ quoteColor: "#4b5563",
6793
+ captionColor: "#6b7280",
6794
+ watermarkColor: "#9ca3af",
6795
+ cardBg: "#f3f4f6"
6796
+ },
6797
+ typography: {
6798
+ headingFont: "PingFang SC",
6799
+ bodyFont: "Songti SC"
6800
+ },
6801
+ footerText: "",
6802
+ watermarkText: "SPECIAL EDITION · 专栏特辑",
6803
+ chrome: {
6804
+ showHeader: true,
6805
+ showWatermark: true,
6806
+ showPageNumber: false,
6807
+ showFooter: false,
6808
+ showFooterDivider: false,
6809
+ showFooterText: false,
6810
+ showFillRatio: false
6811
+ },
6812
+ images: [
6813
+ {
6814
+ id: "tpl_img_editorial_card",
6815
+ src: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=1200&auto=format&fit=crop&q=80",
6816
+ alt: "杂志插图",
6817
+ isAbsolute: true,
6818
+ x: 1400,
6819
+ y: 140,
6820
+ width: 820,
6821
+ height: 720,
6822
+ zIndex: 15,
6823
+ rotate: -2,
6824
+ opacity: 1,
6825
+ borderRadius: 20,
6826
+ layoutPreset: "custom"
6977
6827
  }
6828
+ ]
6829
+ },
6830
+ {
6831
+ ...getDefaultConfig("longform-3-4"),
6832
+ id: "tpl_newspic_poster_cover",
6833
+ name: "贴图封面海报 (3:4)",
6834
+ slot: "newspic_cover",
6835
+ algorithm: {
6836
+ ...getDefaultConfig("longform-3-4").algorithm,
6837
+ baseFontSize: 56,
6838
+ scaleRatio: 1.35,
6839
+ verticalAlign: "center",
6840
+ textAlign: "center"
6978
6841
  },
6979
- {
6980
- ...getDefaultConfig("longform-3-4"),
6981
- id: "tpl_longform_vintage_news",
6982
- name: "复古报刊多图 (3:4)",
6983
- slot: "newspic_content",
6984
- algorithm: {
6985
- ...getDefaultConfig("longform-3-4").algorithm,
6986
- baseFontSize: 38,
6987
- scaleRatio: 1.25,
6988
- verticalAlign: "top",
6989
- textAlign: "left"
6990
- },
6991
- theme: {
6992
- bgColor: "#f7f4ee",
6993
- textColor: "#262320",
6994
- headingColor: "#141210",
6995
- accentColor: "#991b1b",
6996
- quoteColor: "#57534e",
6997
- captionColor: "#78716c",
6998
- watermarkColor: "#a8a29e",
6999
- cardBg: "#eee9df"
7000
- },
7001
- typography: {
7002
- headingFont: "SourceHanSerifSC, serif",
7003
- bodyFont: "SourceHanSerifSC, serif"
7004
- },
7005
- footerText: "WEEKLY REPORT · 观察与沉淀",
7006
- watermarkText: "VOL. 42 // THE CHRONICLE",
7007
- chrome: {
7008
- showHeader: true,
7009
- showWatermark: true,
7010
- showPageNumber: true,
7011
- showFooter: true,
7012
- showFooterDivider: true,
7013
- showFooterText: true,
7014
- showFillRatio: false
6842
+ theme: {
6843
+ bgColor: "#121212",
6844
+ textColor: "#ffffff",
6845
+ headingColor: "#ffffff",
6846
+ accentColor: "#f59e0b",
6847
+ quoteColor: "#a3a3a3",
6848
+ captionColor: "#737373",
6849
+ watermarkColor: "#525252",
6850
+ cardBg: "#1c1c1c"
6851
+ },
6852
+ typography: {
6853
+ headingFont: "PingFang SC",
6854
+ bodyFont: "Songti SC"
6855
+ },
6856
+ footerText: "Typeset Studio · 贴图精选",
6857
+ watermarkText: "FEATURED NEWSPIC",
6858
+ chrome: {
6859
+ showHeader: true,
6860
+ showWatermark: true,
6861
+ showPageNumber: false,
6862
+ showFooter: true,
6863
+ showFooterDivider: false,
6864
+ showFooterText: true,
6865
+ showFillRatio: false
6866
+ },
6867
+ images: [
6868
+ {
6869
+ id: "tpl_img_poster_bg",
6870
+ src: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80",
6871
+ alt: "海报主图",
6872
+ isAbsolute: true,
6873
+ x: 100,
6874
+ y: 780,
6875
+ width: 1000,
6876
+ height: 650,
6877
+ zIndex: 10,
6878
+ rotate: 0,
6879
+ opacity: 1,
6880
+ borderRadius: 20,
6881
+ layoutPreset: "custom"
7015
6882
  }
6883
+ ]
6884
+ },
6885
+ {
6886
+ ...getDefaultConfig("longform-3-4"),
6887
+ id: "tpl_longform_paper_sage",
6888
+ name: "纸白典雅长图文 (3:4)",
6889
+ slot: "newspic_content",
6890
+ theme: {
6891
+ bgColor: "#fbfcfb",
6892
+ textColor: "#1c1917",
6893
+ headingColor: "#0c0a09",
6894
+ accentColor: "#047857",
6895
+ quoteColor: "#44403c",
6896
+ captionColor: "#78716c",
6897
+ watermarkColor: "#a8a29e",
6898
+ cardBg: "#ffffff"
6899
+ },
6900
+ typography: {
6901
+ headingFont: "Songti SC",
6902
+ bodyFont: "Songti SC"
6903
+ },
6904
+ footerText: "Typeset Studio · 纯算法空间排版",
6905
+ watermarkText: "@ANTIGRAVITY",
6906
+ chrome: {
6907
+ showHeader: true,
6908
+ showWatermark: true,
6909
+ showPageNumber: true,
6910
+ showFooter: true,
6911
+ showFooterDivider: true,
6912
+ showFooterText: true,
6913
+ showFillRatio: false
7016
6914
  }
7017
- ];
7018
- for (const preset of presets) {
6915
+ },
6916
+ {
6917
+ ...getDefaultConfig("square-1-1"),
6918
+ id: "tpl_square_quote_card",
6919
+ name: "金句留白方卡 (1:1)",
6920
+ algorithm: {
6921
+ ...getDefaultConfig("square-1-1").algorithm,
6922
+ baseFontSize: 50,
6923
+ scaleRatio: 1.3,
6924
+ verticalAlign: "center",
6925
+ textAlign: "center"
6926
+ },
6927
+ theme: {
6928
+ bgColor: "#18181b",
6929
+ textColor: "#fafafa",
6930
+ headingColor: "#ffffff",
6931
+ accentColor: "#f59e0b",
6932
+ quoteColor: "#d4d4d8",
6933
+ captionColor: "#a1a1aa",
6934
+ watermarkColor: "#71717a",
6935
+ cardBg: "#27272a"
6936
+ },
6937
+ typography: {
6938
+ headingFont: "PingFang SC",
6939
+ bodyFont: "Kaiti SC"
6940
+ },
6941
+ footerText: "",
6942
+ watermarkText: "",
6943
+ chrome: {
6944
+ showHeader: false,
6945
+ showWatermark: false,
6946
+ showPageNumber: false,
6947
+ showFooter: false,
6948
+ showFooterDivider: false,
6949
+ showFooterText: false,
6950
+ showFillRatio: false
6951
+ }
6952
+ },
6953
+ {
6954
+ ...getDefaultConfig("wide-16-9"),
6955
+ id: "tpl_wide_tech_banner",
6956
+ name: "极简技术宽屏 (16:9)",
6957
+ algorithm: {
6958
+ ...getDefaultConfig("wide-16-9").algorithm,
6959
+ baseFontSize: 46,
6960
+ scaleRatio: 1.3,
6961
+ verticalAlign: "center",
6962
+ textAlign: "left"
6963
+ },
6964
+ theme: {
6965
+ bgColor: "#09090b",
6966
+ textColor: "#f4f4f5",
6967
+ headingColor: "#ffffff",
6968
+ accentColor: "#3b82f6",
6969
+ quoteColor: "#a1a1aa",
6970
+ captionColor: "#71717a",
6971
+ watermarkColor: "#52525b",
6972
+ cardBg: "#18181b"
6973
+ },
6974
+ typography: {
6975
+ headingFont: "PingFang SC",
6976
+ bodyFont: "SourceHanSansSC, sans-serif"
6977
+ },
6978
+ footerText: "TECH ENGINEERING INSIGHTS",
6979
+ watermarkText: "DEV // ARCHITECTURE",
6980
+ chrome: {
6981
+ showHeader: true,
6982
+ showWatermark: true,
6983
+ showPageNumber: false,
6984
+ showFooter: true,
6985
+ showFooterDivider: false,
6986
+ showFooterText: true,
6987
+ showFillRatio: false
6988
+ }
6989
+ },
6990
+ {
6991
+ ...getDefaultConfig("longform-3-4"),
6992
+ id: "tpl_longform_vintage_news",
6993
+ name: "复古报刊多图 (3:4)",
6994
+ slot: "newspic_content",
6995
+ algorithm: {
6996
+ ...getDefaultConfig("longform-3-4").algorithm,
6997
+ baseFontSize: 38,
6998
+ scaleRatio: 1.25,
6999
+ verticalAlign: "top",
7000
+ textAlign: "left"
7001
+ },
7002
+ theme: {
7003
+ bgColor: "#f7f4ee",
7004
+ textColor: "#262320",
7005
+ headingColor: "#141210",
7006
+ accentColor: "#991b1b",
7007
+ quoteColor: "#57534e",
7008
+ captionColor: "#78716c",
7009
+ watermarkColor: "#a8a29e",
7010
+ cardBg: "#eee9df"
7011
+ },
7012
+ typography: {
7013
+ headingFont: "SourceHanSerifSC, serif",
7014
+ bodyFont: "SourceHanSerifSC, serif"
7015
+ },
7016
+ footerText: "WEEKLY REPORT · 观察与沉淀",
7017
+ watermarkText: "VOL. 42 // THE CHRONICLE",
7018
+ chrome: {
7019
+ showHeader: true,
7020
+ showWatermark: true,
7021
+ showPageNumber: true,
7022
+ showFooter: true,
7023
+ showFooterDivider: true,
7024
+ showFooterText: true,
7025
+ showFillRatio: false
7026
+ }
7027
+ }
7028
+ ];
7029
+ function seedDefaultTemplates() {
7030
+ if (!existsSync3(TEMPLATES_DIR))
7031
+ return;
7032
+ for (const preset of BUILTIN_PRESETS) {
7019
7033
  const filePath = join3(TEMPLATES_DIR, `${preset.id}.json`);
7020
- writeFileSync(filePath, JSON.stringify(preset, null, 2), "utf-8");
7034
+ if (!existsSync3(filePath)) {
7035
+ try {
7036
+ writeFileSync(filePath, JSON.stringify(preset, null, 2), "utf-8");
7037
+ } catch {}
7038
+ }
7021
7039
  }
7022
7040
  }
7023
- var DEFAULT_SETTINGS_PATH = join3(process.cwd(), "data", "default-template.json");
7041
+ var DEFAULT_SETTINGS_PATH = join3(resolveDataDir(), "default-template.json");
7024
7042
  var BUILTIN_SLOT_DEFAULTS = {
7025
7043
  article_cover: "tpl_wechat_cover_minimal",
7026
7044
  newspic_cover: "tpl_newspic_poster_cover",
@@ -7090,7 +7108,15 @@ function setDefaultTemplateId(id) {
7090
7108
  writeFileSync(DEFAULT_SETTINGS_PATH, JSON.stringify(current, null, 2), "utf-8");
7091
7109
  }
7092
7110
  function findTemplateByKeyOrName(key) {
7093
- ensureTemplatesDir();
7111
+ const lowerKey = key.trim().toLowerCase();
7112
+ const builtin = BUILTIN_PRESETS.find((p) => p.id?.toLowerCase() === lowerKey || p.name.toLowerCase() === lowerKey);
7113
+ if (builtin)
7114
+ return builtin;
7115
+ const builtinPartial = BUILTIN_PRESETS.find((p) => p.name.toLowerCase().includes(lowerKey));
7116
+ if (builtinPartial)
7117
+ return builtinPartial;
7118
+ if (!existsSync3(TEMPLATES_DIR))
7119
+ return null;
7094
7120
  const directPath = join3(TEMPLATES_DIR, `${key}.json`);
7095
7121
  if (existsSync3(directPath)) {
7096
7122
  try {
@@ -7098,7 +7124,7 @@ function findTemplateByKeyOrName(key) {
7098
7124
  } catch {}
7099
7125
  }
7100
7126
  const files = readdirSync2(TEMPLATES_DIR).filter((f) => f.endsWith(".json"));
7101
- const lowerKey = key.trim().toLowerCase();
7127
+ const templates = [];
7102
7128
  for (const file of files) {
7103
7129
  try {
7104
7130
  const content = readFileSync2(join3(TEMPLATES_DIR, file), "utf-8");
@@ -7106,18 +7132,10 @@ function findTemplateByKeyOrName(key) {
7106
7132
  if (tpl.id?.toLowerCase() === lowerKey || tpl.name.toLowerCase() === lowerKey) {
7107
7133
  return tpl;
7108
7134
  }
7135
+ templates.push(tpl);
7109
7136
  } catch {}
7110
7137
  }
7111
- for (const file of files) {
7112
- try {
7113
- const content = readFileSync2(join3(TEMPLATES_DIR, file), "utf-8");
7114
- const tpl = JSON.parse(content);
7115
- if (tpl.name.toLowerCase().includes(lowerKey)) {
7116
- return tpl;
7117
- }
7118
- } catch {}
7119
- }
7120
- return null;
7138
+ return templates.find((tpl) => tpl.name.toLowerCase().includes(lowerKey)) ?? null;
7121
7139
  }
7122
7140
  var templatesRouter = new Hono3;
7123
7141
  templatesRouter.get("/", (c) => {
@@ -7409,7 +7427,7 @@ function mapBodyImages(bodyImages, pageSpecs) {
7409
7427
  });
7410
7428
  }
7411
7429
  var REMOTE_STUDIO_URL = process.env.TYPESET_STUDIO_URL?.replace(/\/$/, "");
7412
- async function renderRemoteBatch(studioUrl, body, outputDir, filenamePrefix, route, kind) {
7430
+ async function renderRemoteBatch(studioUrl, body, outputDir, route, kind) {
7413
7431
  const res = await fetch(`${studioUrl}/api/render/export`, {
7414
7432
  method: "POST",
7415
7433
  headers: { "Content-Type": "application/json" },
@@ -7421,12 +7439,11 @@ async function renderRemoteBatch(studioUrl, body, outputDir, filenamePrefix, rou
7421
7439
  const data = await res.json();
7422
7440
  const assets = [];
7423
7441
  const pagesSummary = [];
7424
- for (let i = 0;i < data.pages.length; i++) {
7425
- const pageItem = data.pages[i];
7442
+ for (const pageItem of data.pages) {
7426
7443
  const pageNum = pageItem.pageNumber;
7427
7444
  const base64Data = pageItem.dataUrl.replace(/^data:image\/png;base64,/, "");
7428
7445
  const buffer = Buffer.from(base64Data, "base64");
7429
- const filename = kind === "cover" ? "cover.png" : `${filenamePrefix}-${String(pageNum).padStart(2, "0")}.png`;
7446
+ const filename = kind === "cover" ? "cover.png" : `article-${String(pageNum).padStart(2, "0")}.png`;
7430
7447
  const filePath = join4(outputDir, filename);
7431
7448
  await writeFile(filePath, buffer);
7432
7449
  assets.push({
@@ -7443,9 +7460,33 @@ async function renderRemoteBatch(studioUrl, body, outputDir, filenamePrefix, rou
7443
7460
  }
7444
7461
  return { assets, pageCount: data.pageCount, pages: pagesSummary };
7445
7462
  }
7463
+ async function renderLocalBatch(body, outputDir, route, kind) {
7464
+ const { result } = resolveTypesetResult(body);
7465
+ const pngs = await renderTypesetResultToPngs(result);
7466
+ const outputs = kind === "cover" ? pngs.slice(0, 1) : pngs;
7467
+ if (kind === "cover" && outputs.length === 0) {
7468
+ throw new Error("Typeset Studio failed to render cover buffer");
7469
+ }
7470
+ const assets = [];
7471
+ const pages = [];
7472
+ for (const [index, output] of outputs.entries()) {
7473
+ const pageNumber = index + 1;
7474
+ const filename = kind === "cover" ? "cover.png" : `article-${String(pageNumber).padStart(2, "0")}.png`;
7475
+ const path = join4(outputDir, filename);
7476
+ await writeFile(path, output.buffer);
7477
+ assets.push({ kind, route, path, ...kind === "page" ? { index: pageNumber } : {} });
7478
+ const images = result.pages[index].images;
7479
+ pages.push({
7480
+ page: pageNumber,
7481
+ imageCount: images.length,
7482
+ imageSources: images.map((image) => image.src)
7483
+ });
7484
+ }
7485
+ return { assets, pageCount: outputs.length, pages };
7486
+ }
7446
7487
  var typesetStudioAdapter = {
7447
7488
  name: "typeset-studio",
7448
- version: "0.2.0",
7489
+ version: "0.2.2",
7449
7490
  async doctor() {
7450
7491
  const checks = [];
7451
7492
  if (REMOTE_STUDIO_URL) {
@@ -7479,7 +7520,7 @@ var typesetStudioAdapter = {
7479
7520
  } catch (err) {
7480
7521
  checks.push({
7481
7522
  name: "@napi-rs/canvas",
7482
- ok: !REMOTE_STUDIO_URL,
7523
+ ok: Boolean(REMOTE_STUDIO_URL),
7483
7524
  message: REMOTE_STUDIO_URL ? "@napi-rs/canvas not installed locally (using Cloud Typeset Studio microservice)" : `@napi-rs/canvas initialization failed: ${err}`
7484
7525
  });
7485
7526
  }
@@ -7500,201 +7541,41 @@ var typesetStudioAdapter = {
7500
7541
  return checks;
7501
7542
  },
7502
7543
  async render(input) {
7503
- const { route, outputDir, title, bodyText, state } = input;
7544
+ const { route, outputDir, bodyText, state } = input;
7504
7545
  await mkdir(outputDir, { recursive: true });
7546
+ if (route === "blog")
7547
+ return { assets: [], pageCount: 0, pages: [] };
7505
7548
  const account = state.route.account || "default";
7506
- const highlightWords = state.route.highlight_words || [];
7507
- const footerText = input.accountVisualParams?.footer;
7508
- const watermarkText = account ? `@${account}` : undefined;
7509
- const effectiveTitle = cleanTitle(title || state.metadata?.title || "");
7510
- if (REMOTE_STUDIO_URL) {
7511
- if (route === "wechat-article") {
7512
- const coverImages2 = mapBodyImages(input.bodyImages, input.pageSpecs);
7513
- return await renderRemoteBatch(REMOTE_STUDIO_URL, {
7514
- slot: "article_cover",
7515
- route: "wechat-article",
7516
- account,
7517
- title: effectiveTitle,
7518
- markdown: `# ${effectiveTitle}`,
7519
- template: input.template,
7520
- images: coverImages2,
7521
- highlightWords,
7522
- config: { footerText, watermarkText }
7523
- }, outputDir, "cover", "wechat-article", "cover");
7524
- }
7525
- if (route === "wechat-newspic") {
7526
- const newspicSpec2 = state.intent?.newspic_render;
7527
- const paginationMode2 = input.template === "longform-3-4" ? "multi" : input.template === "poster-3-4" ? "single" : newspicSpec2?.pagination_mode || "multi";
7528
- const isMultiPage2 = paginationMode2 === "multi";
7529
- const coverImages2 = mapBodyImages(input.bodyImages, input.pageSpecs);
7530
- const coverRes = await renderRemoteBatch(REMOTE_STUDIO_URL, {
7531
- slot: "newspic_cover",
7532
- route: "wechat-newspic",
7533
- kind: "cover",
7534
- account,
7535
- title: effectiveTitle,
7536
- markdown: `# ${effectiveTitle}`,
7537
- template: input.template,
7538
- images: coverImages2,
7539
- highlightWords,
7540
- config: { footerText, watermarkText }
7541
- }, outputDir, "cover", "wechat-newspic", "cover");
7542
- if (!isMultiPage2) {
7543
- return coverRes;
7544
- }
7545
- const contentImages2 = mapBodyImages(input.bodyImages, input.pageSpecs);
7546
- const contentRes = await renderRemoteBatch(REMOTE_STUDIO_URL, {
7547
- slot: "newspic_content",
7548
- route: "wechat-newspic",
7549
- kind: "content",
7550
- account,
7551
- title: effectiveTitle,
7552
- markdown: bodyText || `# ${effectiveTitle}`,
7553
- template: input.template,
7554
- images: contentImages2,
7555
- highlightWords,
7556
- config: { footerText, watermarkText }
7557
- }, outputDir, "article", "wechat-newspic", "page");
7558
- return {
7559
- assets: [...coverRes.assets, ...contentRes.assets],
7560
- pageCount: contentRes.pageCount,
7561
- pages: contentRes.pages
7562
- };
7563
- }
7564
- }
7565
- if (route === "wechat-article") {
7566
- const coverImages2 = mapBodyImages(input.bodyImages, input.pageSpecs);
7567
- const resolved = resolveTypesetResult({
7568
- slot: "article_cover",
7569
- route: "wechat-article",
7570
- account,
7571
- title: effectiveTitle,
7572
- markdown: `# ${effectiveTitle}`,
7573
- template: input.template,
7574
- images: coverImages2,
7575
- highlightWords,
7576
- config: {
7577
- footerText: footerText ?? undefined,
7578
- watermarkText: watermarkText ?? undefined
7579
- }
7580
- });
7581
- const pngOutputs = await renderTypesetResultToPngs(resolved.result);
7582
- const coverBuffer2 = pngOutputs[0]?.buffer;
7583
- if (!coverBuffer2) {
7584
- throw new Error("Typeset Studio failed to render cover buffer");
7585
- }
7586
- const coverPath2 = join4(outputDir, "cover.png");
7587
- await writeFile(coverPath2, coverBuffer2);
7588
- const asset = {
7589
- kind: "cover",
7590
- route: "wechat-article",
7591
- path: coverPath2
7592
- };
7593
- return {
7594
- assets: [asset],
7595
- pageCount: 1,
7596
- pages: [
7597
- {
7598
- page: 1,
7599
- imageCount: resolved.result.pages[0]?.images.length || 0,
7600
- imageSources: (resolved.result.pages[0]?.images || []).map((img) => img.src)
7601
- }
7602
- ]
7603
- };
7604
- }
7605
- if (route === "blog") {
7606
- return {
7607
- assets: [],
7608
- pageCount: 0,
7609
- pages: []
7610
- };
7611
- }
7612
- const newspicSpec = state.intent?.newspic_render;
7613
- const paginationMode = input.template === "longform-3-4" ? "multi" : input.template === "poster-3-4" ? "single" : newspicSpec?.pagination_mode || "multi";
7614
- const isMultiPage = paginationMode === "multi";
7615
- const assets = [];
7616
- const pagesSummary = [];
7617
- const coverImages = mapBodyImages(input.bodyImages, input.pageSpecs);
7618
- const resolvedCover = resolveTypesetResult({
7619
- slot: "newspic_cover",
7620
- route: "wechat-newspic",
7621
- kind: "cover",
7549
+ const title = cleanTitle(input.title || state.metadata?.title || "");
7550
+ const common = {
7622
7551
  account,
7623
- title: effectiveTitle,
7624
- markdown: `# ${effectiveTitle}`,
7552
+ title,
7625
7553
  template: input.template,
7626
- images: coverImages,
7627
- highlightWords,
7628
- config: {
7629
- footerText: footerText ?? undefined,
7630
- watermarkText: watermarkText ?? undefined
7631
- }
7632
- });
7633
- const coverPngs = await renderTypesetResultToPngs(resolvedCover.result);
7634
- const coverBuffer = coverPngs[0]?.buffer;
7635
- if (!coverBuffer) {
7636
- throw new Error("Typeset Studio failed to render newspic cover buffer");
7637
- }
7638
- const coverPath = join4(outputDir, "cover.png");
7639
- await writeFile(coverPath, coverBuffer);
7640
- assets.push({
7641
- kind: "cover",
7642
- route: "wechat-newspic",
7643
- path: coverPath
7644
- });
7645
- if (!isMultiPage) {
7646
- return {
7647
- assets,
7648
- pageCount: 1,
7649
- pages: [
7650
- {
7651
- page: 1,
7652
- imageCount: resolvedCover.result.pages[0]?.images.length || 0,
7653
- imageSources: (resolvedCover.result.pages[0]?.images || []).map((img) => img.src)
7654
- }
7655
- ]
7656
- };
7657
- }
7658
- const contentImages = mapBodyImages(input.bodyImages, input.pageSpecs);
7659
- const resolvedContent = resolveTypesetResult({
7660
- slot: "newspic_content",
7554
+ images: mapBodyImages(input.bodyImages, input.pageSpecs),
7555
+ highlightWords: state.route.highlight_words || [],
7556
+ config: { footerText: input.accountVisualParams?.footer, watermarkText: `@${account}` }
7557
+ };
7558
+ const articleCover = route === "wechat-article";
7559
+ const renderRoute = articleCover ? "wechat-article" : "wechat-newspic";
7560
+ const renderBatch = (body, kind) => REMOTE_STUDIO_URL && (route === "wechat-article" || route === "wechat-newspic") ? renderRemoteBatch(REMOTE_STUDIO_URL, body, outputDir, renderRoute, kind) : renderLocalBatch(body, outputDir, renderRoute, kind);
7561
+ const cover = await renderBatch({
7562
+ ...common,
7563
+ route: renderRoute,
7564
+ slot: articleCover ? "article_cover" : "newspic_cover",
7565
+ ...articleCover ? {} : { kind: "cover" },
7566
+ markdown: `# ${title}`
7567
+ }, "cover");
7568
+ const paginationMode = input.template === "longform-3-4" ? "multi" : input.template === "poster-3-4" ? "single" : state.intent?.newspic_render?.pagination_mode || "multi";
7569
+ if (articleCover || paginationMode !== "multi")
7570
+ return cover;
7571
+ const content = await renderBatch({
7572
+ ...common,
7661
7573
  route: "wechat-newspic",
7662
7574
  kind: "content",
7663
- account,
7664
- title: effectiveTitle,
7665
- markdown: bodyText || `# ${effectiveTitle}`,
7666
- template: input.template,
7667
- images: contentImages,
7668
- highlightWords,
7669
- config: {
7670
- footerText: footerText ?? undefined,
7671
- watermarkText: watermarkText ?? undefined
7672
- }
7673
- });
7674
- const contentPngs = await renderTypesetResultToPngs(resolvedContent.result);
7675
- for (let i = 0;i < contentPngs.length; i++) {
7676
- const pageNum = i + 1;
7677
- const fileName = `article-${String(pageNum).padStart(2, "0")}.png`;
7678
- const pagePath = join4(outputDir, fileName);
7679
- await writeFile(pagePath, contentPngs[i].buffer);
7680
- assets.push({
7681
- kind: "page",
7682
- route: "wechat-newspic",
7683
- index: pageNum,
7684
- path: pagePath
7685
- });
7686
- const flowPage = resolvedContent.result.pages[i];
7687
- pagesSummary.push({
7688
- page: pageNum,
7689
- imageCount: flowPage?.images.length || 0,
7690
- imageSources: (flowPage?.images || []).map((img) => img.src)
7691
- });
7692
- }
7693
- return {
7694
- assets,
7695
- pageCount: contentPngs.length,
7696
- pages: pagesSummary
7697
- };
7575
+ slot: "newspic_content",
7576
+ markdown: bodyText || `# ${title}`
7577
+ }, "page");
7578
+ return { ...content, assets: [...cover.assets, ...content.assets] };
7698
7579
  }
7699
7580
  };
7700
7581
  var plugin = typesetStudioAdapter;